在springboot+vue中使用socketio进行私聊群聊

随着实时通信需求的增加,开发人员寻求有效的解决方案来实现即时通讯功能。在这篇博客中,我们将介绍如何在Spring Boot和Vue.js应用程序中使用Socket.IO库实现私聊和群聊功能。Socket.IO是一个基于Node.js的实时应用程序框架,它为客户端和服务器之间提供了双向的实时通信。

Socket.IO官方中文文档

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。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值