websocket网络编程实战 - 用STOMP协议实如今线群聊聊天室和一对一单聊天室

前言
javascript

WebSocket 是 HTML5 开始提供的一种在单个 TCP 链接上进行全双工通信的协议它对目前主流浏览器(Chrome, Safari,FireFox,IE等)的主流版本兼容性较好;它能够轻松实现客户端和服务器端双向数据传输和通信而且支持多种数据通讯格式(文本和二进制流),适合于对数据的实时性要求比较强的场景,如通讯、直播、共享桌面,特别适合于客户与服务频繁交互的状况下,如实时共享、多人协做等平台。css


WebSocket规范html

在 WebSocket中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输,更好的节省服务器资源和带宽,而且可以更实时地进行通信。浏览器经过 JavaScript 向服务器发出创建 WebSocket 链接的请求,链接创建之后,客户端和服务器端就能够经过 TCP 链接直接交换数据。java

image.pngimage.pngimage.png


STOMP子协议jquery

WebSocket是个规范,在实际的实现中有HTML5规范中的WebSocket API和WebSocket的子协议STOMP。STOMP(Simple Text Oriented Messaging Protocol)简单(流)文本定向消息协议。web

STOMP是基于帧的协议,它的前身是TTMP协议(一个简单的基于文本的协议),专为消息中间件设计。是属于消息队列的一种协议, 和AMQP, JMS平级. 它的简单性恰巧能够用于定义websocket的消息体格式. STOMP协议不少MQ都已支持, 好比RabbitMq, ActiveMq。生产者(发送消息)、消息代理、消费者(订阅而后收到消息)。跨域

image.png

今天咱们就基于STOMP子协议实现一个集群聊和单聊于一体的Web在线聊天室。浏览器


代码设计实现服务器

1、服务器部分实现
websocket

/**
*
@author andychen https://blog.51cto.com/14815984
*
@description:STOMP协议配置
*/
/**
* 开启使用Stomp协议来传输基于Broker的消息
* 控制器才支持使用@MessageMapping
*/
@Configuration
@EnableWebSocketMessageBroker
public class StompConfig implements WebSocketMessageBrokerConfigurer {
   @Resource
   
private AppConfig config;

   
/**
    * 注册STOMP协议终结点,并映射到指定的URL(客户端访问时须要)
    * 并容许跨域访问
    * 指定使用SocketJS协议
    *
@param registry
   
*/
   
@Override
   
public void registerStompEndpoints(StompEndpointRegistry registry) {
       registry.addEndpoint(config.stomp_endpoint)
               .setAllowedOrigins("*")
               .withSockJS();
   
}

   /**
    * 注册消息代理
    *
@param registry
   
*/
   
@Override
   
public void configureMessageBroker(MessageBrokerRegistry registry) {
       String[] prefixs =  config.broker_prefixs.split(";");
       
registry.enableSimpleBroker(prefixs[0],prefixs[1]);
       
registry.setUserDestinationPrefix(config.stomp_queue_name);
   
}
}
/**
*
@author andychen https://blog.51cto.com/14815984
*
@description:WebMVC 配置
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

   @Resource
   
private AppConfig config;

   
/**
    * 配置Controller和View的映射
    *
@param registry
   
*/
   
@Override
   
public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController(config.mvc_path)
                .setViewName(config.mvc_view);
   
}
}
/**
*
@author andychen https://blog.51cto.com/14815984
*
@description:应用配置类
*/
@Component
public class AppConfig {
   @Value("${config.mvc_path}")
   public String mvc_path;

   
@Value("${config.mvc_vew}")
   public String mvc_view;

   
@Value("${config.stomp_endpoint}")
   public String stomp_endpoint;

   
@Value("${config.stomp_broker_prefixs}")
   public String broker_prefixs;

   
@Value("${config.stomp_queue_name}")
   public String stomp_queue_name;
}
/**
*
@author andychen https://blog.51cto.com/14815984
*
@description:STOMP在线聊天室控制器类
*/
@Controller
public class StompController {
   /**
    * 定义消息发送模板
    */
   
@Autowired
   
private SimpMessagingTemplate sendTemplate;
   
/**
    * 群聊:发给全部订阅了代理并加入群聊的用户
    *
@return
   
*/
   
@MessageMapping("/mass/request")
   /**
    * 先发送到用户订阅的代理队列中,Broker再转发到订阅的用户终端
    */
   
@SendTo("/mass/send")
   public ChatMessage mass(ChatMessage message){
       System.out.println("sender:"+message.getSender()+", message content:"
               
+message.getContent());
       return
message;
   
}
   /**
    * 一对一单聊
    *
@return 发送到单个请求的用户端
    */
   
@MessageMapping("/single/request")
   public ChatMessage single(ChatMessage message){
       System.out.println("sender:"+message.getSender()+", message content:"
               
+message.getContent()+", recevier:"+message.getRecevier());
       this
.sendTemplate.convertAndSendToUser(message.getRecevier(), "/single", message);
       return
message;
   
}
}
/**
*
@author andychen https://blog.51cto.com/14815984
*
@description:聊天消息实体
*/
public class ChatMessage {

