前端vue使用vue-socket.io与socket.io-client与后台使用netty-socketio建立实时通信

彭存
2023-12-01

因为项目中需要用到socket实现端到端的实时通信对话
网上的教程也多是后台使用node,笔者这里项目后台使用的是java,所以只能另辟蹊径
话不多说开搞,这里记录一下搞得过程遇到的需要注意的问题
Java 后端
1.在pom.xml添加netty-socketio的jar包

<!-- 引入socketIo的jar包 -->
        <dependency>
            <groupId>com.corundumstudio.socketio</groupId>
            <artifactId>netty-socketio</artifactId>
            <version>1.7.7</version>
        </dependency>


2.添加socketIo辅助类

package com.magicbox.api.prescription.utils;

import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.Transport;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;

@Component("SocketIO")
public class SocketIO implements ApplicationListener<ContextRefreshedEvent> {

    public void onApplicationEvent(ContextRefreshedEvent arg0) {
        if (arg0.getApplicationContext().getParent() != null) {// root application context 有parent,他就是儿子.
            // 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
            new Thread(new Runnable() {

                public void run() {
                    // TODO Auto-generated method stub
                    socketStart();
                }
            }).start();
        }

    }

    private void socketStart() {
        System.out.println("in socketio");

        Configuration config = new Configuration();
        config.setHostname("172.17.30.221");
        config.setPort(7777);

        SocketConfig sockConfig = new SocketConfig();
        //地址服用,这时候再启动不报错
        sockConfig.setReuseAddress(true);
        
        //设置使用的协议和轮询方式
        config.setTransports( Transport.WEBSOCKET,Transport.POLLING);
        //设置允许源
        config.setOrigin(":*:");

        config.setSocketConfig(sockConfig);
        //允许最大帧长度
        config.setMaxFramePayloadLength(1024 * 1024);
        //允许下最大内容
        config.setMaxHttpContentLength(1024 * 1024);
        SocketIOServer server = new SocketIOServer(config);
        server.addConnectListener(new ConnectListener() {
            public void onConnect(SocketIOClient client) {
                // TODO Auto-generated method stub
                String clientInfo = client.getRemoteAddress().toString();
                String clientIp = clientInfo.substring(1, clientInfo.indexOf(":"));// 获取ip
                System.out.println("建立客户端连接ip"+clientIp);
                client.sendEvent("connected", "ip: " + clientIp);
            }
        });

        server.addDisconnectListener(new DisconnectListener() {

            public void onDisconnect(SocketIOClient client) {
                String clientInfo = client.getRemoteAddress().toString();
                String clientIp = clientInfo.substring(1, clientInfo.indexOf(":"));// 获取ip
                System.out.println("断开客户端连接ip"+clientIp);
                client.sendEvent("disconned", "ip: " + clientIp);

            }
        });

        server.addEventListener("msginfo", String.class, new DataListener<String>() {

            public void onData(SocketIOClient client, String data, AckRequest arg2) throws Exception {
                // TODO Auto-generated method stub
                String clientInfo = client.getRemoteAddress().toString();
                String clientIp = clientInfo.substring(1, clientInfo.indexOf(":"));
                System.out.println(clientIp + ":客户端:************" + data);

                client.sendEvent("msginfo", "服务端返回信息!");
            }
        });

        server.start();
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        server.stop();
    }
}

 
 
 
以上的代码有几个需要注意的地方
1.代码实现了implements ApplicationListener<ContextRefreshedEvent>这个以上是容器需要启动的与springmvc差不多(平行)的应用,意思就是有两个需要启动的应用,web项目启动时候,tomcat会遍历有多少个ApplicationListener,这时候会执行这个类的onApplicationEvent方法,如果不加以判断,每次执行root ApplicationListener 时候都会调用一次onApplicationEvent方法,这就会使得重复执行onApplicationEvent方法,会导致 地址被占用的(address already in use)
在本项目也遇到了该问题
先来看一下本项目的容器

