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

1 篇文章 0 订阅
1 篇文章 0 订阅

因为项目中需要用到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

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

会造成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
实时聊天支持一对一和多人

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值