1.浅谈WebSocket

WebSocket是在HTML5基础上单个TCP连接上进行全双工通讯的协议,只要浏览器和服务器进行一次握手,就可以建立一条快速通道,两者就可以实现数据互传了。说白了,就是打破了传统的http协议的无状态传输(只能浏览器请求,服务端响应),websocket全双工通讯,就是浏览器和服务器进行一次握手,浏览器可以随时给服务器发送信息,服务器也可以随时主动发送信息给浏览器了。对webSocket原理有兴趣的客官,可以自行百度。

2.环境搭建

因为是根据项目的需求来的,所以这里我只介绍在SpringBoot下使用WebSocket的其中一种实现【STOMP协议】。因此整个工程涉及websocket使用的大致框架为SpringBoot+Maven+websocket,其他框架的基础搭建,我这里就不说了,相信各位也都很熟悉,我就直接集成websocket了。

在pox.xml加上对springBoot对WebSocket的支持:

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

这样SpringBoot就和WebSocket集成好了,我们就可以直接使用SpringBoot提供对WebSocket操作的API了

3.编码实现

①在Spring上下文中添加对WebSocket的配置

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;/*** 配置WebSocket*/
@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{@Override//注册STOMP协议的节点(endpoint),并映射指定的urlpublic void registerStompEndpoints(StompEndpointRegistry registry) {//注册一个STOMP的endpoint,并指定使用SockJS协议registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS();}@Override//配置消息代理(Message Broker)public void configureMessageBroker(MessageBrokerRegistry registry) {//点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理registry.enableSimpleBroker("/topic","/user");//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/registry.setUserDestinationPrefix("/user");}
}

介绍以上几个相关的注解和方法:

1.@EnableWebSocketMessageBroker:开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样。

2.AbstractWebSocketMessageBrokerConfigurer:继承WebSocket消息代理的类,配置相关信息。

3.registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS(); 添加一个访问端点“/endpointGym”,客户端打开双通道时需要的url,允许所有的域名跨域访问,指定使用SockJS协议。

4. registry.enableSimpleBroker("/topic","/user"); 配置一个/topic广播消息代理和“/user”一对一消息代理

5. registry.setUserDestinationPrefix("/user");点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/

②实现服务器主动向客户端推送消息

SpringBoot封装得太好,webSocket用起来太简单(好处:用起来方便,坏处:你不知道底层实现)

由于代码中涉及到定时任务,这里我们使用spring自带的定时任务功能@EnableScheduling定时任务在配置类上添加@EnableScheduling开启对定时任务的支持(亦可以添加在主启动类上,作用是一样的),在相应的方法上添加@Scheduled声明需要执行的定时任务。 不添加@EnableScheduling注解定时任务是无法生效的。感谢_Devil_K同学的指正。

1.一对多的实现:

先上后台java的代码

package com.cheng.sbjm.boot;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import com.cheng.sbjm.domain.User;@Controller
public class WebSocketController {@Autowiredprivate SimpMessagingTemplate template;//广播推送消息@Scheduled(fixedRate = 10000)public void sendTopicMessage() {System.out.println("后台广播推送!");User user=new User();user.setUserName("oyzc");user.setAge(10);this.template.convertAndSend("/topic/getResponse",user);}
}

简单介绍一下

1.SimpMessagingTemplate:SpringBoot提供操作WebSocket的对象

2.@Scheduled(fixedRate = 10000):为了测试,定时10S执行这个方法,向客户端推送

3.template.convertAndSend("/topic/getResponse",new AricResponse("后台实时推送:,Oyzc!")); :直接向前端推送消息。

3.1参数一:客户端监听指定通道时,设定的访问服务器的URL

3.2参数二:发送的消息(可以是对象、字符串等等)

在上客户端的代码(PC现代浏览器)

html页面:

<!DOCTYPE html>
<html><head><title>websocket.html</title>   <meta name="keywords" content="keyword1,keyword2,keyword3"><meta name="description" content="this is my page"><meta name="content-type" content="text/html" charset="UTF-8"><!--<link rel="stylesheet" type="text/css" href="./styles.css">-->  </head>  <body><div>  <p id="response"></p></div><!-- 独立JS --><script type="text/javascript" src="jquery.min.js" charset="utf-8"></script><script type="text/javascript" src="webSocket.js" charset="utf-8"></script><script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script><script type="text/javascript" src="stomp.js" charset="utf-8"></script></body>
</html>

JS代码[webSocket.js]

var stompClient = null; //加载完浏览器后  调用connect(),打开双通道$(function(){  //打开双通道connect()})//强制关闭浏览器  调用websocket.close(),进行正常关闭window.onunload = function() {disconnect()}function connect(){var socket = new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointOyzc"stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端stompClient.connect({},function(frame){//连接WebSocket服务端     console.log('Connected:' + frame);//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息stompClient.subscribe('/topic/getResponse',function(response){showResponse(JSON.parse(response.body));});});}//关闭双通道function disconnect(){if(stompClient != null) {stompClient.disconnect();}console.log("Disconnected");}function showResponse(message){var response = $("#response");response.append("<p>"+message.userName+"</p>");}

值得注意的是,只需要在连接服务器注册端点endPoint时,写访问服务器的全路径URL:

new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc');

其他监听指定服务器广播的URL不需要写全路径

stompClient.subscribe('/topic/getResponse',function(response){
                showResponse(JSON.parse(response.body));

});

2.一对一的实现

先上后台java的代码

package com.cheng.sbjm.boot;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Controller;
import com.cheng.sbjm.domain.User;@Controller
public class WebSocketController {@Autowiredprivate SimpMessagingTemplate template;//一对一推送消息@Scheduled(fixedRate = 10000)public void sendQueueMessage() {System.out.println("后台一对一推送!");User user=new User();user.setUserId(1);user.setUserName("oyzc");user.setAge(10);this.template.convertAndSendToUser(user.getUserId()+"","/queue/getResponse",user);}
}

简单介绍一下:

1.SimpMessagingTemplate:SpringBoot提供操作WebSocket的对象

2.@Scheduled(fixedRate = 10000):为了测试,定时10S执行这个方法,向客户端推送

3.template.convertAndSendToUser(user.getUserId()+"","/queue/getResponse",user); :直接向前端推送消息。

3.1参数一:指定客户端接收的用户标识(一般用用户ID)

3.2参数二:客户端监听指定通道时,设定的访问服务器的URL(客户端访问URL跟广播有些许不同)

3.3参数三:向目标发送消息体(实体、字符串等等)

在上客户端的代码(PC现代浏览器)

html页面:

<!DOCTYPE html>
<html><head><title>websocket.html</title><meta name="keywords" content="keyword1,keyword2,keyword3"><meta name="description" content="this is my page"><meta name="content-type" content="text/html" charset="UTF-8"><!--<link rel="stylesheet" type="text/css" href="./styles.css">--><!-- 独立css --></head>  <body><div>  <p id="response"></p></div> <!-- 独立JS --><script type="text/javascript" src="jquery.min.js" charset="utf-8"></script><script type="text/javascript" src="webSocket.js" charset="utf-8"></script><script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script><script type="text/javascript" src="stomp.js" charset="utf-8"></script></body>
</html>

JS代码[webSocket.js]

 var stompClient = null;    //加载完浏览器后  调用connect(),打开双通道$(function(){  //打开双通道connect()})  //强制关闭浏览器  调用websocket.close(),进行正常关闭window.onunload = function() {disconnect()}function connect(){var userId=1;var socket = new SockJS('http://127.0.0.1:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointOyzc"stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端stompClient.connect({},function(frame){//连接WebSocket服务端         console.log('Connected:' + frame);//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息stompClient.subscribe('/user/' + userId + '/queue/getResponse',function(response){var code=JSON.parse(response.body);                               showResponse(code)                  });});}//关闭双通道function disconnect(){if(stompClient != null) {stompClient.disconnect();}console.log("Disconnected");}function showResponse(message){var response = $("#response");response.append("<p>只有userID为"+message.userId+"的人才能收到</p>");}

与广播不同的是,在指定通道的URL加个用户标识:

stompClient.subscribe('/user/' + userId + '/queue/getResponse',function(response){
            var code=JSON.parse(response.body);                      
            showResponse(code)

});

该标识userId必须与服务器推送消息时设置的用户标识一致

以上就是实现服务器实时向客户端推送消息,各位可以按照各自的需求进行配合使用。

③实现客户端与服务器之间的直接交互,聊天室demo[在②的基础上添加了一些代码]

1.在webSocket配置中,增加2个WebSocket的代理


package com.cheng.sbjm.configure;import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;/*** 配置WebSocket*/
@Configuration
//注解开启使用STOMP协议来传输基于代理(message broker)的消息,这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer{@Override//注册STOMP协议的节点(endpoint),并映射指定的urlpublic void registerStompEndpoints(StompEndpointRegistry registry) {//注册一个STOMP的endpoint,并指定使用SockJS协议registry.addEndpoint("/endpointOyzc").setAllowedOrigins("*").withSockJS();}@Override//配置消息代理(Message Broker)public void configureMessageBroker(MessageBrokerRegistry registry) {//点对点应配置一个/user消息代理,广播式应配置一个/topic消息代理,群发(mass),单独聊天(alone)registry.enableSimpleBroker("/topic","/user","/mass","/alone");//点对点使用的订阅前缀(客户端订阅路径上会体现出来),不设置的话,默认也是/user/registry.setUserDestinationPrefix("/user");}}

"/mass"用以代理群发消息

"/alone"用以代码一对一聊天

2.java后台实现

package com.cheng.sbjm.boot;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import com.cheng.sbjm.onput.ChatRoomRequest;
import com.cheng.sbjm.onput.ChatRoomResponse;@Controller
public class WebSocketController {@Autowiredprivate SimpMessagingTemplate template;//客户端主动发送消息到服务端,服务端马上回应指定的客户端消息//类似http无状态请求,但是有质的区别//websocket可以从服务器指定发送哪个客户端,而不像http只能响应请求端//群发@MessageMapping("/massRequest")//SendTo 发送至 Broker 下的指定订阅路径@SendTo("/mass/getResponse")public ChatRoomResponse mass(ChatRoomRequest chatRoomRequest){//方法用于群发测试System.out.println("name = " + chatRoomRequest.getName());System.out.println("chatValue = " + chatRoomRequest.getChatValue());ChatRoomResponse response=new ChatRoomResponse();response.setName(chatRoomRequest.getName());response.setChatValue(chatRoomRequest.getChatValue());return response;}//单独聊天@MessageMapping("/aloneRequest") public ChatRoomResponse alone(ChatRoomRequest chatRoomRequest){//方法用于一对一测试System.out.println("userId = " + chatRoomRequest.getUserId());System.out.println("name = " + chatRoomRequest.getName());System.out.println("chatValue = " + chatRoomRequest.getChatValue());         ChatRoomResponse response=new ChatRoomResponse();response.setName(chatRoomRequest.getName());       response.setChatValue(chatRoomRequest.getChatValue());this.template.convertAndSendToUser(chatRoomRequest.getUserId()+"","/alone/getResponse",response);return response;}
}

简单介绍新的注解一下:

一.@MessageMapping("/massRequest"):类似与@RequestMapping,客户端请求服务器的URL,前提是双方端点已经打开

二.@SendTo("/mass/getResponse"):作用跟convertAndSend类似,广播发给与该通道相连的客户端

其他已经在前面解释过了。

3.html代码