所以这里SocketIOServer这个component启动了两次调用了两次onApplicationEvent方法
第一次在root webapplication ,第二次是自己SocketIOServer,所以会造成地址服用
解决:当root webapplication 启动后才运行
代码为

if (arg0.getApplicationContext().getParent() != null) {// root application context 有parent,他就是儿子.
            // 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
            new Thread(new Runnable() {

                public void run() {
                    // TODO Auto-generated method stub
                    socketStart();
                }
            }).start();
        }

    }
 
判断root启动后再执行

2再者是要注意netty socketserver端的配置

        Configuration config = new Configuration();
        config.setHostname("172.17.30.221");
        config.setPort(7777);

        SocketConfig sockConfig = new SocketConfig();
        //地址服用,这时候再启动不报错
        sockConfig.setReuseAddress(true);
        
        //设置使用的协议和轮询方式
        config.setTransports( Transport.WEBSOCKET,Transport.POLLING);
        //设置允许源
        config.setOrigin(":*:");

        config.setSocketConfig(sockConfig);
        //允许最大帧长度
        config.setMaxFramePayloadLength(1024 * 1024);
        //允许下最大内容
        config.setMaxHttpContentLength(1024 * 1024);
        SocketIOServer server = new SocketIOServer(config);
 
3再者是要注意 连接上的方法,断开连接方法,与自定义方法、
01.连接上的方法

    server.addConnectListener(new ConnectListener() {
            public void onConnect(SocketIOClient client) {
                // TODO Auto-generated method stub
                String clientInfo = client.getRemoteAddress().toString();
                String clientIp = clientInfo.substring(1, clientInfo.indexOf(":"));// 获取ip
                System.out.println("建立客户端连接ip"+clientIp);
                client.sendEvent("connected", "ip: " + clientIp);
            }
        });
 
02断开连接的方法

    server.addDisconnectListener(new DisconnectListener() {
            public void onDisconnect(SocketIOClient client) {
                String clientInfo = client.getRemoteAddress().toString();
                String clientIp = clientInfo.substring(1, clientInfo.indexOf(":"));// 获取ip
                System.out.println("断开客户端连接ip"+clientIp);
                client.sendEvent("disconned", "ip: " + clientIp);

            }
        });
 
如果客户端socketIO是在main.js配置的
则建立连接与断开连接的响应方法必须在APP.vue才能响应到,不能在具体页面

03自定义的方法

server.addEventListener("msginfo", String.class, new DataListener<String>() {

            public void onData(SocketIOClient client, String data, AckRequest arg2) throws Exception {
                // TODO Auto-generated method stub
                String clientInfo = client.getRemoteAddress().toString();
                String clientIp = clientInfo.substring(1, clientInfo.indexOf(":"));
                System.out.println(clientIp + ":客户端:************" + data);
                client.sendEvent("msginfo", "服务端返回信息!");
            }
        });
 
其次还要注意client.sendEvent发给客户端的方法有几个,比如广播方法,发给具体的某个人,发到某个房间内等(待完成)
如在node的socket 的方法是这样

// 所有的消息请求都是建立在已连接的基础上的
    io.on('connect', onConnect);
    // 发送给当前客户端
    socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
    //发送给所有客户端,包括发送者
     io.emit('chat message', '欢淫来到德莱联盟'); 
    // 发送给所有客户端,除了发送者
    socket.broadcast.emit('broadcast', 'hello friends!');
    // 发送给同在 'game' 房间的所有客户端,除了发送者
    socket.to('game').emit('nice game', "let's play a game");
    // 发送给同在 'game' 房间的所有客户端,包括发送者
    io.in('game').emit('big-announcement', 'the game will start soon');
 
这里调用者io与socket不一样是看这里

var io = require('socket.io')(80);

io.on('connection', function (socket) {
  socket.on('message', function () { });
  socket.on('disconnect', function () { });
});
 
socket是io的connection方法回调的入参

//服务端接收到chat message事件向所有客户端转发chat message(包括自己)
io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});
 
java这边的对应的方法是?(待完善)node方法与java方法对比
1加入房间
node

