前言
? ? ? ? 在上篇文章tcp編程中,我們實(shí)現(xiàn)了C++客戶端與java服務(wù)器之間的通信,客戶端發(fā)送了一個(gè)消息給服務(wù)器,今天我們要實(shí)現(xiàn)基于WebSocket實(shí)現(xiàn)服務(wù)器主動(dòng)向前端推送消息,并且以服務(wù)器接收到C++客戶端的消息主動(dòng)向前端推送消息的觸發(fā)條件。
了解Websocket
WebSocket 的誕生背景
????????在早期,網(wǎng)站為了實(shí)現(xiàn)推送技術(shù),通常使用輪詢(或稱(chēng)為短輪詢)。輪詢是指瀏覽器每隔一段時(shí)間向服務(wù)器發(fā)出 HTTP 請(qǐng)求,然后服務(wù)器返回最新的數(shù)據(jù)給客戶端。這種方式存在明顯的缺點(diǎn):瀏覽器需要不斷地向服務(wù)器發(fā)出請(qǐng)求,而每次請(qǐng)求都包含較長(zhǎng)的頭部信息,導(dǎo)致帶寬資源浪費(fèi)。
????????為了解決這個(gè)問(wèn)題,HTML5?定義了?WebSocket 協(xié)議,它能更好地節(jié)省服務(wù)器資源和帶寬,并且能夠更實(shí)時(shí)地進(jìn)行通訊。
WebSocket 的基本原理
-
WebSocket 是什么?
- WebSocket 是一種網(wǎng)絡(luò)傳輸協(xié)議,基于?TCP?實(shí)現(xiàn)。
- 它在單個(gè) TCP 連接上進(jìn)行全雙工通信,位于 OSI 模型的應(yīng)用層。
- 與 HTTP 不同,WebSocket 需要先創(chuàng)建連接,然后可以進(jìn)行雙向數(shù)據(jù)傳輸。
-
WebSocket 握手過(guò)程
- 客戶端通過(guò) WebSocket 構(gòu)造函數(shù)創(chuàng)建 WebSocket 對(duì)象,連接到服務(wù)器的 WebSocket URL。
- 客戶端發(fā)送類(lèi)似于 HTTP 請(qǐng)求的報(bào)文,服務(wù)器返回接受 WebSocket 協(xié)議的響應(yīng)。
- 握手成功后,客戶端和服務(wù)器之間的 WebSocket 連接建立,后續(xù)數(shù)據(jù)以幀序列的形式傳輸。
WebSocket 與 HTTP 的區(qū)別
- WebSocket 使用類(lèi)似于 HTTP 的握手連接,但數(shù)據(jù)傳輸更高效。
- 較少的控制開(kāi)銷(xiāo):頭部信息較小。
- 更強(qiáng)的實(shí)時(shí)性:實(shí)時(shí)通信,避免等待請(qǐng)求響應(yīng)。
- 保持連接狀態(tài):WebSocket 是全雙工通信,不需要反復(fù)發(fā)出請(qǐng)求。無(wú)需重新發(fā)起連接。
- 更好的二進(jìn)制支持:處理二進(jìn)制內(nèi)容。
- 可以支持?jǐn)U展:自定義子協(xié)議。
?WebSocket可以做什么
- 推文
- 廣告
- 聊天室
- 公告消息
????...................?
服務(wù)器端
打開(kāi)idea,創(chuàng)建一個(gè)Springboot項(xiàng)目,添加WebSocket依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
創(chuàng)建一個(gè)WebSocket控制類(lèi),代碼如下
/**
* WebSocket操作類(lèi)
*/
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}") // 接口路徑 ws://localhost:8081/webSocket/userId;
public class WebSocket {
/**
* 與某個(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)前類(lèi)名
*/
private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>();
/**
* 用來(lái)存所有在線連接用戶信息,用來(lái)存每個(gè)session
*/
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.error("用戶錯(cuò)誤,原因:"+error.getMessage());
error.printStackTrace();
}
/**
* 廣播消息
*/
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();
}
}
}
/**
* 向多人發(fā)消息
*/
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è)和上篇文章一樣的ServerThread類(lèi),添加@Component注解并添加WebSocket的調(diào)用代碼
@Component//注冊(cè)為Springboot管理的bean,否則不能使用Springboot的其它bean
public class faceServerThread implements Runnable{
@Autowired
private studentDao dao;//用于訪問(wèn)數(shù)據(jù)庫(kù)
WebSocket webSocket=new WebSocket();//用于使用WebSocket中的方法
@Override
public void run() {
try {
ServerSocket server=new ServerSocket(8888);
Socket socket;
byte[] buffer = new byte[1024];
int len;
student stu;
while(true)
{
socket=server.accept();//處于阻塞狀態(tài),直到客戶端連接
System.out.println("客戶端連接成功");
InputStream input=socket.getInputStream();//用于讀取客戶端發(fā)來(lái)的字節(jié)流
while ((len=input.read(buffer))!=-1){
String str = new String(buffer, 0, len);
//此處為代碼修改部分
stu=dao.selectById(str);
if(stu!=null){
System.out.println(str);
webSocket.sendOneMessage("0",str);
}
///
}
System.out.println("接收消息完畢");
//System.out.println("收到消息:"+id);
}
} catch (IOException e) {
System.out.println("客戶端連接失敗:");
e.printStackTrace();
}
}
}
此處實(shí)現(xiàn)了runnable接口,是為了另外開(kāi)一條線程,不與Springboot沖突。?
在啟動(dòng)類(lèi)中添加啟動(dòng)線程
@SpringBootApplication
public class FreshmandemoApplication {
public static void main(String[] args){
ConfigurableApplicationContext context=SpringApplication.run(FreshmandemoApplication.class, args);
faceServerThread faceThread=context.getBean(faceServerThread.class);
new Thread(faceThread).start();
}
}
前端客戶端?
添加一個(gè)HTML文件,實(shí)現(xiàn)WebSocket
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<script type="text/javascript">
var socket;
if (typeof (WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
} else {
console.log("您的瀏覽器支持WebSocket");
//實(shí)現(xiàn)化WebSocket對(duì)象,指定要連接的服務(wù)器地址與端口 建立連接
var reqUrl = "http://localhost:8081/websocket/0" ;
socket = new WebSocket(reqUrl.replace("http", "ws"));
//打開(kāi)事件
socket.onopen = function () {
console.log("Socket 已打開(kāi)");
//socket.send("這是來(lái)自客戶端的消息" + location.href + new Date());
};
//獲得消息事件
socket.onmessage = function (msg) {
console.log("onmessage--" + msg.data);
//發(fā)現(xiàn)消息進(jìn)入 開(kāi)始處理前端觸發(fā)邏輯
};
//關(guān)閉事件
socket.onclose = function () {
console.log("Socket已關(guān)閉");
};
//發(fā)生了錯(cuò)誤事件
socket.onerror = function () {
alert("Socket發(fā)生了錯(cuò)誤");
//此時(shí)可以嘗試刷新頁(yè)面
}
//離開(kāi)頁(yè)面時(shí),關(guān)閉socket
//jquery1.8中已經(jīng)被廢棄,3.0中已經(jīng)移除
// $(window).unload(function(){
// socket.close();
//});
}
/* function sendMessage() {
if (typeof (WebSocket) == "undefined") {
console.log("您的瀏覽器不支持WebSocket");
} else {
// console.log("您的瀏覽器支持WebSocket");
var toUserId = document.getElementById('toUserId').value;
var contentText = document.getElementById('contentText').value;
var msg = '{"sid":"' + toUserId + '","message":"' + contentText + '"}';
console.log(msg);
socket.send(msg);
}
}*/
</script>
</html>
測(cè)試
運(yùn)行服務(wù)器,打開(kāi)HTML文件,并開(kāi)啟瀏覽器控制臺(tái),打開(kāi)上篇文章中的Qt客戶端項(xiàng)目向后端服務(wù)器發(fā)送一個(gè)消息,
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-840477.html
可以看到Qt客戶端向后端服務(wù)器發(fā)送一個(gè)消息的同時(shí),瀏覽器控制臺(tái)也接收到一個(gè)消息。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-840477.html
到了這里,關(guān)于Springboot整合WebSocket實(shí)現(xiàn)主動(dòng)向前端推送消息的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!