<!DOCTYPE html>
<html><head><title>login.html</title><meta name="keywords" content="keyword1,keyword2,keyword3"><meta name="description" content="this is my page"><meta name="content-type" content="text/html" charset="UTF-8"><!--<link rel="stylesheet" type="text/css" href="./styles.css">--><!-- 独立css --><link rel="stylesheet" type="text/css" href="chatroom.css"></head><body>
<div><div style="float:left;width:40%"><p>请选择你是谁:</p><select id="selectName" onchange="sendAloneUser();"><option value="1">请选择</option><option value="ALong">ALong</option><option value="AKan">AKan</option><option value="AYuan">AYuan</option><option value="ALai">ALai</option><option value="ASheng">ASheng</option></select><div class="chatWindow"><p style="color:darkgrey">群聊:</p><section id="chatRecord" class="chatRecord"><p id="titleval" style="color:#CD2626;"></p></section><section class="sendWindow"><textarea name="sendChatValue" id="sendChatValue" class="sendChatValue"></textarea><input type="button" name="sendMessage" id="sendMessage" class="sendMessage" onclick="sendMassMessage()" value="发送"></section></div></div><div style="float:right; width:40%"><p>请选择你要发给谁:</p><select id="selectName2"><option value="1">请选择</option><option value="ALong">ALong</option><option value="AKan">AKan</option><option value="AYuan">AYuan</option><option value="ALai">ALai</option><option value="ASheng">ASheng</option></select><div class="chatWindow"><p style="color:darkgrey">单独聊:</p><section id="chatRecord2" class="chatRecord"><p id="titleval" style="color:#CD2626;"></p></section><section class="sendWindow"><textarea name="sendChatValue2" id="sendChatValue2" class="sendChatValue"></textarea><input type="button" name="sendMessage" id="sendMessage" class="sendMessage" onclick="sendAloneMessage()" value="发送"></section></div></div>
</div>    <!-- 独立JS --><script type="text/javascript" src="jquery.min.js" charset="utf-8"></script>    <script type="text/javascript" src="chatroom.js" charset="utf-8"></script><script type="text/javascript" src="sockjs.min.js" charset="utf-8"></script><script type="text/javascript" src="stomp.js" charset="utf-8"></script></body>
</html>

JS代码[chatroom.js]:

var stompClient = null;//加载完浏览器后  调用connect(),打开双通道$(function(){   //打开双通道connect()})//强制关闭浏览器  调用websocket.close(),进行正常关闭window.onunload = function() {disconnect()}//打开双通道function connect(){var socket = new SockJS('http://172.16.0.56:9091/sbjm-cheng/endpointOyzc'); //连接SockJS的endpoint名称为"endpointAric"stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端stompClient.connect({},function(frame){//连接WebSocket服务端console.log('Connected:' + frame);           //广播接收信息stompTopic();});}//关闭双通道function disconnect(){if(stompClient != null) {stompClient.disconnect();}console.log("Disconnected");}//广播(一对多)function stompTopic(){//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(广播接收信息)stompClient.subscribe('/mass/getResponse',function(response){  var message=JSON.parse(response.body);            //展示广播的接收的内容接收var response = $("#chatRecord");response.append("<p><span>"+message.name+":</span><span>"+message.chatValue+"</span></p>");                  });} //列队(一对一)function stompQueue(){var userId=$("#selectName").val();alert("监听:"+userId)//通过stompClient.subscribe订阅/topic/getResponse 目标(destination)发送的消息(队列接收信息)stompClient.subscribe('/user/' + userId + '/alone/getResponse',function(response){var message=JSON.parse(response.body); //展示一对一的接收的内容接收var response = $("#chatRecord2");response.append("<p><span>"+message.name+":</span><span>"+message.chatValue+"</span></p>");                   });} //选择发送给谁的时候触发连接服务器function sendAloneUser(){stompQueue();}//群发function sendMassMessage(){var postValue={};var chatValue=$("#sendChatValue");var userName=$("#selectName").val();postValue.name=userName;postValue.chatValue=chatValue.val();if(userName==1||userName==null){alert("请选择你是谁!");return;}if(chatValue==""||userName==null){alert("不能发送空消息!");return;}stompClient.send("/massRequest",{},JSON.stringify(postValue));  chatValue.val("");}//单独发function sendAloneMessage(){var postValue={};var chatValue=$("#sendChatValue2");var userName=$("#selectName").val();var sendToId=$("#selectName2").val();var response = $("#chatRecord2");postValue.name=userName;postValue.chatValue=chatValue.val();postValue.userId=sendToId;if(userName==1||userName==null){alert("请选择你是谁!");return;}if(sendToId==1||sendToId==null){alert("请选择你要发给谁!");return;}if(chatValue==""||userName==null){alert("不能发送空消息!");return;}stompClient.send("/aloneRequest",{},JSON.stringify(postValue));  response.append("<p><span>"+userName+":</span><span>"+chatValue.val()+"</span></p>");chatValue.val("");}