socket.join(val.roomid, () => {
            console.log('加入了', val.name);
            OnlineUser[val.name] = socket.id;
            io.in(val.roomid).emit('joined', OnlineUser); // 包括发送者
            // console.log('join', val.roomid, OnlineUser);
        });
 
java

client.joinRoom(string room);//加入房间
client.leaveRoom(string room);//离开房间
server.getRoomOperations(string room).sendEvent("mes", "发送给房间内搜有对象,包括自己");//发送给所有房间内的客户端
//发送给除自己以外的该房间内的所有客户端
Collection<SocketIOClient> clients= server.getRoomOperations(data).getClients();
                for( SocketIOClient roomClient:clients) {
                    if(roomClient.equals(client)) {
                        continue;
                    }
                    roomClient.sendEvent("mes", "发送给除自己以外的该房间内的所有客户端");
                }
 
这里有个博文作者提供的一些api,但是我对一个api存疑,
BroadcastOperations 对象的的这个方法,源码内没有
sendEvent(eventname,excludeSocketIOClient,data) 排除指定客户端广播。

客户端(vue)
1.下载

npm install vue-socket.io --s
npm install socket.io-client --s
1
2
2.在main.js内添加

//socket
import VueSocketIO from 'vue-socket.io';
import socketio from 'socket.io-client';
Vue.use(new VueSocketIO({
    debug: true,
    connection: socketio('http://172.17.30.221:7777', {
        path: '',
        transports: ['websocket', 'xhr-polling', 'jsonp-polling'],
    }) //options object is Optional
})); //xxx填后台给的socket地址,端口号根据实际后台端口写
 
这里有几点需要注意
1.线上环境可以去除debug
2.path千万不要写“/”
因为成功建立的连接是

ws://172.17.30.221:7777?EIO=3
1
现在变成了

ws://172.17.30.221:7777/?EIO=3
1
会造成400非法连接


以上就搭建完成了,但是还没有达到客户端间建立房间发送消息以及优化消息数据结构
在这里笔者想说几句
这里的所有的东西都是笔者一步一步摸出来的,无论对你有没有用也都请你尊重别人的劳动成果
不要只做索取者,倘若我这里没有完善的地方,你用到了,刚好发现了,你也可以指出来,一起完善
说一个最近让人气愤的事,Apollo开源了,一些中国的程序员竟然去到别人的项目地下灌水,wuwukai都扯出来了,丢脸吗,这么神圣的项目被这些老鼠经过撒了一泡屎,这些人素质堪忧,把脸丢到国外,丢zhongguo人的脸.
目前参考的连接有
https://www.jianshu.com/p/0d20a032d0ec
这片文章点醒了我path的设置,以及transport设置
https://www.jianshu.com/p/1c966c74ac26
这篇点醒了我服务端的跨域配置
https://www.cnblogs.com/editor/archive/2019/03/20/10563755.html
这篇辅助我搭建java端(不是全抄)
https://www.cnblogs.com/mayi1/p/6323238.html
这篇点醒了我Tomcat执行了两次application,我修改了等父的application存在再执行,消去了address already in use错误,服务器启动成功
往后还需要参考学习的一些文张
https://blog.csdn.net/sun_t89/article/details/52060946
两端对话消息发送
https://blog.csdn.net/qing_gee/article/details/52525677
两端对话消息发送
https://www.cnblogs.com/pomer-huang/p/netty-socketio.html
广播消息(学习,待确定可用)
https://blog.csdn.net/ntotl/article/details/53179867
多种方法搭建socketserver以及多客户端连接场景
https://blog.csdn.net/zsj777/article/details/81154233
发送图片的
https://blog.csdn.net/weixin_43290991/article/details/82862503
一对一聊天实现
https://github.com/CBDxin/VueSocial#待改进
一个仿微信的项目的项目
https://github.com/MetinSeylan/Vue-Socket.io
Vue-Socket.io官方github
https://github.com/wuyawei/Vchat
实时聊天支持一对一和多人
 转自https://blog.csdn.net/hardly555/article/details/97903474?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-4&spm=1001.2101.3001.4242

 类似资料: