# 一. WebSocket 介绍
项目学习案例,仅供参考!如有更好的方案和想法,欢迎互相交流(关于我)!
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send () 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。
var webSocket = new WebSocket(url, [protocol] );
以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
# 二。项目结构
sw-websocket # WebSocket Demo
- src # 源文件目录
-- main # 主目录
-- java # Java 源文件目录
-- com.lmay.websocket # Java 包路径
-- config # 项目配置类
-- controller # 控制器
-- exception # 自定义异常
-- handler # 处理器
-- service # 服务层
-- impl # 服务实现类
-- task # Spring 定时任务
WebSocketApplication # 应用启动
-- resources # 项目资源目录
-- static # 静态资源
-- templates # HTML模版
application.yml # 项目配置文件
log4j2.xml # 日志配置文件
-- test # 测试目录
pom.xml # Maven 资源库配置文件
# 三。项目架构
- JDK 8
- Spring Boot 2
- Maven
- Guava
- Gson
- lombok
- log4j2
- Spring Scheduled
- …
# 四。源码实现(核心源码)
- WebSocket.java [WebSocket 处理类]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
| package com.lmay.websocket.handler;
import com.google.common.base.Strings; import com.lmay.common.common.Response; import com.lmay.common.exception.CommonException; import com.lmay.common.utils.GsonUtils; import com.lmay.websocket.service.WebSocketService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component;
import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap;
@Slf4j @Component @ServerEndpoint(value = "/ws/{userId}") public class WebSocket { public static WebSocketService webSocketService;
private static ConcurrentHashMap<String, WebSocket> webSocket = new ConcurrentHashMap<>();
private String userId;
private Session session;
@OnOpen public void onOpen(@PathParam(value = "userId") String userId, Session session) { try { this.session = session; this.userId = userId; webSocket.put(this.userId, this); long count = webSocket.size(); log.info("用户[" + this.userId + "]加入连接在线总数[" + count + "]"); sendMessage(GsonUtils.toJson(Response.success("连接成功"))); } catch (CommonException e) { sendMessage(GsonUtils.toJson(Response.failed(e.getError()))); } }
@OnClose public void onClose() { webSocket.remove(this.userId); long count = WebSocket.webSocket.size(); log.info("用户[" + this.userId + "]关闭连接在线总数[" + count + "]"); }
@OnMessage public void onMessage(String json, Session session) { if (!Strings.isNullOrEmpty(json)) { Response<Map<String, Object>> result = webSocketService.selectUserByUserId(userId); result.getData().put("msg", json); sendMessage(GsonUtils.toJson(result)); } else { sendMessage(GsonUtils.toJson(Response.failed("连接成功"))); } }
@OnError public void onError(Session session, Throwable e) { log.error("websocket IO异常", e); }
private void sendMessage(String message) { try { this.session.getBasicRemote().sendText(message); } catch (IOException e) { log.error("发送用户[" + this.userId + "]消息[" + message + "]失败", e); } }
private void sendMessage(String userId, String message) { if (webSocket.get(userId) != null) { webSocket.get(userId).sendMessage(message); } }
public static void sendMessage(Set<String> userIds, String message) { for (String userId : userIds) { webSocket.get(userId).sendMessage(message); } }
public static void sendMessageAll(String message) { for (String userId : webSocket.keySet()) { webSocket.get(userId).sendMessage(message); } }
public static synchronized long getCount() { return webSocket.size(); } }
|
- WebSocketConfig.java [WebSocket 配置类]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| package com.lmay.websocket.config;
import com.lmay.websocket.handler.WebSocket; import com.lmay.websocket.service.WebSocketService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration public class WebSocketConfig {
@Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); }
@Autowired public void setMessageService(WebSocketService webSocketService) { WebSocket.webSocketService = webSocketService; } }
|
- WebSocketTask.java [WebSocket 定时任务类 - 模拟服务器向前端推送消息]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package com.lmay.websocket.task;
import com.lmay.websocket.service.WebSocketService; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component;
@Slf4j @Component public class WebSocketTask { private final WebSocketService webSocketService;
public WebSocketTask(WebSocketService webSocketService) { this.webSocketService = webSocketService; }
@Scheduled(cron = "0/30 * * * * ?") public void initiativeSendMsg() { log.info("服务器主动向客户端推送消息: {}", webSocketService.initiativeSendMsg()); } }
|
- index.ftl [WebSocket 页面]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>WebSocket Demo</title> </head>
<body> Welcome WebSocket Demo:<br/> <input id="msg" name="msg" type="text" /> <button onclick="send();">Send</button> <button onclick="closeWebSocket();">Close</button> <div id="message">
</div> </body>
<script type="text/javascript"> var websocket = null; if ('WebSocket' in window) { websocket = new WebSocket("ws://127.0.0.1:81/ws/10000"); } else { alert('Not support websocket') }
websocket.onerror = function () { setMessageInnerHTML("error"); };
websocket.onopen = function (event) { setMessageInnerHTML("open"); };
websocket.onmessage = function (event) { setMessageInnerHTML("Receive Message: " + event.data); };
websocket.onclose = function () { setMessageInnerHTML("close"); };
window.onbeforeunload = function () { websocket.close(); };
function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML + '<br/>'; }
function closeWebSocket() { websocket.close(); }
function send() { var message = document.getElementById('msg').value; websocket.send(message); } </script> </html>
|
# 五. WebSocket 示例
-
WebSocket 服务连接 [Receive Message: {“code”:200,“msg”:“操作成功”,“data”:“连接成功”} ];
-
客户端发送消息 [Receive Message: {“code”:200,“msg”:“操作成功”,“data”:{“userName”:“WebSocket Test”,“msg”:“o (゚Д゚) っ啥!”,“userId”:“10000”}} ];
-
服务端向客户端发送消息 [Receive Message: {“code”:200,“msg”:“操作成功”,“data”:“欢迎使用 WebSocket 服务!”} ];
# 效果:
# 六。源码地址
GitHub [spring-boot-examples]