chatroom.css

.chatWindow{width: 100%;height: 500px;border: 1px solid blue;
}
.chatRecord{width: 100%;height: 400px;border-bottom: 1px solid blue;line-height:20px;overflow:auto;overflow-x:hidden;
}
.sendWindow{width: 100%;height: 200px;
}
.sendChatValue{width: 90%;height: 40px;}

另外还需要的3个JS包,jquery.min.js、sockjs.min.js、stomp.js。

针对 执丶笔同学提出的问题作如下补充

websocket 注解介绍

1. @MessageMapping

@MessageMapping,用户处理client发送过来的消息,被注解的方法可以具有以下参数。

  • Message:用于接收完整的消息
  • MessageHeaders:用于接收消息中的头信息
  • MessageHeaderAccessor/SimpMessageHeaderAccessor/StompHeaderAccessor:用于接收消息中的头信息,并且构建绑定Spring中的一些附加信息
  • @Headers:用于接收消息中的所有header。这个参数必须用java.util.Map
  • @Header:用于接收特定的头值
  • @Payload:接受STOMP协议中的Body,可以用@javax.validation进行注释, Spring的@Validated自动验证 (类似@RequestBody)
  • DestinationVariable: 用于提取header中destination模板变量 (类似@PathVariable)
  • java.security.Principal:接收在WebSocket HTTP握手时登录的用户

当@MessageMapping方法返回一个值时,默认情况下,该值在被序列化为Payload后,作为消息发送到向订阅者广播的“brokerChannel”,且消息destination与接收destination相同,但前缀为变为配置的值。

可以使用@SendTo指定发送的destination,将Payload消息,进行广播发送到订阅的客户端。@SendToUser是会向与当条消息关联的用户发送回执消息,还可以使用SimpMessagingTemplate发送代替SendTo/@SendToUserji进行消息的发送

2. @SubscribeMapping

@SubscribeMapping注释与@MessageMapping结合使用,以缩小到订阅消息的映射。在这种情况下,@MessageMapping注释指定目标,而@SubscribeMapping仅表示对订阅消息的兴趣。

@SubscribeMapping通常与@MessageMapping没有区别。关键区别在于,@SubscribeMapping的方法的返回值被序列化后,会发送到“clientOutboundChannel”,而不是“brokerChannel”,直接回复到客户端,而不是通过代理进行广播。这对于实现一次性的、请求-应答消息交换非常有用,并且从不占用订阅。这种模式的常见场景是当数据必须加载和呈现时应用程序初始化。

@SendTo注释@SubscribeMapping方法,在这种情况下,返回值被发送到带有显式指定目标目的地的“brokerChannel”。

3. @MessageExceptionHandler

应用程序可以使用@MessageExceptionHandler方法来处理@MessageMapping方法中的异常。        @MessageExceptionHandler方法支持灵活的方法签名,并支持与@MessageMapping方法相同的方法参数类型和返回值。与Spring MVC中的@ExceptionHandler类似。

@Controller
public abstract class BaseController {@MessageExceptionHandler(MyException.class)public xxx handleException(MyException exception) {// ...return Xxx;}
}
public class WsController extends BaseController {// ...
}

如有错漏,请各位大神指教!

DEMO地址: https://gitee.com/liyongzhi_coder/websocket-demo.git

springboot+websocket+sockjs进行消息推送【基于STOMP协议】相关推荐

  1. WebSocket实现android消息推送

    WebSocket实现android消息推送 WebSocket是HTML5出的协议,基于TCP.它实现了浏览器与服务器全双工(full-duplex)通信--允许服务器主动发送信息给客户端. Web ...

  2. springboot实现微信模板消息推送

