SpringBoot+WebSocket实现消息推送功能

作者:剑圣无痕 时间:2021-11-15 12:16:18 

背景

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

WebSocket简介

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

协议原理

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

SpringBoot+WebSocket实现消息推送功能

说明:

  • 客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等。

  • 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据

  • 客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信.

WebSocket与HTTP协议的区别

相同点:都是一样基于TCP的,都是可靠性传输协议。都是应用层协议。

不同点:

  • WebSocket是双向通信协议,可以双向发送或接受信息,而HTTP是单向协议

  • WebSocket需要浏览器和服务器握手进行建立连接的,而http是浏览器发起向服务器的连接。

WebSocket特点

  • 建立在TCP协议之上,服务器端的实现比较容易。

  • 数据格式比较轻量,性能开销小,通信高效。

  • 支持多种数据格式,可以发送文本、二进制数据。

  • 客户端可以与任意服务器通信,无同源限制。

应用场景

  • 即时聊天通信

  • 在线协同编辑/编辑

  • 实时数据流的拉取与推送

  • 实时地图位置

系统集成Websocket

jar包引入

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.2.4.RELEASE</version>
   <relativePath/>
</parent>

<dependency>
     <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-websocket</artifactId>
 </dependency>

Websocket配置

@Configuration
public class WebSocketConfig
{
   @Bean
   public ServerEndpointExporter serverEndpointExporter() {
       return new ServerEndpointExporter();
   }
}

具体实现

@ServerEndpoint(value="/websocket/{uid}")
@Component
public class WebSocketServer
{
   private Logger logger = LoggerFactory.getLogger(WebSocketServer.class);

private static final AtomicInteger onlineCount = new AtomicInteger(0);

private static CopyOnWriteArraySet<Session> sessionSet = new CopyOnWriteArraySet<Session>();

@OnOpen
   public void onOpen(Session session,@PathParam("uid") String uid)
   {
       logger.info("open message uid:{}",uid);
       sessionSet.add(session);
       onlineCount.incrementAndGet();
       logger.info("窗口开始监听uid:{},当前在线人数:{}",uid,onlineCount);
   }

@OnClose
   public void onClose(Session session)
   {
       String sessionId=session.getId();
       logger.info("sessionid:{} close",sessionId);
       sessionSet.remove(this);
       int count=onlineCount.decrementAndGet();
       logger.info("有一连接关闭!当前在线人数为:{}",count);
   }

@OnError
   public void onError(Session session, Throwable error)
   {
       logger.error("消息发生错误:{},Session ID: {}",error.getMessage(),session.getId());
   }

public void batchSendMesasge(String uid,String message) throws IOException
   {
       logger.info("推送消息到窗口:{},推送内容:{}",uid,message);
       for(Session session:sessionSet){
           sendMessage(session, message);
       }
   }

public void sendMessage(Session session, String message) throws IOException {

if(session!=null)
       {
           synchronized (session) {
               session.getBasicRemote().sendText(message);
           }
       }
   }

}

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

测试示例

@Controller
public class WebScoketController
{
  @Autowired
  private WebSocketServer webSocketServer;

@ResponseBody
   @RequestMapping("/sendMessage")
   public String batchMessage(String uid,String message)
   {  
       Map<String, String> map =new HashMap<String, String>();
       try
       {
           map.put("code", "200");
           webSocketServer.batchSendMesasge(uid,message);
       }
       catch (Exception e)
       {
           map.put("code", "-1");
           map.put("message", e.getMessage());
       }
       return JSON.toJSONString(map);
   }

@GetMapping("/enter")
   public String enter()
   {
       return "webscoketTest.html";
   }
}

页面请求websocket

<!DOCTYPE HTML>
<html>
  <head>
  <meta charset="utf-8">
  <title>websocket test</title>

<script type="text/javascript">
           if ("WebSocket" in window)
           {
           console.log("您的浏览器支持 WebSocket!");

var ws = new WebSocket("ws://127.0.0.1:9092/websocket/1234");
              console.log('ws连接状态:' + ws.readyState);

//打开
              ws.onopen = function()
              {
                 ws.send("message test");
                 console.log("mesage sending");
              };

//发送消息
              ws.onmessage = function (evt)
              {
                 var received_msg = evt.data;
                 alert(received_msg);
              };

//关闭
              ws.onclose = function()
              {
                 // 关闭 websocket
                 console.log("socket is close");
              };
           }

else
           {
            console.log("您的浏览器不支持 WebSocket!");
           }
     </script>
  </head>
</html>

测试效果

启动程序:运行http://localhost:9092/enter 进入页面开启websocket。

用户发送消息:http://localhost:9092/sendMessage?uid=1235&message=this%20is%20message1

执行的结果如下:

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

来源:https://juejin.cn/post/7129534763789975588

标签:SpringBoot,WebSocket,消息,推送
0
投稿

猜你喜欢

  • Java annotation元注解原理实例解析

    2022-09-08 00:42:45
  • Android通过记住密码功能学习数据存储类SharedPreferences详解及实例

    2023-05-21 21:37:49
  • Maven打包jar生成javadoc文件和source文件代码实例

    2021-08-22 21:56:52
  • Java+element实现excel的导入和导出

    2022-07-31 12:45:53
  • Spring底层事务原理解析

    2021-11-25 05:40:29
  • 将15位身份证补全为18位身份证的算法示例详解

    2022-07-15 19:33:43
  • 使用Java读取Word文件的简单例子分享

    2022-12-17 02:15:19
  • 获取Android签名证书的公钥和私钥的简单实例

    2022-10-02 22:20:13
  • Java数据结构学习之树

    2022-01-19 23:40:58
  • Android开发之App widget用法实例分析

    2021-09-18 16:25:54
  • C#实现窗体全屏的两种方法

    2021-10-06 19:14:04
  • 细说C#中的枚举:转换、标志和属性

    2021-07-10 06:01:05
  • 自定义log4j日志文件命名规则说明

    2021-11-21 16:55:51
  • 解析spring cloud ouath2中的Eureka

    2023-10-12 04:07:54
  • Compare And Swap底层原理及代码示例详解

    2022-10-10 16:06:18
  • 关于@CacheEvict无法解决分页缓存清除的解决思路

    2023-07-06 20:22:44
  • 详解Android通知栏沉浸式/透明化完整解决方案

    2023-09-06 03:59:11
  • 详述 DB2 分页查询及 Java 实现的示例

    2023-04-21 12:39:12
  • winform实现限制及解除鼠标移动范围的方法

    2023-07-17 22:11:51
  • java保证对象在内存中唯一性的实现方法

    2023-11-27 21:30:03
  • asp之家 软件编程 m.aspxhome.com