Springboot整合Socket實(shí)現(xiàn)單點(diǎn)發(fā)送,廣播群發(fā),1對(duì)1,1對(duì)多實(shí)戰(zhàn)
本篇內(nèi)容:
后端 + 前端簡(jiǎn)單HTML頁(yè)面
功能場(chǎng)景點(diǎn):
1. 群發(fā),所有人都能收到
2. 局部群發(fā),部分人群都能收到
3. 單點(diǎn)推送, 指定某個(gè)人的頁(yè)面
慣例,先看看本次實(shí)戰(zhàn)示例項(xiàng)目結(jié)構(gòu):

可以看到內(nèi)容不多,也就是說(shuō),springboot 整合socket, 跟著我學(xué),輕輕松松。
古有曹植七步成詩(shī),如今,咱們也是 7步學(xué)會(huì)整合socket!
不多說(shuō),開(kāi)始:
① pom引入核心依賴
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>② yml加上配置項(xiàng)
server: port: 8089 socketio: host: localhost port: 8503 maxFramePayloadLength: 1048576 maxHttpContentLength: 1048576 bossCount: 1 workCount: 100 allowCustomRequests: true upgradeTimeout: 10000 pingTimeout: 60000 pingInterval: 25000
③ 創(chuàng)建socket配置加載類 MySocketConfig.java
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/06/13 21:50
*/
@Configuration
public class MySocketConfig{
@Value("${socketio.host}")
private String host;
@Value("${socketio.port}")
private Integer port;
@Value("${socketio.bossCount}")
private int bossCount;
@Value("${socketio.workCount}")
private int workCount;
@Value("${socketio.allowCustomRequests}")
private boolean allowCustomRequests;
@Value("${socketio.upgradeTimeout}")
private int upgradeTimeout;
@Value("${socketio.pingTimeout}")
private int pingTimeout;
@Value("${socketio.pingInterval}")
private int pingInterval;
@Bean
public SocketIOServer socketIOServer() {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
buildSocketConfig(socketConfig, config);
return new SocketIOServer(config);
}
/**
* 掃描netty-socketIo的注解( @OnConnect、@OnEvent等)
*/
@Bean
public SpringAnnotationScanner springAnnotationScanner() {
return new SpringAnnotationScanner(socketIOServer());
}
private void buildSocketConfig(SocketConfig socketConfig, com.corundumstudio.socketio.Configuration config) {
config.setSocketConfig(socketConfig);
config.setHostname(host);
config.setPort(port);
config.setBossThreads(bossCount);
config.setWorkerThreads(workCount);
config.setAllowCustomRequests(allowCustomRequests);
config.setUpgradeTimeout(upgradeTimeout);
config.setPingTimeout(pingTimeout);
config.setPingInterval(pingInterval);
}
}④創(chuàng)建消息實(shí)體 MyMessage.java
/**
* @Author: JCccc
* @Date: 2022-07-23 9:05
* @Description:
*/
public class MyMessage {
private String type;
private String content;
private String from;
private String to;
private String channel;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getChannel() {
return channel;
}
public void setChannel(String channel) {
this.channel = channel;
}
}代碼簡(jiǎn)析:

⑤創(chuàng)建 socket handler 負(fù)責(zé)記錄客戶端 連接、下線
MySocketHandler.java
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.socket.mysocket.util.SocketUtil;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/6/23 21:21
*/
@Component
public class MySocketHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SocketIOServer socketIoServer;
@PostConstruct
private void start(){
try {
socketIoServer.start();
}catch (Exception e){
e.printStackTrace();
}
}
@PreDestroy
private void destroy(){
try {
socketIoServer.stop();
}catch (Exception e){
e.printStackTrace();
}
}
@OnConnect
public void connect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
SocketUtil.connectMap.put(userFlag, client);
log.info("客戶端userFlag: "+ userFlag+ "已連接");
}
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
log.info("客戶端userFlag:" + userFlag + "斷開(kāi)連接");
SocketUtil.connectMap.remove(userFlag, client);
}
}代碼簡(jiǎn)析:

⑥ 封裝的socket 小函數(shù)
SocketUtil.java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/6/23 21:28
*/
@Component
public class SocketUtil {
private final Logger log = LoggerFactory.getLogger(this.getClass());
//暫且把用戶&客戶端信息存在緩存
public static ConcurrentMap<String, SocketIOClient> connectMap = new ConcurrentHashMap<>();
@OnEvent(value = "CHANNEL_SYSTEM")
public void systemDataListener(String receiveMsg) {
if (!StringUtils.hasLength(receiveMsg)){
return;
}
JSONObject msgObject = (JSONObject) JSON.parse(receiveMsg);
String userFlag = String.valueOf(msgObject.get("from"));
String content = String.valueOf(msgObject.get("content"));
log.info("收到用戶 : {} 推送到系統(tǒng)頻道的一條消息 :{}",userFlag,content );
}
public void sendToAll(Map<String, Object> msg,String sendChannel) {
if (connectMap.isEmpty()){
return;
}
//給在這個(gè)頻道的每個(gè)客戶端發(fā)消息
for (Map.Entry<String, SocketIOClient> entry : connectMap.entrySet()) {
entry.getValue().sendEvent(sendChannel, msg);
}
}
public void sendToOne(String userFlag, Map<String, Object> msg,String sendChannel) {
//拿出某個(gè)客戶端信息
SocketIOClient socketClient = getSocketClient(userFlag);
if (Objects.nonNull(socketClient) ){
//單獨(dú)給他發(fā)消息
socketClient.sendEvent(sendChannel,msg);
}
}
/**
* 識(shí)別出客戶端
* @param userFlag
* @return
*/
public SocketIOClient getSocketClient(String userFlag){
SocketIOClient client = null;
if (StringUtils.hasLength(userFlag) && !connectMap.isEmpty()){
for (String key : connectMap.keySet()) {
if (userFlag.equals(key)){
client = connectMap.get(key);
}
}
}
return client;
}
}代碼簡(jiǎn)析:

