SpringBoot如何添加WebSocket的方法示例
一、WebSocket介紹
網(wǎng)站上的即時(shí)通訊是很常見的,比如網(wǎng)頁的QQ,聊天系統(tǒng)等。按照以往的技術(shù)能力通常是采用輪詢、Comet技術(shù)解決。
HTTP協(xié)議是非持久化的,單向的網(wǎng)絡(luò)協(xié)議,在建立連接后只允許瀏覽器向服務(wù)器發(fā)出請(qǐng)求后,服務(wù)器才能返回相應(yīng)的數(shù)據(jù)。當(dāng)需要即時(shí)通訊時(shí),通過輪詢?cè)谔囟ǖ臅r(shí)間間隔(如1秒),由瀏覽器向服務(wù)器發(fā)送Request請(qǐng)求,然后將最新的數(shù)據(jù)返回給瀏覽器。這樣的方法最明顯的缺點(diǎn)就是需要不斷的發(fā)送請(qǐng)求,而且通常HTTP request的Header是非常長(zhǎng)的,為了傳輸一個(gè)很小的數(shù)據(jù) 需要付出巨大的代價(jià),是很不合算的,占用了很多的寬帶。
缺點(diǎn):會(huì)導(dǎo)致過多不必要的請(qǐng)求,浪費(fèi)流量和服務(wù)器資源,每一次請(qǐng)求、應(yīng)答,都浪費(fèi)了一定流量在相同的頭部信息上
然而WebSocket的出現(xiàn)可以彌補(bǔ)這一缺點(diǎn)。在WebSocket中,只需要服務(wù)器和瀏覽器通過HTTP協(xié)議進(jìn)行一個(gè)握手的動(dòng)作,然后單獨(dú)建立一條TCP的通信通道進(jìn)行數(shù)據(jù)的傳送。
二、WebSocket運(yùn)行機(jī)制
WebSocket 是 HTML5 一種新的協(xié)議,也是一個(gè)典型的應(yīng)用層協(xié)議。它實(shí)現(xiàn)了瀏覽器與服務(wù)器全雙工通信,能更好的節(jié)省服務(wù)器資源和帶寬并達(dá)到實(shí)時(shí)通訊,它建立在 TCP 之上,同 HTTP 一樣通過 TCP 來傳輸數(shù)據(jù),但是它和 HTTP 最大不同是:
WebSocket 是一種雙向通信協(xié)議,在建立連接后,WebSocket 服務(wù)器和 Browser/Client Agent 都能主動(dòng)的向?qū)Ψ桨l(fā)送或接收數(shù)據(jù),就像 Socket 一樣;
WebSocket 需要類似 TCP 的客戶端和服務(wù)器端通過握手連接,連接成功后才能相互通信。
非 WebSocket 模式傳統(tǒng) HTTP 客戶端與服務(wù)器的交互如下圖所示:

使用 WebSocket 模式客戶端與服務(wù)器的交互如下圖:

