当前位置: 首页 > news >正文

唤醒手腕 Java 后端 Springboot 框架结合 socketio 学习笔记

socketio 安装配置

Socket.IO是一个完全由JavaScript实现、基于Node.js、支持WebSocket的协议用于实时通信、跨平台的开源框架,它包括了客户端的JavaScript和服务器端的Node.js。

Socket.IO除了支持WebSocket通讯协议外,还支持许多种轮询(Polling)机制以及其它实时通信方式,并封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。Socket.IO实现的Polling通信机制包括Adobe Flash Socket、AJAX长轮询、AJAX multipart streaming、持久Iframe、JSONP轮询等。Socket.IO能够根据浏览器对通讯机制的支持情况自动地选择最佳的方式来实现网络实时应用。

GitHub:https://github.com/mrniko/netty-socketio

安装 netty-socketio 依赖

<dependency><groupId>com.corundumstudio.socketio</groupId><artifactId>netty-socketio</artifactId><version>1.7.23</version>
</dependency>

在这里插入图片描述

这个配置写在服务端,客户端不用写,主要是一些 socket.io 的配置信息。

#socket.io 配置
socketio.host=127.0.0.1(别写 localhost,写服务器的 ip)
socketio.port=9999
# 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器
socketio.maxFramePayloadLength=1048576
# 设置 http 交互最大内容长度
socketio.maxHttpContentLength=1048576
# socket连接数大小(如只监听一个端口 boss 线程组为 1 即可)
socketio.bossCount=1
socketio.workCount=100
socketio.allowCustomRequests=true
# 协议升级超时时间(毫秒),默认 10 秒。HTTP握手升级为 ws 协议超时时间
socketio.upgradeTimeout=1000000
# Ping 消息超时时间(毫秒),默认 60 秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
socketio.pingTimeout=6000000
# Ping 消息间隔(毫秒),默认 25 秒。客户端向服务器发送一条心跳消息间隔
socketio.pingInterval=25000

创建 Socketio 配置类

package com.mslmsxp.mathscat.config;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;@Configuration
public class SocketioConfig {@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;@Beanpublic SocketIOServer socketIOServer() {SocketConfig socketConfig = new SocketConfig();socketConfig.setTcpNoDelay(true);socketConfig.setSoLinger(0);com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();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);return new SocketIOServer(config);}@Beanpublic SpringAnnotationScanner springAnnotationScanner() {return new SpringAnnotationScanner(socketIOServer());}
}

public SpringAnnotationScanner springAnnotationScanner()

用于扫描netty-socketio的注解,比如 @OnConnect、@OnEvent

注意:如果想要SocketIO 的注解生效,必须注入SpringAnnotationScanner 这个类。

说明:@OnDisconnect,@OnConnect,@OnEvent都属于SocketIO的注解,想要注解生效,则必须在配置配配置SpringAnnotationScanner;
@OnConnect: 监听客户端连接
@OnDisconnect: 监听客户端断开连接
@OnEvent (value=“text”): 用于监听客户端发送的消息,value的值就是客户端请求的唯一标识,如:socket.emit(‘text’,‘要发送的消息’);

注意:SocketIOMessageEventHandler 继承了Observable,Observable是JDK自带的观察者模式中的类,继承这个类的类说明是被观察者。

ConcurrentHashMap

ConcurrentHashMapHashMap 一样,是一个存放键值对的容器。使用 hash 算法 来获取值的地址,因此时间复杂度是 O(1)。查询非常快。
同时,ConcurrentHashMap 是线程安全的 HashMap。专门用于多线程环境。

HashMap

HashMap 是线程不安全的,因为 HashMap 中操作都没有加锁,因此在多线程环境下会导致数据覆盖之类的问题,所以,在多线程中使用 HashMap 是会抛出异常的。

HashTable

HashTable 是线程安全的,但是 HashTable 只是单纯的在 put() 方法上加上 synchronized。保证插入时阻塞其他线程的插入操作。虽然安全,但因为设计简单,所以性能低下。

ConcurrentHashMap

ConcurrentHashMap是线程安全的,ConcurrentHashMap 并非锁住整个方法,而是通过原子操作和局部加锁的方法保证了多线程的线程安全,且尽可能减少了性能损耗。