⑦寫(xiě)1個(gè)接口,模擬場(chǎng)景,前端頁(yè)面調(diào)用后端接口,做消息推送
TestController.java
import com.socket.mysocket.dto.MyMessage;
import com.socket.mysocket.util.SocketUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: JCccc
* @Description:
* @Date: 2022/06/13 21:50
*/
@RestController
public class TestController {
public final static String SEND_TYPE_ALL = "ALL";
public final static String SEND_TYPE_ALONE = "ALONE";
@Autowired
SocketUtil socketUtil;
@PostMapping("/testSendMsg")
public String testSendMsg(@RequestBody MyMessage myMessage){
Map<String, Object> map = new HashMap<>();
map.put("msg",myMessage.getContent());
//群發(fā)
if (SEND_TYPE_ALL.equals(myMessage.getType())){
socketUtil.sendToAll( map,myMessage.getChannel());
return "success";
}
//指定單人
if (SEND_TYPE_ALONE.equals(myMessage.getType())){
socketUtil.sendToOne(myMessage.getTo(), map, myMessage.getChannel());
return "success";
}
return "fail";
}
}代碼簡(jiǎn)析:

好了,7步了。一切已經(jīng)就緒了。
前端簡(jiǎn)單頁(yè)面
接下來(lái)搞點(diǎn)前端HTML頁(yè)面, 玩一玩看看效果:

第一個(gè)頁(yè)面:
TestClientStudentJC.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>我要連SOCKET</title>
<base>
<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<style>
body {
padding: 20px;
}
#console {
height: 450px;
overflow: auto;
}
.msg-color {
color: green;
}
</style>
</head>
<body>
<div id="console" class="well"></div>
<div id="conversationDiv">
<labal>給系統(tǒng)推消息</labal>
<input type="text" id="content"/>
<button id="btnSendToSystem" onclick="sendSys();">發(fā)送</button>
</div>
</body>
<script type="text/javascript">
var socket;
connect();
function connect() {
var userFlag = 'user_JC';
var opts = {
query: 'userFlag=' + userFlag
};
socket = io.connect('http://localhost:8503', opts);
socket.on('connect', function () {
console.log("連接成功");
output('當(dāng)前用戶是:' + userFlag );
output('<span class="msg-color">連接成功了。</span>');
});
socket.on('disconnect', function () {
output('<span class="msg-color">下線了。 </span>');
});
socket.on('CHANNEL_STUDENT', function (data) {
let msg= JSON.stringify(data)
output('收到學(xué)生頻道消息了:' + msg );
console.log(data);
});
socket.on('CHANNEL_SYSTEM', function (data) {
let msg= JSON.stringify(data)
output('收到系統(tǒng)全局消息了:' + msg );
console.log(data);
});
}
function sendSys() {
console.log('發(fā)送消息給服務(wù)端');
var content = document.getElementById('content').value;
socket.emit('CHANNEL_SYSTEM',JSON.stringify({
'content': content,
'from': 'user_JC'
}));
}
function output(message) {
var element = $("<div>" + message + "</div>");
$('#console').prepend(element);
}
</script>
</html>代碼簡(jiǎn)析:

