java后端+前端使用WebSocket實(shí)現(xiàn)消息推送的詳細(xì)流程
前言
在項(xiàng)目的開(kāi)發(fā)時(shí),遇到實(shí)現(xiàn)服務(wù)器主動(dòng)發(fā)送數(shù)據(jù)到前端頁(yè)面的功能的需求。實(shí)現(xiàn)該功能不外乎使用輪詢和websocket技術(shù),但在考慮到實(shí)時(shí)性和資源損耗后,最后決定使用websocket?,F(xiàn)在就記錄一下用Java實(shí)現(xiàn)Websocket技術(shù)吧~
Java實(shí)現(xiàn)Websocket通常有兩種方式:1、創(chuàng)建WebSocketServer類,里面包含open、close、message、error等方法;2、利用Springboot提供的webSocketHandler類,創(chuàng)建其子類并重寫(xiě)方法。我們項(xiàng)目雖然使用Springboot框架,不過(guò)仍采用了第一種方法實(shí)現(xiàn)。
創(chuàng)建WebSocket的簡(jiǎn)單實(shí)例操作流程
1.引入Websocket依賴
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.7.0</version>
</dependency>
2.創(chuàng)建配置類WebSocketConfig
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 開(kāi)啟WebSocket支持
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3.創(chuàng)建WebSocketServer
在websocket協(xié)議下,后端服務(wù)器相當(dāng)于ws里面的客戶端,需要用@ServerEndpoint指定訪問(wèn)路徑,并使用@Component注入容器
@ServerEndpoint:當(dāng)ServerEndpointExporter類通過(guò)Spring配置進(jìn)行聲明并被使用,它將會(huì)去掃描帶有@ServerEndpoint注解的類。被注解的類將被注冊(cè)成為一個(gè)WebSocket端點(diǎn)。所有的配置項(xiàng)都在這個(gè)注解的屬性中
( 如:@ServerEndpoint(“/ws”) )
下面的栗子中@ServerEndpoint指定訪問(wèn)路徑中包含sid,這個(gè)是用于區(qū)分每個(gè)頁(yè)面
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* @ServerEndpoint 注解是一個(gè)類層次的注解,它的功能主要是將目前的類定義成一個(gè)websocket服務(wù)器端,
* 注解的值將被用于監(jiān)聽(tīng)用戶連接的終端訪問(wèn)URL地址,客戶端可以通過(guò)這個(gè)URL來(lái)連接到WebSocket服務(wù)器端
*/
@ServerEndpoint("/notice/{userId}")
@Component
@Slf4j
public class NoticeWebsocket {
//記錄連接的客戶端
public static Map<String, Session> clients = new ConcurrentHashMap<>();
/**
* userId關(guān)聯(lián)sid(解決同一用戶id,在多個(gè)web端連接的問(wèn)題)
*/
public static Map<String, Set<String>> conns = new ConcurrentHashMap<>();
private String sid = null;
private String userId;
/**
* 連接成功后調(diào)用的方法
* @param session
* @param userId
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.sid = UUID.randomUUID().toString();
this.userId = userId;
clients.put(this.sid, session);
Set<String> clientSet = conns.get(userId);
if (clientSet==null){
clientSet = new HashSet<>();
conns.put(userId,clientSet);
}
clientSet.add(this.sid);
log.info(this.sid + "連接開(kāi)啟!");
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose() {
log.info(this.sid + "連接斷開(kāi)!");
clients.remove(this.sid);
}
/**
* 判斷是否連接的方法
* @return
*/
public static boolean isServerClose() {
if (NoticeWebsocket.clients.values().size() == 0) {
log.info("已斷開(kāi)");
return true;
}else {
log.info("已連接");
return false;
}
}
/**
* 發(fā)送給所有用戶
* @param noticeType
*/
public static void sendMessage(String noticeType){
NoticeWebsocketResp noticeWebsocketResp = new NoticeWebsocketResp();
noticeWebsocketResp.setNoticeType(noticeType);
sendMessage(noticeWebsocketResp);
}
/**
* 發(fā)送給所有用戶
* @param noticeWebsocketResp
*/
public static void sendMessage(NoticeWebsocketResp noticeWebsocketResp){
String message = JSONObject.toJSONString(noticeWebsocketResp);
for (Session session1 : NoticeWebsocket.clients.values()) {
try {
session1.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 根據(jù)用戶id發(fā)送給某一個(gè)用戶
* **/
public static void sendMessageByUserId(String userId, NoticeWebsocketResp noticeWebsocketResp) {
if (!StringUtils.isEmpty(userId)) {
String message = JSONObject.toJSONString(noticeWebsocketResp);
Set<String> clientSet = conns.get(userId);
if (clientSet != null) {
Iterator<String> iterator = clientSet.iterator();
while (iterator.hasNext()) {
String sid = iterator.next();
Session session = clients.get(sid);
if (session != null) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
/**
* 收到客戶端消息后調(diào)用的方法
* @param message
* @param session
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到來(lái)自窗口"+this.userId+"的信息:"+message);
}
/**
* 發(fā)生錯(cuò)誤時(shí)的回調(diào)函數(shù)
* @param error
*/
@OnError
public void onError(Throwable error) {
log.info("錯(cuò)誤");
error.printStackTrace();
}
}
封裝了一個(gè)發(fā)送消息的對(duì)象可以直接使用
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("ws通知返回對(duì)象")
public class NoticeWebsocketResp<T> {
@ApiModelProperty(value = "通知類型")
private String noticeType;
@ApiModelProperty(value = "通知內(nèi)容")
private T noticeInfo;
}
4.websocket調(diào)用
一個(gè)用戶調(diào)用接口,主動(dòng)將信息發(fā)給后端,后端接收后再主動(dòng)推送給指定/全部用戶
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/test")
public R test() {
NoticeWebsocket.sendMessage("你好,WebSocket");
return R.ok();
}
}
前端WebSocket連接
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SseEmitter</title>
</head>
<body>
<div id="message"></div>
</body>
<script>
var limitConnect = 0;
init();
function init() {
var ws = new WebSocket('ws://192.168.2.88:9060/notice/1');
// 獲取連接狀態(tài)
console.log('ws連接狀態(tài):' + ws.readyState);
//監(jiān)聽(tīng)是否連接成功
ws.onopen = function () {
console.log('ws連接狀態(tài):' + ws.readyState);
limitConnect = 0;
//連接成功則發(fā)送一個(gè)數(shù)據(jù)
ws.send('我們建立連接啦');
}
// 接聽(tīng)服務(wù)器發(fā)回的信息并處理展示
ws.onmessage = function (data) {
console.log('接收到來(lái)自服務(wù)器的消息:');
console.log(data);
//完成通信后關(guān)閉WebSocket連接
// ws.close();
}
// 監(jiān)聽(tīng)連接關(guān)閉事件
ws.onclose = function () {
// 監(jiān)聽(tīng)整個(gè)過(guò)程中websocket的狀態(tài)
console.log('ws連接狀態(tài):' + ws.readyState);
reconnect();
}
// 監(jiān)聽(tīng)并處理error事件
ws.onerror = function (error) {
console.log(error);
}
}
function reconnect() {
limitConnect ++;
console.log("重連第" + limitConnect + "次");
setTimeout(function(){
init();
},2000);
}
</script>
</html>
項(xiàng)目啟動(dòng),打開(kāi)頁(yè)面后控制臺(tái)打印連接信息

調(diào)用order/test方法后前端打印推送消息內(nèi)容

這樣,就可以接口或者ws調(diào)用網(wǎng)址的方式進(jìn)行websocket的通信啦~
如果沒(méi)有前端頁(yè)面也可以使用在線WebSocket測(cè)試

總結(jié)
到此這篇關(guān)于java后端+前端使用WebSocket實(shí)現(xiàn)消息推送的文章就介紹到這了,更多相關(guān)javaWebSocket實(shí)現(xiàn)消息推送內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java中使用WebSocket的幾種方式
- Java實(shí)現(xiàn)使用Websocket發(fā)送消息詳細(xì)代碼舉例
- 如何在Java中使用WebSocket協(xié)議
- springboot整合websocket后啟動(dòng)報(bào)錯(cuò)(javax.websocket.server.ServerContainer not available)
- Java WebSocket客戶端接收大量數(shù)據(jù)的三種方案
- 使用Java WebSocket獲取客戶端IP地址的示例代碼
- Java中實(shí)現(xiàn)WebSocket方法詳解
- 教你如何使用Java實(shí)現(xiàn)WebSocket
- 一步步教你如何使用Java實(shí)現(xiàn)WebSocket
- JAVA 日常開(kāi)發(fā)中Websocket示例詳解
相關(guān)文章
使用SpringAop動(dòng)態(tài)獲取mapper執(zhí)行的SQL,并保存SQL到Log表中
這篇文章主要介紹了使用SpringAop動(dòng)態(tài)獲取mapper執(zhí)行的SQL,并保存SQL到Log表中問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Java基礎(chǔ)教程之理解Annotation詳細(xì)介紹
這篇文章主要介紹了Java基礎(chǔ)教程之理解Annotation詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2017-01-01
java jvm兩種存儲(chǔ)區(qū)的類型知識(shí)點(diǎn)講解
在本篇文章里小編給大家整理的是一篇關(guān)于java jvm兩種存儲(chǔ)區(qū)的類型知識(shí)點(diǎn)講解內(nèi)容,有興趣的朋友們可以學(xué)習(xí)下。2021-03-03
mybatis動(dòng)態(tài)sql之新增與更新方式
這篇文章主要介紹了mybatis動(dòng)態(tài)sql之新增與更新方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
springboot @Valid注解對(duì)嵌套類型的校驗(yàn)功能
這篇文章主要介紹了springboot~@Valid注解對(duì)嵌套類型的校驗(yàn),主要介紹 @Valid在項(xiàng)目中的使用,需要的朋友可以參考下2018-05-05
springboot?+rabbitmq+redis實(shí)現(xiàn)秒殺示例
本文主要介紹了springboot?+rabbitmq+redis實(shí)現(xiàn)秒殺示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07