   public String getSender() {
       return sender;
   
}

   public void setSender(String sender) {
       this.sender = sender;
   
}

   public String getContent() {
       return content;
   
}

   public void setContent(String content) {
       this.content = content;
   
}

   public String getRecevier() {
       return recevier;
   
}

   public void setRecevier(String recevier) {
       this.recevier = recevier;
   
}

   /**
    * 发送者
    */
   
private String sender;
   
/**
    * 消息内容
    */
   
private String content;
   
/**
    * 接受者
    */
   
private String recevier;
}


2、Web和JS部分实现

<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org">
<head>
   <meta
charset="UTF-8">
   <meta
name="aplus-terminal" content="1">
   <meta
name="apple-mobile-web-app-title" content="">
   <meta
name="apple-mobile-web-app-capable" content="yes">
   <meta
name="apple-mobile-web-app-status-bar-style" content="black-translucent">
   <meta
name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
   <meta
name="format-detection" content="telephone=no, address=no">
   <title>
STOMP在线聊天室</title>
   <link
rel="stylesheet" th:href="@{/css/chatroom.css}" type="text/css"/>
</head>
<body>
   <div>
       <div
class="window_frame">
           <span><e
style="font-weight: bold;">选择你的网名:</e>
           <select
id="selectSender">
               <option
value="">请选择..</option>
               <option
value="zhangsan">zhangsan</option>
               <option
value="lisi">lisi</option>
               <option
value="wangwu">wangwu</option>
               <option
value="zhaoliu">zhaoliu</option>
               <option
value="chenqi">chenqi</option>
               <option
value="qianba">qianba</option>
           </select>
           <e
style="font-weight: bold;">群聊:</e>
           </span>
           <div
class="chatWindow">
               <section
class="chatRecord">
                   <div
id="mass_div" class="mobile-page"></div>
               </section>
               <section
class="sendWindow">
                   <textarea
name="txtContent" id="txtContent"  class="send_box"></textarea>
                   <input
type="button" id="btnSend" value="发送" class="send_btn"/>
               </section>
           </div>
       </div>
       <div
class="window_frame">
           <span><e
style="font-weight: bold;">选择聊天的对象:</e>
               <select
id="selectRecevier">
                   <option
value="">请选择..</option>
                   <option
value="zhangsan">zhangsan</option>
                   <option
value="lisi">lisi</option>
                   <option
value="wangwu">wangwu</option>
                   <option
value="zhaoliu">zhaoliu</option>
                   <option
value="chenqi">chenqi</option>
                   <option
value="qianba">qianba</option>
               </select>
               <e
style="font-weight: bold;">单聊:</e>
           </span>
           <div
class="chatWindow">
               <section
class="chatRecord">
                   <div
id="single_div" class="mobile-page"></div>
               </section>
               <section
class="sendWindow">
                   <textarea
name="txtContent2" id="txtContent2" class="send_box"></textarea>
                   <input
type="button" id="btnSend2" value="发送" class="send_btn"/>
               </section>
           </div>
       </div>
   </div>
   <script
type="text/javascript" th:src="@{/js/sockjs.min.js}"></script>
   <script
type="text/javascript" th:src="@{/js/stomp.min.js}"></script>
   <script
type="text/javascript" th:src="@{/js/jquery-1.9.1.min.js}"></script>
   <script
