SpringBoot集成WebSocket【基于純H5】進(jìn)行點(diǎn)對(duì)點(diǎn)[一對(duì)一]和廣播[一對(duì)多]實(shí)時(shí)推送
之前實(shí)現(xiàn)WebSocket基于STOMP的,覺得SpringBoot封裝的太高,不怎么靈活,現(xiàn)在實(shí)現(xiàn)一個(gè)純H5的,也大概了解webSocket在內(nèi)部是怎么傳輸?shù)摹?/p>
1.環(huán)境搭建
因?yàn)樵谏弦黄赟TOMP協(xié)議實(shí)現(xiàn)的WebSocket里已經(jīng)有大概介紹過Web的基本情況了,所以在這篇就不多說了,我們直接進(jìn)入正題吧,在SpringBoot中,我們還是需要導(dǎo)入WebSocket的包。
在pox.xml加上對(duì)springBoot對(duì)WebSocket的支持:
<!-- webSocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
這里大概說一下自己的一點(diǎn)小見解:客戶端與服務(wù)器建立WebSocket連接,實(shí)際上是創(chuàng)建了一個(gè)Socket,這個(gè)Socket是共享與客戶端和服務(wù)器的。兩者只要往對(duì)應(yīng)的Socket里操作,就可以實(shí)現(xiàn)雙方實(shí)時(shí)通訊了
2.編碼實(shí)現(xiàn)
一、在SpringBoot中,添加WebSocket的配置
package com.cloud.sbjm.configure;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import com.cloud.sbjm.security.WebSocketInterceptor;
import com.cloud.sbjm.service.Imp.MyHandler;
//實(shí)現(xiàn)接口來配置Websocket請(qǐng)求的路徑和攔截器。
@Configuration
@EnableWebSocket
public class WebSocketH5Config implements WebSocketConfigurer{
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//handler是webSocket的核心,配置入口
registry.addHandler(new MyHandler(), "/myHandler/{ID}").setAllowedOrigins("*").addInterceptors(new WebSocketInterceptor());
}
}
1.@Configuration:注解標(biāo)識(shí)該類為Spring的配置類
2.@EnableWebSocket:開啟注解接收和發(fā)送消息
3.實(shí)現(xiàn)WebSocketConfigurer接口,重寫registerWebSocketHandlers方法,這是一個(gè)核心實(shí)現(xiàn)方法,配置websocket入口,允許訪問的域、注冊(cè)Handler、定義攔截器??蛻舳送ㄟ^“/myHandler/{ID}”直接訪問Handler核心類,進(jìn)行socket的連接、接收、發(fā)送等操作,這里由于還加了個(gè)攔截器,所以建立新的socket訪問時(shí),都先進(jìn)來攔截器再進(jìn)去Handler類,“new WebSocketInterceptor()”是我實(shí)現(xiàn)的攔截器,“new MyHandler()”是我實(shí)現(xiàn)的一個(gè)Handler類。
二、WebSocketInterceptor攔截器的實(shí)現(xiàn):
package com.cloud.sbjm.security;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
public class WebSocketInterceptor implements HandshakeInterceptor {
//進(jìn)入hander之前的攔截
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
if (request instanceof ServletServerHttpRequest) {
String ID = request.getURI().toString().split("ID=")[1];
System.out.println("當(dāng)前session的ID="+ID);
ServletServerHttpRequest serverHttpRequest = (ServletServerHttpRequest) request;
HttpSession session = serverHttpRequest.getServletRequest().getSession();
map.put("WEBSOCKET_USERID",ID);
}
return true;
}
@Override
public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
System.out.println("進(jìn)來webSocket的afterHandshake攔截器!");
}
}
1.實(shí)現(xiàn)了HandshakeInterceptor 接口,并實(shí)現(xiàn)了beforeHandshake該方法,該方法是在進(jìn)入Handler核心類之前進(jìn)行攔截。
這里主要實(shí)現(xiàn)的邏輯是:
截取客戶端建立webSocket連接時(shí)發(fā)送的URL地址字符串,并通過對(duì)該字符串進(jìn)行特殊標(biāo)識(shí)截取操作,獲取客戶端發(fā)送的唯一標(biāo)識(shí)(由自己定義的,一般是系統(tǒng)用戶ID唯一標(biāo)識(shí),用以標(biāo)識(shí)該用戶),并把它以鍵值對(duì)的形式放到Session里,這樣后期可以通過該session獲取它對(duì)應(yīng)的用戶ID了?!疽粋€(gè)session對(duì)應(yīng)著一個(gè)webSocketSession】
三、MyHandler核心類的實(shí)現(xiàn)
package com.cloud.sbjm.service.Imp;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
@Service
public class MyHandler implements WebSocketHandler {
//在線用戶列表
private static final Map<String, WebSocketSession> users;
static {
users = new HashMap<>();
}
//新增socket
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("成功建立連接");
String ID = session.getUri().toString().split("ID=")[1];
System.out.println(ID);
if (ID != null) {
users.put(ID, session);
session.sendMessage(new TextMessage("成功建立socket連接"));
System.out.println(ID);
System.out.println(session);
}
System.out.println("當(dāng)前在線人數(shù):"+users.size());
}
//接收socket信息
@Override
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
try{
JSONObject jsonobject = JSONObject.fromObject(webSocketMessage.getPayload());
System.out.println(jsonobject.get("id"));
System.out.println(jsonobject.get("message")+":來自"+(String)webSocketSession.getAttributes().get("WEBSOCKET_USERID")+"的消息");
sendMessageToUser(jsonobject.get("id")+"",new TextMessage("服務(wù)器收到了,hello!"));
}catch(Exception e){
e.printStackTrace();
}
}
/**
* 發(fā)送信息給指定用戶
* @param clientId
* @param message
* @return
*/
public boolean sendMessageToUser(String clientId, TextMessage message) {
if (users.get(clientId) == null) return false;
WebSocketSession session = users.get(clientId);
System.out.println("sendMessage:" + session);
if (!session.isOpen()) return false;
try {
session.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 廣播信息
* @param message
* @return
*/
public boolean sendMessageToAllUsers(TextMessage message) {
boolean allSendSuccess = true;
Set<String> clientIds = users.keySet();
WebSocketSession session = null;
for (String clientId : clientIds) {
try {
session = users.get(clientId);
if (session.isOpen()) {
session.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
allSendSuccess = false;
}
}
return allSendSuccess;
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
System.out.println("連接出錯(cuò)");
users.remove(getClientId(session));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("連接已關(guān)閉:" + status);
users.remove(getClientId(session));
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 獲取用戶標(biāo)識(shí)
* @param session
* @return
*/
private Integer getClientId(WebSocketSession session) {
try {
Integer clientId = (Integer) session.getAttributes().get("WEBSOCKET_USERID");
return clientId;
} catch (Exception e) {
return null;
}
}
}
1.實(shí)現(xiàn)了WebSocketHandler接口,并實(shí)現(xiàn)了關(guān)鍵的幾個(gè)方法。
① afterConnectionEstablished(接口提供的):建立新的socket連接后回調(diào)的方法。主要邏輯是:將成功建立連接的webSocketSssion放到定義好的常量[private static final Map<String, WebSocketSession> users;]中去。這里也截取客戶端訪問的URL的字符串,拿到標(biāo)識(shí),以鍵值對(duì)的形式講每一個(gè)webSocketSession存到users里,以記錄每個(gè)Socket。
② handleMessage(接口提供的):接收客戶端發(fā)送的Socket。主要邏輯是:獲取客戶端發(fā)送的信息。這里之所以可以獲取本次Socket的ID,是因?yàn)榭蛻舳嗽诘谝淮芜M(jìn)行連接時(shí),攔截器進(jìn)行攔截后,設(shè)置好ID,這樣也說明,雙方在相互通訊的時(shí)候,只是對(duì)第一次建立好的socket持續(xù)進(jìn)行操作。
③ sendMessageToUser(自己定義的):發(fā)送給指定用戶信息。主要邏輯是:根據(jù)用戶ID從常量users(記錄每一個(gè)Socket)中,獲取Socket,往該Socket里發(fā)送消息,只要客戶端還在線,就能收到該消息。
④sendMessageToAllUsers (自己定義的):這個(gè)廣播消息,發(fā)送信息給所有socket。主要邏輯是:跟③類型,只不過是遍歷整個(gè)users獲取每一個(gè)socket,給每一個(gè)socket發(fā)送消息即可完廣播發(fā)送
⑤handleTransportError(接口提供的):連接出錯(cuò)時(shí),回調(diào)的方法。主要邏輯是:一旦有連接出錯(cuò)的Socket,就從users里進(jìn)行移除,有提供該Socket的參數(shù),可直接獲取ID,進(jìn)行移除。這個(gè)在客戶端沒有正常關(guān)閉連接時(shí),會(huì)進(jìn)來,所以在開發(fā)客戶端時(shí),記得關(guān)閉連接
⑥afterConnectionClosed(接口提供的):連接關(guān)閉時(shí),回調(diào)的方法。主要邏輯:一旦客戶端/服務(wù)器主動(dòng)關(guān)閉連接時(shí),將個(gè)socket從users里移除,有提供該Socket的參數(shù),可直接獲取ID,進(jìn)行移除。
后臺(tái)的開發(fā)就開發(fā)完了,大家有沒有發(fā)現(xiàn)比基于STOMP協(xié)議實(shí)現(xiàn)要靈活得多?
四、客戶端頁面的實(shí)現(xiàn)【基于H5】
不需要加入任何的JS包
<!DOCTYPE html>
<html>
<head>
<title>socket.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" rel="external nofollow" >-->
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
<!-- 公共JS -->
<script type="text/javascript" src="../webSocket/jquery.min.js"></script>
<script type="text/javascript">
var userID="888";
var websocket=null;
$(function() {
//創(chuàng)建WebSocket
connectWebSocket();
})
//強(qiáng)制關(guān)閉瀏覽器 調(diào)用websocket.close(),進(jìn)行正常關(guān)閉
window.onunload = function() {
//關(guān)閉連接
closeWebSocket();
}
//建立WebSocket連接
function connectWebSocket(){
console.log("開始...");
//建立webSocket連接
websocket = new WebSocket("ws://127.0.0.1:9091/cloud-sbjm/myHandler/ID="+userID);
//打開webSokcet連接時(shí),回調(diào)該函數(shù)
websocket.onopen = function () {
console.log("onpen");
}
//關(guān)閉webSocket連接時(shí),回調(diào)該函數(shù)
websocket.onclose = function () {
//關(guān)閉連接
console.log("onclose");
}
//接收信息
websocket.onmessage = function (msg) {
console.log(msg.data);
}
}
//發(fā)送消息
function send(){
var postValue={};
postValue.id=userID;
postValue.message=$("#text").val();
websocket.send(JSON.stringify(postValue));
}
//關(guān)閉連接
function closeWebSocket(){
if(websocket != null) {
websocket.close();
}
}
</script>
</body>
</html>
頁面比較簡(jiǎn)單,簡(jiǎn)單解釋一下:
1.new WebSocket("ws://127.0.0.1:9091/cloud-sbjm/myHandler/ID="+userID),與服務(wù)器建立webSocket連接,后面的ID="+userID,是動(dòng)態(tài)參數(shù),跟服務(wù)器配置Handler的訪問地址時(shí)對(duì)應(yīng)"/myHandler/{ID}"。
2.H5也提供多個(gè)回調(diào)函數(shù)
onopen:打開webSokcet連接時(shí),回調(diào)該函數(shù)
onclose:關(guān)閉webSocket連接時(shí),回調(diào)該函數(shù)
onmessage:服務(wù)器給該socket發(fā)送消息時(shí),回調(diào)該函數(shù),獲取消息
websocket.send(JSON.stringify(postValue));:給Socket發(fā)送消息,服務(wù)器獲取
websocket.close();客戶端主要關(guān)閉連接,會(huì)觸發(fā)客戶端的onclose方法和服務(wù)器的afterConnectionClosed方法
到此服務(wù)端的開發(fā)也完成了,下面執(zhí)行一下程序效果圖:
一、建立連接
客戶端:


服務(wù)器:

二、發(fā)送消息
客戶端:


服務(wù)器:

三、服務(wù)器主動(dòng)推送消息
服務(wù)器代碼:


到此已經(jīng)完成了,各位可以根據(jù)自己需求進(jìn)行修改,這會(huì)靈活多了!
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
idea導(dǎo)入springboot項(xiàng)目沒有maven的解決
這篇文章主要介紹了idea導(dǎo)入springboot項(xiàng)目沒有maven的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
spring boot自定義log4j2日志文件的實(shí)例講解
下面小編就為大家分享一篇spring boot自定義log4j2日志文件的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2017-11-11
Java基于JDBC連接數(shù)據(jù)庫及顯示數(shù)據(jù)操作示例
這篇文章主要介紹了Java基于JDBC連接數(shù)據(jù)庫及顯示數(shù)據(jù)操作,結(jié)合實(shí)例形式分析了Java使用jdbc進(jìn)行mysql數(shù)據(jù)庫連接與數(shù)據(jù)讀取、顯示等相關(guān)操作技巧,需要的朋友可以參考下2018-06-06
java泛型的局限探究及知識(shí)點(diǎn)總結(jié)
在本篇內(nèi)容里小編給大家分享的是一篇關(guān)于java泛型的局限探究及知識(shí)點(diǎn)總結(jié)內(nèi)容,有需要的朋友們可以跟著學(xué)習(xí)參考下。2021-07-07
關(guān)于spring.factories的常用配置項(xiàng)說明
這篇文章主要介紹了關(guān)于spring.factories的常用配置項(xiàng)說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-07-07
基于Java的Socket多客戶端Client-Server聊天程序的實(shí)現(xiàn)
這篇文章主要介紹了基于Java的Socket多客戶端Client-Server聊天程序的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
java實(shí)現(xiàn)簡(jiǎn)單的圖書借閱系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單的圖書借閱系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
Spring Cache優(yōu)化數(shù)據(jù)庫訪問的項(xiàng)目實(shí)踐
本文主要介紹了Spring Cache優(yōu)化數(shù)據(jù)庫訪問的項(xiàng)目實(shí)踐,將創(chuàng)建一個(gè)簡(jiǎn)單的圖書管理應(yīng)用作為示例,并演示如何通過緩存減少對(duì)數(shù)據(jù)庫的頻繁查詢,感興趣的可以了解一下2024-01-01