    springboot实现微信模板消息推送 在上一篇文章我们已经知道了怎么获取openid 还不知道的可以查看我的上一篇文章springboot+微信小程序用codeid换取openid 这次我们不光要 ...

  3. springboot整合websocket实现一对一消息推送和广播消息推送

    springboot基础环境,请参考springboot文档 maven依赖 <dependency><groupId>org.springframework.boot< ...

  4. 拾人牙慧篇之——基于HTML5中websocket来实现消息推送功能

    一.写在前面 要求做一个,后台发布信息,前台能即时得到通知的消息推送功能.网上搜了也有很多方式,ajax的定时询问,Comet方式,Server-Sent方式,以及websocket.表示除了定时询问 ...

  5. 如何利用springboot快速搭建一个消息推送系统

    最近在完善毕设的路上,由于是设计一个远程控制物联网系统,所以服务端到硬件我选用了MQTT协议.因为MQTT的发布/订阅模式很适合这种场景.接下来就来聊聊遇到的一些问题以及解决思路吧. 毕设技术栈:sp ...

  6. SpringBoot如何集成MQTT消息推送

    1.需求分析 近期笔者项目需要用到mqtt实现消息推送,笔者选择emq作为mqtt服务器载体,上篇笔者讲解了如何在linux中安装mqtt服务:https://blog.csdn.net/zhangx ...

  7. 【微信小程序】1、SpringBoot整合WxJava开启消息推送

    接入微信小程序消息推送服务,可以3种方式选择其一: 1.开发者服务器接收消息推送 2.云函数接收消息推送 3.微信云托管服务接收消息推送 开发者服务器接收消息推送,开发者需要按照如下步骤完成: 1.填 ...

  8. 消息推送与同步协议的思考

    同步状态和消息推送,几乎是每个app或者设备都需要的,设计一个省流量,能简化两端逻辑,能应对业务增长的框架尤为重要. 我认为,以下方法不够好: 1.每一个状态都设计一个消息,导致每增加一个状态,服务端 ...

  9. java连接imserver_java后端IM消息推送服务开发——协议

    最近在一家saas企业使用Mqtt开发IM消息推送服务,把开发中的一些问题记录下来,项目仍在商用中,完整的消息服务包括4个模块---协议protocol,信令Signal,规则Rule,状态Statu ...

最新文章

  1. 10分钟搞定 Java 并发队列
  2. ChannelFactory.Endpoint 上的地址属性为空。ChannelFactory 的终结点必须指定一个有效的地址。...
  3. UI 实用素材|可视化后台系统UI视觉界面
  4. PowerShell 2.0远程管理之隐式远程管理
  5. #2009. 「SCOI2015」小凸玩密室
  6. UEFI+GPT安装Win10和RHEL6.5双系统
  7. matlab2c使用c++实现matlab函数系列教程-tanh函数
  8. [书目20080225]软件工程与项目管理解析
  9. <<游戏设计艺术>>笔记
  10. 德软件开发者否认蓄意植入“心血”安全漏洞
  11. eclipse插件下载速度慢,可以这样解决
  12. 140个电脑小知识、小技巧(1)
  13. Unity 导出obj模型
  14. 著名TED演讲 《用肢体语言塑造你自己》 - Amy Cuddy
  15. Rectangle矩形类
  16. 云效搭建流水线实现自动化构建部署
  17. 机械硬盘(HDD)与固态硬盘(SSD)
  18. 学习C++:C++进阶(三)CMake基础篇---用一个小型项目了解CMake及环境构建
  19. Linux C哲学家吃饭问题
  20. 数据分析实战——星巴克门店数量可视化分析

热门文章

  1. 基于业务流程的数据报表设计怎么做
  2. 基于j2ee博客重写计划
  3. Vue2与Vue3共存于一台电脑 保姆级教程
  4. 用python进行桌面程序开发
  5. sql 判断教师是否拥有邮箱
  6. 创业日记:微团队,技术应用思考
  7. Hololens 二维码识别
  8. BZOJ1492【NOI2007】货币兑换
  9. 查询大量数据表的总记录数
  10. android latinime分析,Android AOSP输入法(LatinIME)大写判断分析