三、WebSocket實(shí)現(xiàn)
這里通過一個(gè)簡(jiǎn)單的聊天例程,實(shí)現(xiàn)spring boot +websocket代碼的演示。
引入maven
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
配置類WebConfig
package org.antry.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @ClassName WebSocketConfig
* @Description TODO
* @Autor TT
* @Date 2020/10/9 23:00
* @Version 1.0
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocket類
在這個(gè)類里,有一個(gè)集合,用來存放所有連接。
當(dāng)有新的連接進(jìn)來時(shí),在onOpen()方法中,我們每個(gè)連接都會(huì)存下對(duì)應(yīng)id作為標(biāo)識(shí)。假設(shè)這個(gè)id已經(jīng)存過,則會(huì)關(guān)閉此連接,防止同一個(gè)id多處登錄。
連接斷開時(shí),在onClose()方法中,將此連接移出集合。
當(dāng)有新的消息發(fā)送過來時(shí),會(huì)去遍歷連接,找到對(duì)應(yīng)接收人id的連接,然后把消息推送給接收人。
package org.antry.websocket;
import org.springframework.stereotype.Component;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @ClassName MyWebsocket
* @Description TODO
* @Autor TT
* @Date 2020/10/10 11:02
* @Version 1.0
*/
@ServerEndpoint(value = "/websocket/{user}")
@Component
public class MyWebsocket {
// 通過類似GET請(qǐng)求方式傳遞參數(shù)的方法(服務(wù)端采用第二種方法"WebSocketHandler"實(shí)現(xiàn))
// websocket = new WebSocket("ws://127.0.0.1:18080/testWebsocket?id=23&name=Lebron");
/**
* 在線人數(shù)
*/
public static AtomicInteger onlineNumber = new AtomicInteger(0);
/**
* 所有的對(duì)象,每次連接建立,都會(huì)將我們自己定義的MyWebsocket存放到List中,
*/
public static List<MyWebsocket> webSockets = new CopyOnWriteArrayList<MyWebsocket>();
/**
* 會(huì)話,與某個(gè)客戶端的連接會(huì)話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
*/
private Session session;
/**
* 每個(gè)會(huì)話的用戶
*/
private String user;
/**
* 建立連接
*
* @param session
*/
@OnOpen
public void onOpen(Session session, @PathParam("user") String user) {
System.err.println(user);
if (user == null || "".equals(user)) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
onlineNumber.incrementAndGet();
for (MyWebsocket MyWebsocket : webSockets) {
if (user.equals(MyWebsocket.user)) {
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
}
this.session = session;
this.user = user;
webSockets.add(this);
}
/**
* 連接關(guān)閉
*/
@OnClose
public void onClose() {
onlineNumber.decrementAndGet();
webSockets.remove(this);
}
/**
* 收到客戶端的消息
*
* @param message 消息
* @param session 會(huì)話
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam("user") String user) {
System.err.println(message);
String[] strArr = message.split("~");
pushMessage(user,strArr[0],strArr[1]);
}
/**
* 發(fā)送消息
*
* @param message 消息
*/
public void sendMessage(String message) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 消息推送
*
* @param message
* @param uuid uuid為空則推送全部人員
*/
public static void pushMessage(String user, String message, String uuid) {
if (uuid == null || "".equals(uuid)) {
for (MyWebsocket MyWebsocket : webSockets) {
MyWebsocket.sendMessage(user + ":" + message);
}
} else {
for (MyWebsocket MyWebsocket : webSockets) {
if (uuid.equals(MyWebsocket.user)) {
MyWebsocket.sendMessage(message);
}
}
}
}
}
兩個(gè)簡(jiǎn)單的前端頁面,他們的不同是,接收id和發(fā)送id不同。
testOne.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天窗口1</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="public/dist/lib/uploader/zui.uploader.min.css" rel="external nofollow" rel="external nofollow" rel="stylesheet">
</head>
<body>
<div>
<div class="input-group">
<input type="text" class="form-control" id="msg">
<span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="send()">發(fā)送</button>
</span>
</div>
</div>
<script src="public/dist/lib/jquery/jquery.js"></script>
<script src="public/dist/js/zui.min.js"></script>
<script src="public/layer/layer.js"></script>
<script src="public/js/function.js"></script>
<script src="public/js/testOne.js"></script>
</body>
</html>
testOne.js
var websocket = null;
var sendId = 0;
var receiveId = 1;
//---------------------------
/**
* 初始化websocket
* @param id
*/
doInit(sendId)
function doInit(sendId){
if ('WebSocket' in window) {
websocket = new WebSocket("ws:localhost:10086/websocket/" + sendId);
} else {
alert("瀏覽器不支持");
}
websocket.onopen = function () {
addMessage("webscoket已經(jīng)連接成功");
};
websocket.onclose = function () {
addMessage("webscoket連接失敗");
};
websocket.onmessage = function (event) {
alert("收到消息:"+event.data);
};
websocket.onerror = function () {
addMessage("webscoket連接失敗");
};
}
/**
* 發(fā)送一條消息
*/
function send(){
websocket.send(value('msg')+"~"+receiveId);
}
/**
* 測(cè)試打印調(diào)試信息用
* @param msg
*/
function addMessage(msg) {
console.log(msg)
}
testTwo.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天窗口2</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="public/dist/lib/uploader/zui.uploader.min.css" rel="external nofollow" rel="external nofollow" rel="stylesheet">
</head>
<body>
<div>
<div class="input-group">
<input type="text" class="form-control" id="msg">
<span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="send()">發(fā)送</button>
</span>
</div>
</div>
<script src="public/dist/lib/jquery/jquery.js"></script>
<script src="public/dist/js/zui.min.js"></script>
<script src="public/layer/layer.js"></script>
<script src="public/js/function.js"></script>
<script src="public/js/testTwo.js"></script>
</body>
</html>
testTwo.js
var websocket = null;
var sendId = 1;
var receiveId = 0;
//---------------------------
/**
* 初始化websocket
* @param id
*/
doInit(sendId)
function doInit(receiveId){
if ('WebSocket' in window) {
websocket = new WebSocket("ws:localhost:10086/websocket/" + sendId);
} else {
alert("瀏覽器不支持");
}
websocket.onopen = function () {
addMessage("webscoket已經(jīng)連接成功");
};
websocket.onclose = function () {
addMessage("webscoket連接失敗");
};
websocket.onmessage = function (event) {
alert("收到消息:"+event.data);
};
websocket.onerror = function () {
addMessage("webscoket連接失敗");
};
}
/**
* 發(fā)送一條消息
*/
function send(){
websocket.send(value('msg')+"~"+receiveId);
}
/**
* 測(cè)試打印調(diào)試信息用
* @param msg
*/
function addMessage(msg) {
console.log(msg)
}
分別用兩個(gè)瀏覽器,打開這兩個(gè)頁面進(jìn)行訪問。結(jié)果如下


到此這篇關(guān)于SpringBoot如何添加WebSocket的方法示例的文章就介紹到這了,更多相關(guān)SpringBoot添加WebSocket內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot項(xiàng)目打成jar包后無法獲取static下的靜態(tài)資源文件的問題分析
這篇文章主要介紹了springboot項(xiàng)目打成jar包后無法獲取static下的靜態(tài)資源文件的問題分析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08
MAC配置java+jmeter環(huán)境變量過程解析
這篇文章主要介紹了MAC配置java+jmeter環(huán)境變量過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
SpringBoot+Quartz實(shí)現(xiàn)定時(shí)任務(wù)的代碼模版分享
quartz?是一款開源且豐富特性的Java?任務(wù)調(diào)度庫(kù),用于實(shí)現(xiàn)任務(wù)調(diào)度和定時(shí)任務(wù),本文主要和大家分享一個(gè)SpringBoot整合Quartz實(shí)現(xiàn)定時(shí)任務(wù)的代碼模版,需要的可以參考一下2023-06-06
你知道怎么從Python角度學(xué)習(xí)Java基礎(chǔ)
這篇文章主要為大家詳細(xì)介紹了Python角度學(xué)習(xí)Java基礎(chǔ)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-02-02
用java等語言仿360首頁拼音輸入全模糊搜索和自動(dòng)換膚
這篇文章主要為大家詳細(xì)介紹了仿360首頁支持拼音輸入全模糊搜索和自動(dòng)換膚的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Java生成隨機(jī)姓名、性別和年齡的實(shí)現(xiàn)示例
這篇文章主要介紹了Java生成隨機(jī)姓名、性別和年齡的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