java.lang.IllegalStateException: Failed to execute CommandLineRunner
······
Caused by: java.nio.channels.UnresolvedAddressException: null

socketio 业务层

package com.mslmsxp.mathscat.socketio;import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;@Slf4j
@Component
public class SocketService{@Autowiredprivate SocketIOServer socketIoServer;@OnConnectpublic void onConnect(SocketIOClient client) {String username = client.getHandshakeData().getSingleUrlParam("username");log.info("客户端:" + client.getRemoteAddress() + "  sessionId:" + client.getSessionId() + " username: " + username + "已连接");}@OnDisconnectpublic void onDisconnect(SocketIOClient client) {log.info("客户端:" + client.getSessionId() + "断开连接");Map<String, Object> paramMap = new HashMap<>();paramMap.put("type", "disconnect");paramMap.put("sessionId", client.getSessionId().toString());log.error(paramMap.toString());}
}

socket 会话本地存储

package com.mslmsxp.mathscat.socketio;import com.corundumstudio.socketio.SocketIOClient;
import io.micrometer.common.util.StringUtils;
import org.springframework.stereotype.Component;import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;@Component
public class ClientCache {private static Map<String, HashMap<UUID, SocketIOClient>> concurrentHashMap = new ConcurrentHashMap<>();public void saveClient(String userId, UUID sessionId, SocketIOClient socketIOClient) {if (StringUtils.isNotBlank(userId)) {HashMap<UUID, SocketIOClient> sessionIdClientCache = concurrentHashMap.get(userId);if (sessionIdClientCache == null) {sessionIdClientCache = new HashMap<>();}sessionIdClientCache.put(sessionId, socketIOClient);concurrentHashMap.put(userId, sessionIdClientCache);}}public HashMap<UUID, SocketIOClient> getUserClient(String userId) {return concurrentHashMap.get(userId);}public void deleteSessionClient(String userId, UUID sessionId) {concurrentHashMap.get(userId).remove(sessionId);}
}

启动 socketio server

方法一:@PostConstruct 注解 注入完成 启动

package com.mslmsxp.mathscat.socketio;import com.corundumstudio.socketio.SocketIOServer;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class SocketioServer {@Autowiredprivate SocketIOServer server;@PostConstructvoid init() {server.start();System.out.println("socketio launch");}}

该注解是 Java jdk 提供的注解,而不是 Spring 框架提供的, JavaEE5 引入了 @PostConstruct 和 @PreDestroy 两个作用于 Servlet 生命周期的注解,实现 Bean 初始化之前和销毁之前的自定义操作。

该注解的方法在整个 Bean 初始化中的执行顺序

Constructor(构造方法) > @Autowired(依赖注入) > @PostConstruct(注释的初始化方法)

官方文档:https://docs.oracle.com/javase/8/docs/api/javax/annotation/PostConstruct.html

@PostConstruct 注解的功能:当依赖注入完成后用于执行初始化的方法,并且只会被执行一次。

2023-02-20T18:33:27.120+08:00  INFO 16508 --- [  restartedMain] c.c.socketio.SocketIOServer              : Session store / pubsub factory used: MemoryStoreFactory (local session store only)
2023-02-20T18:33:27.300+08:00  INFO 16508 --- [ntLoopGroup-2-1] c.c.socketio.SocketIOServer              : SocketIO server started at port: 9999
socketio launch
2023-02-20T18:33:27.490+08:00  INFO 16508 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2023-02-20T18:33:27.509+08:00  INFO 16508 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 5000 (http) with context path ''
2023-02-20T18:33:27.515+08:00  INFO 16508 --- [  restartedMain] c.mslmsxp.mathscat.MathscatApplication   : Started MathscatApplication in 1.594 seconds (process running for 2.074)

方法二:使用 CommandLineRunner 启动

package com.mslmsxp.mathscat.socketio;import com.corundumstudio.socketio.SocketIOServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;@Component
public class SocketioServer implements CommandLineRunner {@Autowiredprivate SocketIOServer server;@Overridepublic void run(String... args) throws Exception {this.server.start();System.out.println("Command Line Start Socketio Server");}
}

