随着实时通信需求的增加,开发人员寻求有效的解决方案来实现即时通讯功能。在这篇博客中,我们将介绍如何在Spring Boot和Vue.js应用程序中使用Socket.IO库实现私聊和群聊功能。Socket.IO是一个基于Node.js的实时应用程序框架,它为客户端和服务器之间提供了双向的实时通信。
Socket.IO和其在实时通信中的作用
Socket.IO是一个基于Node.js的实时应用程序框架,旨在实现实时、双向的通信。它通过使用WebSocket协议提供了一种简单而强大的方式,使服务器和客户端能够进行实时通信。
什么是Socket.IO?
Socket.IO是一个开源的JavaScript库,可在客户端和服务器之间建立持久的、双向的通信通道。它允许实时数据传输,不需要客户端发起请求来获取更新的数据。Socket.IO支持多种传输协议,包括WebSocket、轮询和长轮询等,以确保在各种环境下都能实现实时通信。
Socket.IO的优势和特点
-
实时性:Socket.IO基于WebSocket协议,通过建立持久的双向连接,能够实现实时的双向通信,使得服务器能够主动推送数据给客户端。
-
跨平台和跨浏览器:Socket.IO可用于Web、移动设备和桌面应用程序,支持多种平台和浏览器,具有广泛的兼容性。
-
自适应传输:Socket.IO可以根据环境自动选择最佳的传输协议,包括WebSocket、轮询和长轮询等,以确保在各种网络条件下都能进行实时通信。
-
容错性和可靠性:Socket.IO具备自动重新连接、心跳检测和故障恢复等机制,能够处理网络中断或其他故障情况,并保持通信的可靠性。
-
简单易用:Socket.IO提供了简单而灵活的API,使得开发人员能够轻松地构建实时应用程序,并处理客户端和服务器之间的实时数据传输。
Spring Boot服务端
引入依赖
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
在yml中配置属性
socketio:
host: localhost
port: 3000
# 设置最大每帧处理数据的长度,防止他人利用大数据来攻击服务器
maxFramePayloadLength: 1048576
# 设置http交互最大内容长度
maxHttpContentLength: 1048576
# socket连接数大小(如只监听一个端口boss线程组为1即可)
bossCount: 1
workCount: 100
allowCustomRequests: true
# 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
upgradeTimeout: 1000000
# Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
pingTimeout: 6000000
# Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
pingInterval: 25000
创建Socket.IO配置类
@Configuration
public class SocketIOConfig implements InitializingBean {
@Resource
private SocketIOHandler socketIOHandler;
@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;
@Override
public void afterPropertiesSet() throws Exception {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setReuseAddress(true);
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration configuration = new com.corundumstudio.socketio.Configuration();
configuration.setSocketConfig(socketConfig);
// host在本地测试可以设置为localhost或者本机IP,在Linux服务器跑可换成服务器IP
configuration.setHostname(host);
configuration.setPort(port);
// socket连接数大小(如只监听一个端口boss线程组为1即可)
configuration.setBossThreads(bossCount);
configuration.setWorkerThreads(workCount);
configuration.setAllowCustomRequests(allowCustomRequests);
// 协议升级超时时间(毫秒),默认10秒。HTTP握手升级为ws协议超时时间
configuration.setUpgradeTimeout(upgradeTimeout);
// Ping消息超时时间(毫秒),默认60秒,这个时间间隔内没有接收到心跳消息就会发送超时事件
configuration.setPingTimeout(pingTimeout);
// Ping消息间隔(毫秒),默认25秒。客户端向服务器发送一条心跳消息间隔
configuration.setPingInterval(pingInterval);
SocketIOServer socketIOServer = new SocketIOServer(configuration);
//添加事件监听器
socketIOServer.addListeners(socketIOHandler);
//启动SocketIOServer
socketIOServer.start();
System.out.println("SocketIO启动完毕");
}
}
创建用户缓存
@Component
public class ClientCache {
private static Map<String, Map<UUID, SocketIOClient>> userClient = new ConcurrentHashMap<>();
public boolean saveClient(String username, SocketIOClient client, UUID sessionId){
Map<UUID, SocketIOClient> hashMap = userClient.get(username);
if(hashMap == null){
hashMap = new HashMap<>();
hashMap.put(sessionId,client);
userClient.put(username,hashMap);
return true;
}else{
return false;
}
}
public void deleteClient(UUID sessionId){
for (Map.Entry<String, Map<UUID, SocketIOClient>> mapEntry : userClient.entrySet()) {
if(mapEntry.getValue().get(sessionId) != null){
userClient.remove(mapEntry.getKey());
}
}
}
public SocketIOClient getClient(String username){
Map<UUID, SocketIOClient> map = userClient.get(username);
if (map != null) {
for (Map.Entry<UUID, SocketIOClient> entry : map.entrySet()) {
return entry.getValue();
}
}
return null;
}
public Map<UUID,SocketIOClient> getUser(String username){
return userClient.get(username);
}
public Map<String, Map<UUID, SocketIOClient>> getUsers(){
return userClient;
}
public Map<String,Map<UUID,SocketIOClient>> getUsers(List<String> usersname){
Map<String,Map<UUID,SocketIOClient>> map = new ConcurrentHashMap<>();
for (String username : usersname) {
if(userClient.containsKey(username)){
map.put(username,userClient.get(username));
}
}
return map;
}
}
创建消息类
@Getter
@Setter
@Component
@NoArgsConstructor
@AllArgsConstructor
public class MessageInfo {
private String sentUsername;
private String message;
private String receiveUsername;
}
创建服务端事务处理
private ClientCache clientCache;
public SocketIOHandler(ClientCache clientCache) {
this.clientCache = clientCache;
}
@OnConnect
public void connect(SocketIOClient client){
System.out.println("连接");
client.getHandshakeData().getHeaders();
}
@OnDisconnect
public void disconnect(SocketIOClient client){
UUID sessionId = client.getSessionId();
clientCache.deleteClient(sessionId);
System.out.println("已断开连接");
}
//监听客户端传递的用户信息
@OnEvent("userInfo")
public void userInfo(SocketIOClient client, AckRequest ackRequest, String username){
UUID sessionId = client.getSessionId();
if(!clientCache.saveClient(username,client,sessionId)){
client.sendEvent("userInfo", "用户已存在));
return;
}
client.sendEvent("userInfo","连接成功");
}
//监听私聊消息,@OnEvent的变量是与客户端发送的变量要相同
@OnEvent("privateMessage")
public void privateUser(SocketIOClient client, AckRequest ackRequest, MessageInfo info){
SocketIOClient ioClient = clientCache.getClient(info.getReceiveUsername());
client.sendEvent("privateSent","发送成功");
ioClient.sendEvent("privateReceive",info.getMessage());
}
//监听群聊消息
@OnEvent("groupMessage")
public void groupUser(SocketIOClient client, AckRequest ackRequest, MessageInfo info){
Chat chat = chatService.getById(info.getChatId());
List<String> usersname = info.getUsersname();
//获取群内所有在线用户
Map<UUID, SocketIOClient> clientMap = clientCache.getUsers(usersname).values().stream()
.flatMap(map -> map.entrySet().stream())
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
//给群组发消息
for (Map.Entry<UUID, SocketIOClient> entry : clientMap.entrySet()) {
entry.getValue().sendEvent("groupReceive",ApiResult.success(info));
}
client.sendEvent("groupReceive",info);
}
Vue客户端
安装依赖(socket.io-client似乎没有用到,但还是安装了好一点)
npm i vue-socket-io
npm i socket.io-client
main.js中配置连接服务端端口
import Vue from 'vue'
import VueSocketIO from 'vue-socket.io'
Vue.use(new VueSocketIO({
//是否打印日志
debug:false,
//服务端端口地址
connection: 'http://localhost:3000',
options:{
//是否自动连接
autoConnect:false
}
}))
import App from './App.vue'
import router from './router'
import store from './store'
注!!!VueSocketIO最好写在导入了vue之后就导入,不然容易有bug(至少我是这样)
实现私聊与群聊功能
这里我就不写视图层的代码了
export default {
data(){
return{
message:"",
username:"",
chatMessages:{
//可以在这里创建接收私聊与群聊消息的数组
privateMessage:[],
groupMessage:[],
}
}
},
methods:{
openSocket(){
//打开socket连接
//在测试时将username后的数字更改
this.username = '用户123';
this.$socket.open();
},
sendPrivateMessage(){
//发送私聊消息
//第一个参数是与服务端约定好的监听变量名,第二个是要传输的数据,可以是一个对象
this.$socket.emit("privateMessage","这是一条私聊消息");
},
sendGroupMessage(){
this.$socket.emit("groupMessage","这是一条群聊消息");
}
},
sockets:{
connect(){
//向客户端发送用户信息
this.$socket.emit("userInfo",{username:this.username});
console.log("连接成功");
},
connecting(){
console.log("正在连接");
},
disconnect(){
console.log("断开连接");
},
connect_error() {
console.log("Socket 连接失败");
},
//监听用户信息的返回
userInfo(data){
console.log(data);
},
//监听私聊消息
//这种方法无法使用this.***
//发送消息后自己的监听
privateSent(data){
console.log(data);
},
//对方接收的消息
privateReceive(data){
console.log(data);
},
//监听群聊消息
groupMessage:function (data){
console.log(data);
}
}
}
这样基本就能够实现一个使用springboot+vue+socket.io进行的实时通信了,如果不行的话可能是我哪里写错或者泄露了,可以联系我帮你找bug。