1 Websocket是什么
WebSocket 是一種基于 TCP 協(xié)議的全雙工通信協(xié)議,可以在瀏覽器和服務(wù)器之間建立實(shí)時(shí)、雙向的數(shù)據(jù)通信
。可以用于在線聊天、在線游戲、實(shí)時(shí)數(shù)據(jù)展示等場(chǎng)景。與傳統(tǒng)的 HTTP 協(xié)議不同,WebSocket 可以保持長(zhǎng)連接
,實(shí)時(shí)傳輸數(shù)據(jù),避免了頻繁的 HTTP 請(qǐng)求和響應(yīng),節(jié)省了網(wǎng)絡(luò)帶寬和服務(wù)器資源,提高了應(yīng)用程序的性能和用戶體驗(yàn)。
2 Websocket可以做什么
項(xiàng)目中大部分的請(qǐng)求都是前臺(tái)主動(dòng)發(fā)送給后臺(tái),后臺(tái)接收后返回?cái)?shù)據(jù)給前臺(tái),返回?cái)?shù)據(jù)后這個(gè)連接就終止了。如果要實(shí)現(xiàn)實(shí)時(shí)通信,通用的方式是采用 HTTP 協(xié)議
不斷發(fā)送請(qǐng)求。但這種方式即浪費(fèi)帶寬(HTTP HEAD 是比較大的),又消耗服務(wù)器 CPU 占用(沒(méi)有信息也要接受請(qǐng)求)。
websocket可以建立長(zhǎng)連接實(shí)現(xiàn)雙向通信,客戶端和服務(wù)端都可以主動(dòng)的向?qū)Ψ桨l(fā)送消息
。
例如:
假設(shè)張三今天有個(gè)快遞快到了,但是張三忍耐不住,就每隔十分鐘給快遞員或者快遞站打電話,詢問(wèn)快遞到了沒(méi),每次快遞員就說(shuō)還沒(méi)到,等到下午張三的快遞到了,但是,快遞員不知道哪個(gè)電話是張三的,(可不是只有張三打電話,還有李四,王五),所以只能等張三打電話,才能通知他,你的快遞到了。
而最好的情況是,張三給快遞員第一次打電話時(shí),說(shuō)明自己的身份,快遞員記錄下來(lái),讓自己和快遞員之間形成一對(duì)一的關(guān)系可以互相聯(lián)系到。張三也不用再次給快遞員打電話了,快遞到了快遞員會(huì)主動(dòng)聯(lián)系張三通知他來(lái)取。
后者就是websocket模式,在客戶端斷開(kāi)WebSocket連接或Server端中斷連接前,不需要客戶端和服務(wù)端重新發(fā)起連接請(qǐng)求。在海量并發(fā)及客戶端與服務(wù)器交互負(fù)載流量大的情況下,極大的節(jié)省了網(wǎng)絡(luò)帶寬資源的消耗,有明顯的性能優(yōu)勢(shì),且客戶端發(fā)送和接受消息是在同一個(gè)持久連接上發(fā)起,實(shí)現(xiàn)了“真·長(zhǎng)鏈接”,實(shí)時(shí)性優(yōu)勢(shì)明顯。
在項(xiàng)目中聊天功能也是類似的邏輯,A發(fā)送了消息B立刻就要收到,A和B都屬于前臺(tái)客戶端,不可能直接從一個(gè)前臺(tái)不走服務(wù)器傳輸給另一個(gè)前臺(tái),過(guò)程一定是前臺(tái) —> 服務(wù)器 -> 前臺(tái)
。那前臺(tái)客戶端B接收消息是被動(dòng)的,需要服務(wù)器主動(dòng)發(fā)送消息請(qǐng)求,這就用到了WebSocket。大體流程如下圖:
3 Springboot整合Websocket
3.1 服務(wù)端
-
添加依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
-
添加Websocket配置文件
@Configuration public class WebSocketConfig { /** * 注入ServerEndpointExporter, * 這個(gè)bean會(huì)自動(dòng)注冊(cè)使用了@ServerEndpoint注解聲明的Websocket */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
-
Webscoket操作類
import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.logging.Logger; import static java.util.logging.Level.WARNING; @Component @ServerEndpoint("/websocket/{userId}") // 接口路徑 ws://localhost:9001/webSocket/userId; public class WebSocket { private static final Logger log = Logger.getLogger(WebSocket.class.getName()); //與某個(gè)客戶端的連接會(huì)話,需要通過(guò)它來(lái)給客戶端發(fā)送數(shù)據(jù) private Session session; /** * 用戶ID */ private String userId; //concurrent包的線程安全Set,用來(lái)存放每個(gè)客戶端對(duì)應(yīng)的MyWebSocket對(duì)象。 //雖然@Component默認(rèn)是單例模式的,但springboot還是會(huì)為每個(gè)websocket連接初始化一個(gè)bean,所以可以用一個(gè)靜態(tài)set保存起來(lái)。 // 注:底下WebSocket是當(dāng)前類名 private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>(); // 用來(lái)存在線連接用戶信息 private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>(); /** * 鏈接成功調(diào)用的方法 */ @OnOpen public void onOpen(Session session, @PathParam(value="userId")String userId) { try { this.session = session; this.userId = userId; webSockets.add(this); sessionPool.put(userId, session); log.info("【websocket消息】有新的連接,總數(shù)為:"+webSockets.size()); } catch (Exception e) { } } /** * 鏈接關(guān)閉調(diào)用的方法 */ @OnClose public void onClose() { try { webSockets.remove(this); sessionPool.remove(this.userId); log.info("【websocket消息】連接斷開(kāi),總數(shù)為:"+webSockets.size()); } catch (Exception e) { } } /** * 收到客戶端消息后調(diào)用的方法 * * @param message */ @OnMessage public void onMessage(String message) { log.info("【websocket消息】收到客戶端消息:"+message); } /** 發(fā)送錯(cuò)誤時(shí)的處理 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { log.log(WARNING,"用戶錯(cuò)誤,原因:"+error.getMessage()); error.printStackTrace(); } /** * 下面為服務(wù)端向客戶端發(fā)送消息 */ // 此為廣播消息 public void sendAllMessage(String message) { log.info("【websocket消息】廣播消息:"+message); for(WebSocket webSocket : webSockets) { try { if(webSocket.session.isOpen()) { webSocket.session.getAsyncRemote().sendText(message); } } catch (Exception e) { e.printStackTrace(); } } } // 此為單點(diǎn)消息 public void sendOneMessage(String userId, String message) { Session session = sessionPool.get(userId); if (session != null&&session.isOpen()) { try { log.info("【websocket消息】 單點(diǎn)消息:"+message); session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } // 此為單點(diǎn)消息(多人) public void sendMoreMessage(String[] userIds, String message) { for(String userId:userIds) { Session session = sessionPool.get(userId); if (session != null&&session.isOpen()) { try { log.info("【websocket消息】 單點(diǎn)消息:"+message); session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } } }
上面的操作類中有幾個(gè)主要的點(diǎn)需要注意
-
@ServerEndpoint
注解:注解的value屬性為調(diào)用時(shí)路徑。類似于@RequestMapping("")
設(shè)置的路徑。添加該注解,才會(huì)被注冊(cè)。 -
@OnOpen
:鏈接成功調(diào)用的方法。 -
@OnMessage
:客戶端可以主動(dòng)給服務(wù)端發(fā)送消息,此方法接受數(shù)據(jù)并處理。 -
@OnError
:發(fā)送錯(cuò)誤時(shí)的處理。
-
-
服務(wù)端主動(dòng)向客戶端發(fā)送消息
測(cè)試用例
@Resource private WebSocket webSocket; @GetMapping("sendMessage") public AjaxResult queryById(@Validated @NotNull Long id){ //創(chuàng)建業(yè)務(wù)消息信息 JSONObject obj = new JSONObject(); obj.put("msgId", "00000001");//消息id obj.put("msgTxt", "服務(wù)端->客戶端發(fā)送消息");//消息內(nèi)容 //全體發(fā)送 webSocket.sendAllMessage(obj.toJSONString()); //單個(gè)用戶發(fā)送 (userId為用戶id) //webSocket.sendOneMessage(userId, obj.toJSONString()); //多個(gè)用戶發(fā)送 (userIds為多個(gè)用戶id,逗號(hào)‘,’分隔) //webSocket.sendMoreMessage(userIds, obj.toJSONString()); return AjaxResult.success("執(zhí)行成功"); }
3.2 客戶端
<script>
export default {
name: "index",
data() {
return {
websock:null
};
},
mounted() {
//初始化websocket
this.initWebSocket()
},
destroyed: function () {
//關(guān)閉連接
this.websocketclose();
},
methods: {
initWebSocket: function () { // 建立連接
// WebSocket與普通的請(qǐng)求所用協(xié)議有所不同,ws等同于http,wss等同于https
var userId = "user-001";
var url = "ws://localhost:9001/websocket/" + userId;
this.websock = new WebSocket(url);
this.websock.onopen = this.websocketonopen;
this.websock.onerror = this.websocketonerror;
this.websock.onmessage = this.websocketonmessage;
this.websock.onclose = this.websocketclose;
},
// 連接成功后調(diào)用
websocketonopen: function () {
console.log("WebSocket連接成功");
},
// 發(fā)生錯(cuò)誤時(shí)調(diào)用
websocketonerror: function (e) {
console.log("WebSocket連接發(fā)生錯(cuò)誤");
},
// 接收后端消息
websocketonmessage: function (e) {
console.log("eee",e)
var data = eval("(" + e.data + ")");
},
// 關(guān)閉連接時(shí)調(diào)用
websocketclose: function (e) {
console.log("connection closed (" + e.code + ")");
},
//向后臺(tái)發(fā)送消息
sendMessage(){
let params = {
id:"00000",
msg:"前端消息測(cè)試"
}
let a = JSON.stringify(params);
this.websock.send(a)
},
}
</script>
websockke中內(nèi)置了連接、錯(cuò)誤、接收消息、接收消息、關(guān)閉的回調(diào),下面自己定義的websocketonopen、websocketonmessage
等方法的名字可以隨便起,但需要在初始化時(shí)賦值給websocket對(duì)應(yīng)的屬性。
屬性 | 事件處理回調(diào)函數(shù) | 描述 |
---|---|---|
onopen | websocketonopen | 建立連接時(shí)觸發(fā) |
onerror | websocketonerror | 通信發(fā)生錯(cuò)誤時(shí)觸發(fā) |
onmessage | websocketonmessage | 客戶端接收服務(wù)端消息時(shí)觸發(fā) |
onclose | websocketclose | 連接關(guān)閉觸發(fā) |
send | 無(wú) | 不需要回調(diào)函數(shù),建議直接調(diào)用websocket的send方法 |
下面測(cè)試下完整流程
-
創(chuàng)建連接
ws
同http,wss
同https,后面路徑為服務(wù)段@ServerEndpoint
注解的值,以此選擇連接不同連接端。 -
前臺(tái)客戶端主動(dòng)發(fā)送消息給服務(wù)端
調(diào)用
websock.send()
方法,但消息的類型需要注意,socket本質(zhì)是傳輸字節(jié)流,所以不能把任意類型的數(shù)據(jù)直接傳入send方法,限制類型如下:等接收到數(shù)據(jù)以后通過(guò)IO包裝類都可以把數(shù)據(jù)還原。
服務(wù)端成功接收到消息:
-
服務(wù)端主動(dòng)向客戶端發(fā)送消息
客戶端成功接收
可以看到是一個(gè)請(qǐng)求長(zhǎng)連接:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-691389.html
- 綠色向上的箭頭是客戶端發(fā)送給服務(wù)端
- 紅色向下的箭頭是服務(wù)端發(fā)送給客戶端
上述測(cè)試的流程如下:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-691389.html
到了這里,關(guān)于SpringBoot整合Websocket(Java websocket怎么使用)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!