最后等 http Tomcat 启动完成之后 进行启动。

2023-02-20T18:36:58.703+08:00  INFO 31684 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2023-02-20T18:36:58.725+08:00  INFO 31684 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 5000 (http) with context path ''
2023-02-20T18:36:58.734+08:00  INFO 31684 --- [  restartedMain] c.mslmsxp.mathscat.MathscatApplication   : Started MathscatApplication in 1.907 seconds (process running for 2.505)
2023-02-20T18:36:58.736+08:00  INFO 31684 --- [  restartedMain] c.c.socketio.SocketIOServer              : Session store / pubsub factory used: MemoryStoreFactory (local session store only)
Command Line Start Socketio Server
2023-02-20T18:36:58.947+08:00  INFO 31684 --- [ntLoopGroup-2-1] c.c.socketio.SocketIOServer              : SocketIO server started at port: 9999

客户端运行测试

SocketIO 官方网站:https://socket.io/zh-CN/

使用 <script> 引入

<script src="/socket.io/socket.io.js"></script>

使用 ESM 引入

<script type="module">import { io } from "https://cdn.socket.io/4.3.2/socket.io.esm.min.js";
</script>

前端代码案例

<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Socket Client First</title>
</head><body><div>Socket Client</div><script type="module">import { io } from "https://cdn.socket.io/4.3.2/socket.io.esm.min.js";const socket = io.connect("ws://localhost:3000?username=helloworld")socket.on("connection", (socket) => {console.log(socket.id); // x8WIv7-mJelg7on_ALbx});socket.on("connect", () => {console.log(socket.id); // x8WIv7-mJelg7on_ALbx});socket.on("disconnect", () => {console.log(socket.id); // undefined});</script>
</body></html>

服务器端运行结果:

2023-02-20T19:53:59.651+08:00  INFO 14856 --- [tLoopGroup-3-40] c.m.mathscat.socketio.SocketService      : 客户端:/127.0.0.1:63182  sessionId:64971bce-996e-4d2f-a235-800d3ac1d4a6 username: helloworld已连接
http://www.lryc.cn/news/14607.html

相关文章:

  • C++入门:内联函数、auto关键字、基于范围for循环及指针空值nullptr
  • Python遗传算法
  • GEE学习笔记 六十四:绿色中国报告(个人版)
  • 【Kubernetes】【十八】数据存储 高级存储 配置存储
  • 传输层TCP与UDP协议
  • 字节数组的通俗解释
  • 硬件学习 软件Cadence day06 原理图网表导入PCB (过程和操作的错误),开始的画板
  • OCT 医学图像分类
  • 华为OD机试 - 合并数组 | 机试题算法思路 【2023】
  • 前端开发页面样式通用约定法则
  • 向上跳空缺口选股公式,选出回补后再启动的标的
  • 【IoT】做短视频之前,你需要先做好内容定位
  • 苏宁基于 AI 和图技术的智能监控体系的建设
  • 3、内存管理
  • 【蓦然回首忆Java·基础卷Ⅰ】
  • 类属性和对象属性
  • 【TensorFlow 】查看Tensorflow和python对应版本、将现有的TensorFlow更新到指定的版本
  • VO、DTO、BO、PO、DO区别
  • 速看!!!一套能直接拿捏大厂面试官的软件测试面试宝典
  • 超级完整 的 Maven 讲解 以及私服搭建
  • 数据结构之算法的时间复杂度和空间复杂度
  • 【微信小程序】使用页面跳转并携带多个特定参数
  • CVPR 2021 | Involution:超越convolution和self-attention的神经网络算子
  • 11 OpenCV图像识别之人脸识别
  • ssh设置:免密登入、修改默认端口、禁止root登入、限制错误登入次数
  • 【Fastdfs】| 入门连续剧——安装
  • 【ESP32-S3】Pycharm 使用 microPython 教程(避坑)
  • Allegro如何通过报表的方式检查单板上是否有假器件操作指导
  • 清理bib文件(删除重复项,仅保留tex中引用的条目)
  • Rust编程细节知识点拾遗