前言
javascript
WebSocket 是 HTML5 開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議。它對目前主流瀏覽器(Chrome, Safari,FireFox,IE等)的主流版本兼容性較好;它能夠輕鬆實現客戶端和服務器端雙向數據傳輸和通信而且支持多種數據通訊格式(文本和二進制流),適合於對數據的實時性要求比較強的場景,如通訊、直播、共享桌面,特別適合於客戶與服務頻繁交互的狀況下,如實時共享、多人協做等平臺。css
WebSocket規範html
在 WebSocket中,瀏覽器和服務器只須要完成一次握手,二者之間就直接能夠建立持久性的鏈接,並進行雙向數據傳輸,更好的節省服務器資源和帶寬,而且可以更實時地進行通信。瀏覽器經過 JavaScript 向服務器發出創建 WebSocket 鏈接的請求,鏈接創建之後,客戶端和服務器端就能夠經過 TCP 鏈接直接交換數據。java
STOMP子協議jquery
WebSocket是個規範,在實際的實現中有HTML5規範中的WebSocket API和WebSocket的子協議STOMP。STOMP(Simple Text Oriented Messaging Protocol)簡單(流)文本定向消息協議。web
STOMP是基於幀的協議,它的前身是TTMP協議(一個簡單的基於文本的協議),專爲消息中間件設計。是屬於消息隊列的一種協議, 和AMQP, JMS平級. 它的簡單性恰巧能夠用於定義websocket的消息體格式. STOMP協議不少MQ都已支持, 好比RabbitMq, ActiveMq。生產者(發送消息)、消息代理、消費者(訂閱而後收到消息)。跨域
今天咱們就基於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()
});
});
結果驗證
羣聊相關截圖
單聊截圖
總結
以上就是採用STOMP子協議實現WebSocket通信的關鍵代碼和過程。其實實現WebSocket的方法不止一種,除了STOMP外還有,原生的WebSocket協議實現方式、Netty的是實現方式等。後面咱們將重點基於後兩種實現WebSocket高實時性網絡通信,請繼續關注!有任何關於這塊的問題,請下方留言,一塊兒討論。