因为项目中需要用到socket实现端到端的实时通信对话
网上的教程也多是后台使用node,笔者这里项目后台使用的是java,所以只能另辟蹊径
话不多说开搞,这里记录一下搞得过程遇到的需要注意的问题
Java 后端
1.在pom.xml添加netty-socketio的jar包
com.corundumstudio.socketio
netty-socketio
1.7.7
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 {
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() {
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
这个以上是容器需要启动的与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() {
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 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
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
现在变成了
ws://172.17.30.221:7777/?EIO=3
以上就搭建完成了,但是还没有达到客户端间建立房间发送消息以及优化消息数据结构
在这里笔者想说几句
这里的所有的东西都是笔者一步一步摸出来的,无论对你有没有用也都请你尊重别人的劳动成果
不要只做索取者,倘若我这里没有完善的地方,你用到了,刚好发现了,你也可以指出来,一起完善
说一个最近让人气愤的事,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
实时聊天支持一对一和多人