type="text/javascript" th:src="@{/js/chatroom.js}"></script>
</body>
</html>
ChatRoom = {
   client:null
};
/**
* 选择发送人
*/
ChatRoom.selectSender = function () {
   ChatRoom.subscribeSingle();
};
/**
* 发送群聊信息
*/
ChatRoom.sendMassMsg = function () {
   let msg = {};
   
let content = $("#txtContent").val().trim();
   
let userName = $("#selectSender").val();
   
msg.sender = userName;
   
msg.content = content;
   
if("" === userName){
       alert("请选择你的身份!")
       return;
   
}
   if("" === content){
       alert("发送的消息内容不能为空!")
       return;
   
}
   ChatRoom.client.send("/mass/request", {}, JSON.stringify(msg));
   
$("#txtContent").val("")
}
/**
* 发送单聊信息
*/
ChatRoom.sendSingleMsg = function () {
   let content = $("#txtContent2").val().trim();
   
let sender = $("#selectSender").val();
   
let recevier = $("#selectRecevier").val().trim();
   
let container = $("#single_div");
   
let msg = {
       sender: sender,
       
content: content,
       
recevier: recevier
   };
   
if("" === sender){
       alert("请选择你的身份!");
       
return;
   
}
   if("" === recevier){
       alert("请选择消息接收人!");
       
return;
   
}
   if("" === content){
       alert("发送消息不能为空!");
       
return;
   
}
   ChatRoom.client.send("/single/request",{}, JSON.stringify(msg));
   
container.append("<div class='user-group'>" +
       "          <div class='user-msg'>" +
       "                <span class='user-reply'>"+content+"</span>" +
       "                <i class='triangle-user'></i>" +
       "          </div><span style='padding-top:10px;'>" +sender+
       "     </span></div>");
   
$("#txtContent2").val("");
};
/**
* 链接服务器
*/
ChatRoom.connect = function () {
   try{
       let socket = new SockJS('/chatendpoint1');//采用SockJS链接服务器endpoint:chatendpoint1
       
ChatRoom.client = Stomp.over(socket);//使用STOMP协议
       
ChatRoom.client.connect({}, function (info) {
           console.log("服务器链接: "+info);
           
//订阅广播消息
           
ChatRoom.subscribeMass();
       
});
   
}catch (e) {
       console.log("链接异常:"+e);
   
}
};
/**
* 服务器广播消息订阅
*/
ChatRoom.subscribeMass = function(){
   ChatRoom.client.subscribe('/mass/send', function (data) {
        let msgObj = JSON.parse(data.body);
       
//渲染消息
       
let container = $("#mass_div");
       
let userName = $("#selectSender").val();
       
if(msgObj.sender === userName){
            container.append("<div class='user-group'>" +
                "          <div class='user-msg'>" +
                "                <span class='user-reply'>"+msgObj.content+"</span>" +
                "                <i class='triangle-user'></i>" +
                "          </div><span style='padding-top:10px;'>" +userName+
                "     </span></div>");
       
}else{
            container.append("     <div class='admin-group'><span style='padding-top:10px;'>"+
                msgObj.sender+
                "</span><div class='admin-msg'>"+
                "    <i class='triangle-admin'></i>"+
                "    <span class='admin-reply'>"+msgObj.content+"</span>"+
                "</div>"+
                "</div>");
       
}
   });
};
/**
* 服务器单播消息订阅
*/
ChatRoom.subscribeSingle = function(){
   let userName = $("#selectSender").val();
   
if("" === userName){
       alert("请选择你的身份");
       
return;
   
}
   //alert("开始监听用户端:"+userName);
   
$("title").text('STOMP在线聊天室 - '+userName);
   
//订阅特定用户发送的消息
   
ChatRoom.client.subscribe("/squeue/"+userName+"/single", function (data) {
        let msgObj = JSON.parse(data.body);
       
let container = $("#single_div");
       
container.append("     <div class='admin-group'><span style='padding-top:10px;'>"+
           msgObj.sender+
           "</span><div class='admin-msg'>"+
           "    <i class='triangle-admin'></i>"+
           "    <span class='admin-reply'>"+msgObj.content+"</span>"+
           "</div>"+
           "</div>");
   
});
};
/**
* 断开链接
*/
ChatRoom.disconnect = function (server) {
   if(null != ChatRoom.client){
       ChatRoom.client.disconnect();
   
}
   console.log(server);
   
console.log("断开与服务器链接!");
};

/**
* 窗口卸载以前事件
*/
window.onbeforeunload = function () {
   ChatRoom.disconnect();
}

/**
* 页面加载完成后
*/
$(function () {
   //创建链接
   
ChatRoom.connect();
   
//注册事件
   
$("#selectSender").change(function () {
       ChatRoom.selectSender();
   
});
   
$("#btnSend").click(function () {
       ChatRoom.sendMassMsg();
   
});
   
$("#btnSend2").click(function () {
       ChatRoom.sendSingleMsg()
   });
});


结果验证

image.png

群聊相关截图

image.png

单聊截图

image.png

image.pngimage.png

image.png

总结

以上就是采用STOMP子协议实现WebSocket通信的关键代码和过程。其实实现WebSocket的方法不止一种,除了STOMP外还有,原生的WebSocket协议实现方式、Netty的是实现方式等。后面咱们将重点基于后两种实现WebSocket高实时性网络通信,请继续关注!有任何关于这块的问题,请下方留言,一块儿讨论。

相关文章
相关标签/搜索