背景
最近有一个项目,需要实现二维码扫码登录。很显然,长连接是基础。
原计划,直接通过vue + node来实现,后面鉴于后台技术栈已经是java了。所以只能通过vue + java来实现,不曾想,坑还是蛮多的。
实现
大致流程如下:
--------------------
服务端监听握手状态,监听到握手了
↓
监听到握手了,发送初始化指令给前端
↓
前端初始化
---------------------
vue端用的socket.io在之前的文章里头已经讲解过了。
重点讲解下java端的socket.io的用法。
服务端实现代码
引入依赖:
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
核心工具类:
package com.ruijie.rlogin.controller;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.ruijie.rlogin.core.constant.LoginConstant;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Service
public class SocketIO {
/**
* 用来存已连接的客户端
*/
private static Map<String, SocketIOClient> clientMap = new ConcurrentHashMap<>();
private SocketIOServer socketIOServer;
/**
* Spring IoC容器创建之后,在加载SocketIOServiceImpl Bean之后启动
* @throws Exception
*/
@PostConstruct
private void autoStartup() {
start();
}
/**
* Spring IoC容器在销毁SocketIOServiceImpl Bean之前关闭,避免重启项目服务端口占用问题
* @throws Exception
*/
@PreDestroy
private void autoStop() {
stop();
}
private void start() {
Configuration config = new Configuration();
// 别加,不然部署生产环境会出现访问不了的现象
// config.setHostname("127.0.0.1");
config.setPort(9092);
config.setMaxFramePayloadLength(1024 * 1024);
config.setMaxHttpContentLength(1024 * 1024);
socketIOServer = new SocketIOServer(config);
socketIOServer.addConnectListener((SocketIOClient client) -> {
String clientInfo = client.getRemoteAddress().toString();
String clientIp = clientInfo.substring(1,clientInfo.indexOf(":"));//获取ip
log.info("链接用户:{}, {}", clientIp, client.getSessionId());
String key = LoginConstant.UUID_PREFIX + LoginController.loopGenerate() + 1;
client.sendEvent("con", key);
});
socketIOServer.addDisconnectListener(client -> {
String clientInfo = client.getRemoteAddress().toString();
String clientIp = clientInfo.substring(1,clientInfo.indexOf(":"));//获取ip
client.sendEvent("discon", "ip: " + clientIp);
log.info("socket.io断开链接");
});
socketIOServer.addEventListener("wakeup", String.class, (client, data, arg2) -> {
// String clientInfo = client.getRemoteAddress().toString();
// String clientIp = clientInfo.substring(1, clientInfo.indexOf(":"));
System.out.println(":客户端:************"+data);
// client.sendEvent("msginfo", "服务端返回信息!");
client.sendEvent("con","测试下");
});
socketIOServer.start();
log.info("socket.io初始化服务完成");
}
public void stop() {
if (socketIOServer != null) {
socketIOServer.stop();
socketIOServer = null;
}
log.info("socket.io服务已关闭");
}
}
前端代码
sockets: {
con(data) {
// 服务端addConnectListener里头触发con方法
console.log(data)
this.qrcode(data)
},
disconnect(data){
this.qrcode()
},
newmsg(data) {
console.log(`接收到服务端传来的参数:${JSON.stringify(data)}`)
}
},
看似很简单。丫的,坑就暗藏其中!
问题
addConnectListener里头的Client.sendEvent方法会被执行,但是前端收不到信息
整个项目运行过程中,我发现了如上问题。经过跟踪和调查,发现addConnectListener里头的client.sendEvent存在一定的失败概率。因此,我想了下,改变了触发初始化的策略:
当vue组件mounted的时候,由前端往服务端发送一个指令,告诉服务端,可以初始化了,然后服务端,再往前端发送初始化的指令!
整个流程变成:
--------------------
客户端界面初始化完毕,发送可以初始化的指令到服务端
↓
服务端接收到指令,发送初始化指令给前端
↓
前端初始化
---------------------
前端代码修改如下:
sockets: {
con(data) {
console.log(data)
this.qrcode(data)
},
disconnect(data){
this.qrcode()
},
newmsg(data) {
console.log(`接收到服务端传来的参数:${JSON.stringify(data)}`)
}
},
mounted() {
this.qrcode();
this.$socket.emit('wakeup','初始化指令')
},
后端代码修改如下(注释掉addConnectListener发送指令的代码,其他不变):
socketIOServer.addConnectListener((SocketIOClient client) -> {
String clientInfo = client.getRemoteAddress().toString();
String clientIp = clientInfo.substring(1,clientInfo.indexOf(":"));//获取ip
// log.info("链接用户:{}, {}", clientIp, client.getSessionId());
// String key = LoginConstant.UUID_PREFIX + LoginController.loopGenerate() + 1;
// client.sendEvent("con", key);
});