来源:
前言
WebSocket 是一种在单个 TCP 连接上进行全双工 通信的协议。WebSocket 通信协议于 2011 年被 IETF 定为标准 RFC 6455,并由 RFC 7936 补充规范。WebSocket API 也被 W3C 定为标准。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
说明
本篇主要介绍在 SpringBoot 框架下,WebSocket 基于注解使用的 3 种场景:
自己给自己发消息
自己给所有客户端发送消息(不包括自己)
自己给另一个客户端发送消息
代码示例
代码结构如下:
POM 文件中的依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-websocket</artifactId > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.47</version > </dependency >
application.yml
文件:
WebSocket 配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter () { return new ServerEndpointExporter(); } }
在 resources
目录中创建一个 static
文件夹,然后新建一个 index.html
页面:
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 <!DOCTYPE HTML > <html > <head > <title > My WebSocket</title > </head > <body > <input id ="text" 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://localhost:18092/test/one" ); } else { alert('Not support websocket' ) } websocket.onerror = function () { setMessageInnerHTML("error" ); }; websocket.onopen = function (event) { } websocket.onmessage = function (event) { setMessageInnerHTML(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('text' ).value; websocket.send(message); } </script > </html >
对应上面的 3 种场景,分别给出代码:
1、自己给自己发消息
创建一个 OneWebSocket
类,用来服务端与客户端进行交互:
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 @Slf 4j@ServerEndpoint (value = "/test/one" )@Component public class OneWebSocket { private static AtomicInteger onlineCount = new AtomicInteger(0 ); @OnOpen public void onOpen (Session session) { onlineCount.incrementAndGet(); log.info("有新连接加入:{},当前在线人数为:{}" , session.getId(), onlineCount.get()); } @OnClose public void onClose (Session session) { onlineCount.decrementAndGet(); log.info("有一连接关闭:{},当前在线人数为:{}" , session.getId(), onlineCount.get()); } @OnMessage public void onMessage (String message, Session session) { log.info("服务端收到客户端[{}]的消息:{}" , session.getId(), message); this .sendMessage("Hello, " + message, session); } @OnError public void onError (Session session, Throwable error) { log.error("发生错误" ); error.printStackTrace(); } private void sendMessage (String message, Session toSession) { try { log.info("服务端给客户端[{}]发送消息{}" , toSession.getId(), message); toSession.getBasicRemote().sendText(message); } catch (Exception e) { log.error("服务端发送消息给客户端失败:{}" , e); } }
其中 @ServerEndpoint
注解是服务端与客户端交互的关键,其值 (/test/one
) 得与 index
页面中的请求路径对应。
启动服务,在浏览器请求 http://localhost:18092/index.html
,如下所示:
请求一发出,立马就会建立服务端与客户端的连接。服务端打印日志如下:
在文本框中输入内容:“你好”,然后点击 Send 按钮,浏览器效果:
服务端日志:
(备注:服务端关闭或者浏览器关闭的效果,都会导致连接断开,这里不演示)
2、自己给所有客户端发送消息(不包括自己)
创建一个 OneToManyWebSocket
类,用来服务端与客户端进行交互:
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 @Slf 4j@ServerEndpoint (value = "/test/oneToMany" )@Component public class OneToManyWebSocket { private static AtomicInteger onlineCount = new AtomicInteger(0 ); private static Map<String, Session> clients = new ConcurrentHashMap<>(); @OnOpen public void onOpen (Session session) { onlineCount.incrementAndGet(); clients.put(session.getId(), session); log.info("有新连接加入:{},当前在线人数为:{}" , session.getId(), onlineCount.get()); } @OnClose public void onClose (Session session) { onlineCount.decrementAndGet(); clients.remove(session.getId()); log.info("有一连接关闭:{},当前在线人数为:{}" , session.getId(), onlineCount.get()); } @OnMessage public void onMessage (String message, Session session) { log.info("服务端收到客户端[{}]的消息:{}" , session.getId(), message); this .sendMessage(message, session); } @OnError public void onError (Session session, Throwable error) { log.error("发生错误" ); error.printStackTrace(); } private void sendMessage (String message, Session fromSession) { for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) { Session toSession = sessionEntry.getValue(); if (!fromSession.getId().equals(toSession.getId())) { log.info("服务端给客户端[{}]发送消息{}" , toSession.getId(), message); toSession.getAsyncRemote().sendText(message); } } } }
将 index.html
页面的路径改为 /test/oneToMany
。重启服务,在浏览器开多个页面。服务端日志如下:
在浏览器第一个页面中输入内容:“你好”,会发现其它页面都收到了消息“你好”。服务端日志如下:
3、自己给另一个客户端发送消息
创建一个 OneToOneWebSocket
类,用来服务端与客户端进行交互:
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 @Slf 4j@ServerEndpoint (value = "/test/oneToOne" )@Component public class OneToOneWebSocket { private static AtomicInteger onlineCount = new AtomicInteger(0 ); private static Map<String, Session> clients = new ConcurrentHashMap<>(); @OnOpen public void onOpen (Session session) { onlineCount.incrementAndGet(); clients.put(session.getId(), session); log.info("有新连接加入:{},当前在线人数为:{}" , session.getId(), onlineCount.get()); } @OnClose public void onClose (Session session) { onlineCount.decrementAndGet(); clients.remove(session.getId()); log.info("有一连接关闭:{},当前在线人数为:{}" , session.getId(), onlineCount.get()); } @OnMessage public void onMessage (String message, Session session) { log.info("服务端收到客户端[{}]的消息[{}]" , session.getId(), message); try { MyMessage myMessage = JSON.parseObject(message, MyMessage.class ) ; if (myMessage != null ) { Session toSession = clients.get(myMessage.getUserId()); if (toSession != null ) { this .sendMessage(myMessage.getMessage(), toSession); } } } catch (Exception e) { log.error("解析失败:{}" , e); } } @OnError public void onError (Session session, Throwable error) { log.error("发生错误" ); error.printStackTrace(); } private void sendMessage (String message, Session toSession) { try { log.info("服务端给客户端[{}]发送消息[{}]" , toSession.getId(), message); toSession.getBasicRemote().sendText(message); } catch (Exception e) { log.error("服务端发送消息给客户端失败:{}" , e); } } }
将 index.html
页面的路径改为 /test/oneToOne
。重启服务,在浏览器打开两个页面。
由于服务端是解析的 JSON 字符串,这里为了简单起见,直接在浏览器输入 JSON 字符串:{"message":"你好", "userId":1}
。另一个页面收到了消息:
服务端日志:
至此,所有场景演示完毕。
Publishing this article is for the purpose of conveying more information, and does not mean agreeing with its views or confirming its description, nor does it mean that we are responsible for its authenticity. Should you have any questions or doubts about the content of the post, please don't hesitate to contact us. We will respond to you and deal with it as quickly as possible.