• Spring Boot集成WebSocket实现消息推送


    背景

    项目中经常会用到消息推送功能,关于推送技术的实现,我们通常会联想到轮询、comet长连接技术,虽然这些技术能够实现,但是需要反复连接,对于服务资源消耗过大,随着技术的发展,HtML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。本文将介绍如何采用websocket实现消息推送。

    WebSocket简介

    WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。浏览器和服务器仅需一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

    协议原理

    Websocket协议基于Http协议,针对Http协议进行了相关的改善,且Websocket协议也需要建立TCP连接来实现数据传输,具体实现如下图:

    说明:

    • 客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等。
    • 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据
    • 客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信.

    WebSocket与HTTP协议的区别

    • 相同点:都是一样基于TCP的,都是可靠性传输协议。都是应用层协议。
    • 不同点:
    1. WebSocket是双向通信协议,可以双向发送或接受信息,而HTTP是单向协议
    2. WebSocket需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接。

    WebSocket特点

    • 建立在TCP协议之上,服务器端的实现比较容易。
    • 数据格式比较轻量,性能开销小,通信高效。
    • 支持多种数据格式,可以发送文本、二进制数据。
    • 客户端可以与任意服务器通信,无同源限制。

    应用场景

    • 即时聊天通信
    • 在线协同编辑/编辑
    • 实时数据流的拉取与推送
    • 实时地图位置

    系统集成Websocket

    jar包引入

    1. <parent>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-parent</artifactId>
    4. <version>2.2.4.RELEASE</version>
    5. <relativePath/>
    6. </parent>
    7. <dependency>
    8. <groupId>org.springframework.boot</groupId>
    9. <artifactId>spring-boot-starter-websocket</artifactId>
    10. </dependency>
    11. 复制代码

    Websocket配置

    1. @Configuration
    2. public class WebSocketConfig
    3. {
    4. @Bean
    5. public ServerEndpointExporter serverEndpointExporter() {
    6. return new ServerEndpointExporter();
    7. }
    8. }
    9. 复制代码

    具体实现

    1. @ServerEndpoint(value="/websocket/{uid}")
    2. @Component
    3. public class WebSocketServer
    4. {
    5. private Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
    6. private static final AtomicInteger onlineCount = new AtomicInteger(0);
    7. private static CopyOnWriteArraySet<Session> sessionSet = new CopyOnWriteArraySet<Session>();
    8. @OnOpen
    9. public void onOpen(Session session,@PathParam("uid") String uid)
    10. {
    11. logger.info("open message uid:{}",uid);
    12. sessionSet.add(session);
    13. onlineCount.incrementAndGet();
    14. logger.info("窗口开始监听uid:{},当前在线人数:{}",uid,onlineCount);
    15. }
    16. @OnClose
    17. public void onClose(Session session)
    18. {
    19. String sessionId=session.getId();
    20. logger.info("sessionid:{} close",sessionId);
    21. sessionSet.remove(this);
    22. int count=onlineCount.decrementAndGet();
    23. logger.info("有一连接关闭!当前在线人数为:{}",count);
    24. }
    25. @OnError
    26. public void onError(Session session, Throwable error)
    27. {
    28. logger.error("消息发生错误:{},Session ID: {}",error.getMessage(),session.getId());
    29. }
    30. public void batchSendMesasge(String uid,String message) throws IOException
    31. {
    32. logger.info("推送消息到窗口:{},推送内容:{}",uid,message);
    33. for(Session session:sessionSet){
    34. sendMessage(session, message);
    35. }
    36. }
    37. public void sendMessage(Session session, String message) throws IOException {
    38. if(session!=null)
    39. {
    40. synchronized (session) {
    41. session.getBasicRemote().sendText(message);
    42. }
    43. }
    44. }
    45. }
    46. 复制代码

    说明: @OnOpen :当有新的WebSocket连接进入时调用 @OnClose:当有WebSocket连接关闭时调用 @OnError :当有WebSocket抛出异常时调用 @OnMessage:当接收到字符串消息时,对该方法进行回调

    测试示例

    1. @Controller
    2. public class WebScoketController
    3. {
    4. @Autowired
    5. private WebSocketServer webSocketServer;
    6. @ResponseBody
    7. @RequestMapping("/sendMessage")
    8. public String batchMessage(String uid,String message)
    9. {
    10. Map<String, String> map =new HashMap<String, String>();
    11. try
    12. {
    13. map.put("code", "200");
    14. webSocketServer.batchSendMesasge(uid,message);
    15. }
    16. catch (Exception e)
    17. {
    18. map.put("code", "-1");
    19. map.put("message", e.getMessage());
    20. }
    21. return JSON.toJSONString(map);
    22. }
    23. @GetMapping("/enter")
    24. public String enter()
    25. {
    26. return "webscoketTest.html";
    27. }
    28. }
    29. 复制代码

    页面请求websocket

    1. <!DOCTYPE HTML>
    2. <html>
    3. <head>
    4. <meta charset="utf-8">
    5. <title>websocket test</title>
    6. <script type="text/javascript">
    7. if ("WebSocket" in window)
    8. {
    9. console.log("您的浏览器支持 WebSocket!");
    10. var ws = new WebSocket("ws://127.0.0.1:9092/websocket/1234");
    11. console.log('ws连接状态:' + ws.readyState);
    12. //打开
    13. ws.onopen = function()
    14. {
    15. ws.send("message test");
    16. console.log("mesage sending");
    17. };
    18. //发送消息
    19. ws.onmessage = function (evt)
    20. {
    21. var received_msg = evt.data;
    22. alert(received_msg);
    23. };
    24. //关闭
    25. ws.onclose = function()
    26. {
    27. // 关闭 websocket
    28. console.log("socket is close");
    29. };
    30. }
    31. else
    32. {
    33. console.log("您的浏览器不支持 WebSocket!");
    34. }
    35. </script>
    36. </head>
    37. </html>
    38. 复制代码

    测试效果

    执行的结果如下:

    1. open message uid:1234
    2. [nio-9092-exec-2] c.s.f.w.controller.WebSocketServer: 窗口开始监听uid:1234,当前在线人数:1
    3. [nio-9092-exec-5] c.s.f.w.controller.WebSocketServer: 推送消息到窗口:1234,推送内容:this is message
    4. 复制代码

    总结

    本文对于websocket进行简单讲解,关于其他的高级功能将在后续文章中进行说明,如有疑问,请随时反馈。

     

  • 相关阅读:
    在边缘计算场景中使用Dapr
    uniapp 全局置灰、哀悼置灰(可动态、同时支持nvue、vue)插件 Ba-Gray
    【0基础学算法】归并排序(超详细讲解+私人笔记+源码)
    Java中的<<、>>、>>>运算符
    计算机毕业设计 SSM消防物资存储系统 物资存储系统 应急物资库智慧存储系统Java Vue MySQL数据库 远程调试 代码讲解
    Ansible角色定制实例
    一卷到底,大明哥带你横扫 Netty
    const char *p,char const *p和char *const p区别
    从table1 里获取每个person_id最大end_date的数据。(inner join)
    vim打开文件时执行命令
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/126603451