第二個(gè)頁(yè)面,跟第一個(gè)基本一樣,改一下用戶唯一標(biāo)識(shí):
TestClientStudentPU.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>我要連SOCKET</title>
<base>
<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.min.js"></script>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<style>
body {
padding: 20px;
}
#console {
height: 450px;
overflow: auto;
}
.msg-color {
color: green;
}
</style>
</head>
<body>
<div id="console" class="well"></div>
<div id="conversationDiv">
<labal>給系統(tǒng)推消息</labal>
<input type="text" id="content"/>
<button id="btnSendToSystem" onclick="sendSys();">發(fā)送</button>
</div>
</body>
<script type="text/javascript">
var socket;
connect();
function connect() {
var userFlag = 'user_PU';
var opts = {
query: 'userFlag=' + userFlag
};
socket = io.connect('http://localhost:8503', opts);
socket.on('connect', function () {
console.log("連接成功");
output('當(dāng)前用戶是:' + userFlag );
output('<span class="msg-color">連接成功了。</span>');
});
socket.on('disconnect', function () {
output('<span class="msg-color">下線了。 </span>');
});
socket.on('CHANNEL_STUDENT', function (data) {
let msg= JSON.stringify(data)
output('收到學(xué)生頻道消息了:' + msg );
console.log(data);
});
socket.on('CHANNEL_SYSTEM', function (data) {
let msg= JSON.stringify(data)
output('收到系統(tǒng)全局消息了:' + msg );
console.log(data);
});
}
function sendSys() {
console.log('發(fā)送消息給服務(wù)端');
var content = document.getElementById('content').value;
socket.emit('CHANNEL_SYSTEM',JSON.stringify({
'content': content,
'from': 'user_PU'
}));
}
function output(message) {
var element = $("<div>" + message + "</div>");
$('#console').prepend(element);
}
</script>
</html>OK,把項(xiàng)目跑起來(lái),開(kāi)始玩。
直接訪問(wèn)客戶端頁(yè)面 模擬學(xué)生 JC連接socket:
http://127.0.0.1:8089/TestClientStudentJC.html

可以看到服務(wù)端有監(jiān)測(cè)到:

這里監(jiān)測(cè)的:

先試試客戶端給系統(tǒng)推消息先:

可以看到服務(wù)端成功收到消息:

這種方式,其實(shí)是因?yàn)榉?wù)監(jiān)聽(tīng)了相關(guān)的頻道:

前端使用JS推到這個(gè)系統(tǒng)頻道:

ps: 其實(shí)前端給服務(wù)端推消息,其實(shí)調(diào)用接口就可以。
OK,進(jìn)入核心應(yīng)用場(chǎng)景1:
群發(fā),所有人都能收到
系統(tǒng)給連上的客戶端都推送消息

{
"type":?"ALL",
"content":"你們好,這是一條廣播消息,全部人都能收到",
"channel":"CHANNEL_SYSTEM"
}看看效果

場(chǎng)景2,局部群發(fā),部分人群都能收到
其實(shí)也就是通過(guò)HTML 客戶端監(jiān)聽(tīng)主題做區(qū)分就好:
直接拉人口,升3 :
模擬2個(gè)學(xué)生,1個(gè)老師都連接上了socket
當(dāng)然,老師監(jiān)聽(tīng)的是 老師頻道:

然后我們模擬推送一下消息到指定的老師頻道:

{
"type":?"ALL",
"content":"給老師們推一條消息?。。?,
"channel":"CHANNEL_TEACHER"
}
最后一個(gè)場(chǎng)景,也就是單點(diǎn)推送,指定某個(gè)人收到
模擬 學(xué)生 PU 給 學(xué)生JC 推消息:

可以看到在學(xué)生頻道的JC正常收到了PU的消息:

到此這篇關(guān)于Springboot整合Socket實(shí)現(xiàn)單點(diǎn)發(fā)送,廣播群發(fā),1對(duì)1,1對(duì)多實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Springboot Socket實(shí)戰(zhàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot整合WebSocket的客戶端和服務(wù)端的實(shí)現(xiàn)代碼
- SpringBoot整合Netty實(shí)現(xiàn)WebSocket的示例代碼
- SpringBoot整合websocket實(shí)現(xiàn)即時(shí)通信聊天
- 使用springboot整合websocket實(shí)現(xiàn)群聊教程
- springboot整合websocket實(shí)現(xiàn)群聊思路代碼詳解
- springboot整合websocket最基礎(chǔ)入門使用教程詳解
- Springboot之整合Socket連接案例
- SpringBoot2.0整合WebSocket代碼實(shí)例
- 通過(guò)實(shí)例講解springboot整合WebSocket
相關(guān)文章
Springboot獲取前端反饋信息并存入數(shù)據(jù)庫(kù)的實(shí)現(xiàn)代碼
這篇文章主要介紹了Springboot獲取前端反饋信息并存入數(shù)據(jù)庫(kù)的實(shí)現(xiàn)代碼,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
springboot @Value實(shí)現(xiàn)獲取計(jì)算機(jī)中絕對(duì)路徑文件的內(nèi)容
這篇文章主要介紹了springboot @Value實(shí)現(xiàn)獲取計(jì)算機(jī)中絕對(duì)路徑文件的內(nèi)容,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2021-09-09
Prometheus監(jiān)控Springboot程序的實(shí)現(xiàn)方法
這篇文章主要介紹了Prometheus監(jiān)控Springboot程序的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
Maven分步詳解多環(huán)境配置與應(yīng)用流程
這篇文章主要介紹了Maven進(jìn)階多環(huán)境配置與應(yīng)用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
idea2019.2安裝MybatisCodeHelper插件的超詳細(xì)教程
這篇文章主要介紹了idea2019.2安裝MybatisCodeHelper插件的教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
javaweb圖書(shū)商城設(shè)計(jì)之購(gòu)物車模塊(3)
這篇文章主要為大家詳細(xì)介紹了javaweb圖書(shū)商城設(shè)計(jì)之購(gòu)物車模塊的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11

