后端 + 前端简单HTML页面
1. 群发,所有人都能收到
2. 局部群发,部分人群都能收到
3. 单点推送, 指定某个人的页面
惯例,先看看本次实战示例项目结构:
可以看到内容不多,也就是说,springboot 整合socket, 跟着我学,轻轻松松。
不多说,开始:
-
-
-
com.alibaba -
fastjson -
1.2.75 -
-
-
com.corundumstudio.socketio -
netty-socketio -
1.7.7 -
-
-
org.springframework.boot -
spring-boot-starter-web -
-
-
org.springframework.boot -
spring-boot-starter-test -
test -
-
- server:
- port: 8089
-
- socketio:
- host: localhost
- port: 8503
- maxFramePayloadLength: 1048576
- maxHttpContentLength: 1048576
- bossCount: 1
- workCount: 100
- allowCustomRequests: true
- upgradeTimeout: 10000
- pingTimeout: 60000
- pingInterval: 25000
- 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);
- }
- }
- /**
- * @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;
- }
- }
代码简析:
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 + "断开连接");
- SocketUtil.connectMap.remove(userFlag, client);
- }
- }
代码简析:
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
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("收到用户 : {} 推送到系统频道的一条消息 :{}",userFlag,content );
- }
-
- public void sendToAll(Map
msg,String sendChannel) { - if (connectMap.isEmpty()){
- return;
- }
- //给在这个频道的每个客户端发消息
- for (Map.Entry
entry : connectMap.entrySet()) { - entry.getValue().sendEvent(sendChannel, msg);
- }
- }
-
- public void sendToOne(String userFlag, Map
msg,String sendChannel) { - //拿出某个客户端信息
- SocketIOClient socketClient = getSocketClient(userFlag);
- if (Objects.nonNull(socketClient) ){
- //单独给他发消息
- socketClient.sendEvent(sendChannel,msg);
- }
- }
-
-
- /**
- * 识别出客户端
- * @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;
- }
-
-
-
- }
代码简析:
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
map = new HashMap<>(); - map.put("msg",myMessage.getContent());
-
- //群发
- 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";
- }
- }
代码简析:
好了,7步了。一切已经就绪了。
接下来搞点前端HTML页面, 玩一玩看看效果:
第一个页面:
TestClientStudentJC.html
- html>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
- <title>我要连SOCKETtitle>
- <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>给系统推消息labal>
- <input type="text" id="content"/>
- <button id="btnSendToSystem" onclick="sendSys();">发送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('当前用户是:' + userFlag );
- output('连接成功了。');
- });
- socket.on('disconnect', function () {
- output('下线了。 ');
- });
-
- socket.on('CHANNEL_STUDENT', function (data) {
- let msg= JSON.stringify(data)
- output('收到学生频道消息了:' + msg );
- console.log(data);
-
- });
- socket.on('CHANNEL_SYSTEM', function (data) {
- let msg= JSON.stringify(data)
- output('收到系统全局消息了:' + msg );
- console.log(data);
-
- });
-
- }
-
- function sendSys() {
- console.log('发送消息给服务端');
- var content = document.getElementById('content').value;
-
- socket.emit('CHANNEL_SYSTEM',JSON.stringify({
- 'content': content,
- 'from': 'user_JC'
- }));
-
- }
- function output(message) {
- var element = $("" + message + "");
- $('#console').prepend(element);
- }
-
- script>
- html>
代码简析:
第二个页面,跟第一个基本一样,改一下用户唯一标识:
TestClientStudentPU.html
- html>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
- <title>我要连SOCKETtitle>
- <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>给系统推消息labal>
- <input type="text" id="content"/>
- <button id="btnSendToSystem" onclick="sendSys();">发送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('当前用户是:' + userFlag );
- output('连接成功了。');
- });
- socket.on('disconnect', function () {
- output('下线了。 ');
- });
-
- socket.on('CHANNEL_STUDENT', function (data) {
- let msg= JSON.stringify(data)
- output('收到学生频道消息了:' + msg );
- console.log(data);
-
- });
- socket.on('CHANNEL_SYSTEM', function (data) {
- let msg= JSON.stringify(data)
- output('收到系统全局消息了:' + msg );
- console.log(data);
-
- });
-
- }
-
- function sendSys() {
- console.log('发送消息给服务端');
- var content = document.getElementById('content').value;
-
- socket.emit('CHANNEL_SYSTEM',JSON.stringify({
- 'content': content,
- 'from': 'user_PU'
- }));
-
- }
- function output(message) {
- var element = $("" + message + "");
- $('#console').prepend(element);
- }
-
- script>
- html>
OK,把项目跑起来,开始玩。
直接访问客户端页面 模拟学生 JC连接socket:
http://127.0.0.1:8089/TestClientStudentJC.html
可以看到服务端有监测到:
这里监测的:
先试试客户端给系统推消息先:
可以看到服务端成功收到消息:
这种方式,其实是因为服务监听了相关的频道:
前端使用JS推到这个系统频道:
ps: 其实前端给服务端推消息,其实调用接口就可以。
OK,进入核心应用场景1:
{
"type": "ALL",
"content":"你们好,这是一条广播消息,全部人都能收到",
"channel":"CHANNEL_SYSTEM"
}
看看效果:
其实也就是通过HTML 客户端监听主题做区分就好:
直接拉人口,升3 :
模拟2个学生,1个老师都连接上了socket
当然,老师监听的是 老师频道:
然后我们模拟推送一下消息到指定的老师频道:
{
"type": "ALL",
"content":"给老师们推一条消息!!!",
"channel":"CHANNEL_TEACHER"
}
模拟 学生 PU 给 学生JC 推消息:
可以看到在学生频道的JC正常收到了PU的消息:
好了,该篇就到这吧。