今天在这里跟大家分享一下springboot项目集成socketIo实现实时推送功能。不多说什么直接上代码,然后慢慢讲解。
第一步项目中准备socketIo的运行环境
<!--socketio-->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.11</version>
</dependency>
第二步 socketIo的运行类,和启动类。
@Configuration
public class NettySocketConfig {
private String ipWin;
private String ipLinxu;
private int port;
@Bean
public SocketIOServer socketIOServer() throws Exception{
ipWin = (String)YmlUtil.getValue("socketIo.win");
ipLinxu = (String)YmlUtil.getValue("socketIo.linxu");
port = (Integer) YmlUtil.getValue("socketIo.port");
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
String os = System.getProperty("os.name");
if(os.toLowerCase().startsWith("win")){
//在本地window环境测试时用localhost
config.setHostname(ipWin);
} else {
//部署到你的远程服务器正式发布环境时用服务器公网ip
config.setHostname(ipLinxu);
}
// 端口,任意
config.setPort(port);
config.setMaxFramePayloadLength(1024 * 1024);
config.setMaxHttpContentLength(1024 * 1024);
//该处进行身份验证h
config.setAuthorizationListener(handshakeData -> {
//http://localhost:8081?username=test&password=test
//例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息
//String username = data.getSingleUrlParam("username");
//String password = data.getSingleUrlParam("password");
return true;
});
final SocketIOServer server = new SocketIOServer(config);
return server;
}
@Bean
public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
return new SpringAnnotationScanner(socketServer);
}
}
读取配置文件的util类,用于读取yml文件中的配置
public class YmlUtil {
/**
* key:文件名索引
* value:配置文件内容
*/
private static Map<String, LinkedHashMap> ymls = new HashMap<>();
/**
* string:当前线程需要查询的文件名
*/
private static ThreadLocal<String> nowFileName = new ThreadLocal<>();
/**
* 加载配置文件
* @param fileName
*/
public static void loadYml(String fileName) {
nowFileName.set(fileName);
if (!ymls.containsKey(fileName)) {
ymls.put(fileName, new Yaml().loadAs(YmlUtil.class.getResourceAsStream("/" + fileName), LinkedHashMap.class));
}
}
public static Object getValueByKey(String key) throws Exception {
// 首先将key进行拆分
String[] keys = key.split("[.]");
// 将配置文件进行复制
Map ymlInfo = (Map) ymls.get(nowFileName.get()).clone();
for (int i = 0; i < keys.length; i++) {
Object value = ymlInfo.get(keys[i]);
if (i < keys.length - 1) {
ymlInfo = (Map) value;
} else if (value == null) {
throw new Exception("key不存在");
} else {
return value;
}
}
throw new RuntimeException("不可能到这里的...");
}
public static Object getValue(String key) throws Exception {
// 首先加载配置文件
loadYml("application.yml");
return getValueByKey(key);
}
}
socketIo启动类
@Component
@Order(value=1)
public class ServerRunner implements CommandLineRunner {
private final SocketIOServer server;
@Autowired
public ServerRunner(SocketIOServer server) {
this.server = server;
}
@Override
public void run(String... args) throws Exception {
server.start();
System.out.println("socket.io启动成功!");
}
}
socketIo的前后端交互
@Component
public class SocketIoServer {
public static SocketIOServer socketIoServer;
@Autowired
public SocketIoServer(SocketIOServer server) {
this.socketIoServer = server;
}
@OnConnect
public void onConnect(SocketIOClient client) {
// TODO Auto-generated method stub
String sa = client.getRemoteAddress().toString();
String clientIp = sa.substring(1, sa.indexOf(":"));// 获取设备ip
System.out.println(clientIp + "-------------------------" + "客户端已连接");
Map<String, List<String>> params = client.getHandshakeData().getUrlParams();
SocketIoServerMapUtil.put(clientIp, client);
}
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
// TODO Auto-generated method stub
String sa = client.getRemoteAddress().toString();
String clientIp = sa.substring(1, sa.indexOf(":"));// 获取设备ip
System.out.println(clientIp + "-------------------------" + "客户端已断开连接");
SocketIoServerMapUtil.remove(clientIp);
}
@OnEvent(value = "text")
public void onEvent(SocketIOClient client, AckRequest ackRequest, String data) {
// TODO Auto-generated method stub
// 客户端推送advert_info事件时,onData接受数据,这里是string类型的json数据,还可以为Byte[],object其他类型
String sa = client.getRemoteAddress().toString();
String clientIp = sa.substring(1, sa.indexOf(":"));// 获取客户端连接的ip
Map<String, List<String>> params = client.getHandshakeData().getUrlParams();// 获取客户端url参数
System.out.println(clientIp + ":客户端:************" + data);
JSONObject gpsData = (JSONObject) JSONObject.parse(data);
String userIds = gpsData.get("userName") + "";
String taskIds = gpsData.get("password") + "";
client.sendEvent("text1", "后台得到了数据");
}
}
存放前端连接的设备ip SocketIoServerMapUtil 类
public class SocketIoServerMapUtil {
public static ConcurrentMap<String, SocketIOClient> webSocketMap = new ConcurrentHashMap<>();
public static void put(String key, SocketIOClient SocketIOClient) {
webSocketMap.put(key, SocketIOClient);
}
public static SocketIOClient get(String key) {
return webSocketMap.get(key);
}
public static void remove(String key) {
webSocketMap.remove(key);
}
public static Collection<SocketIOClient> getValues() {
return webSocketMap.values();
}
public static ConcurrentMap<String, SocketIOClient> getWebSocketMap() {
return webSocketMap;
}
}
前端代码,这里需要我们前端项目中引进socket.io.js这个类。我这边介绍vue引入socket:npm install --save socket.io,不是vue环境的直接去下载一个js文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./socket.io.js"></script>
</head>
<body>
<script>
//创建socket
var socket = io('http://localhost:9092');//用于连接后端的服务
/*
创建自定义事件 'news'
作用:接受服务端 socket.emit('news', 数据); 发出的数据
*/
socket.on('connect', function () {
socket.emit('text', JSON.stringify({userName:"caiyy",passWord:"123456"}));
});
socket.on('text1', function (data) {
//输出服务端响应了数据
console.log(data);
});
</script>
</body>
</html>
现在所有的代码已经都准备好了,开始讲解详细步骤了。
开始导入socketIo的jar包,由于springboot存在一个启动类,我们这边的socketIo的配置虽然可以放在springboot的启动类中,但是一般情况下还是提取起来到一个类中。
public static void main(String[] args) {
SpringApplication.run(EventApplication.class,args);
}
这里定义的是NettySocketConfig类。这个类中是socketIo的配置项,ipWin是window系统的ip,一般也就是本机测试的ip127.0.0.1;ipLinux是linux环境的ip,一般是我们正式环境的ip;port是socket的端口号,一般是9092,这些都是可以在application.yml中配置的,下面就是我在yml中的配置。
#网络ip
socketIo:
win: localhost
linux: 各自linux环境的ip
port: 9092
然后就是配置socketIo的启动项即ServerRunner类。@Order(value=1)这个配置很重要,
这个标记包含一个value属性。属性接受整形值。如:1,2 等等。值越小拥有越高的优先级。就是因为我们springboot自带一个启动类,所以在这里配置启动的value值为1.就是在springboot启动之后在启动socketIo.启动完成配置也搞定了 ,现在就是要进行前后端的交互了。
@OnConnect用于监听客户端连接信息的,
@OnDisconnect用户监听客户端断开信息的。
@OnEvent(value = "text")用户后端监听前端的请求事件的。value值就是前端请求的唯一标识,前端携带这个请求的唯一标识进行请求后台,然后后台监听到这个请求,然后进行一系列操作。
client.sendEvent("text1", "后台得到了数据");用于后端响应前端数据。text1就是后台给前端的唯一标识,前端通过这个唯一标识来筛选后端给的数据是否是这个自己这个连接中所需要的。来确保消息不会发送给错误的前端连接者。
当后台服务启动之后,通过浏览器访问我们写的那个页面,后台就是看到想对应的ip连到后台服务当中,前端也会相对应的打印出“后台得到了数据”。
现在就在讲一个中间写了的一个没有用到的util类,SocketIoServerMapUtil 类。
这个类用户存储前端正在存于连接的ip,我们可以用这个类进行集体推送消息。只要前端连接到我们这个服务,在SocketIoServer里面的监听里面就用到了这个类的put方法。SocketIoServerMapUtil.put(clientIp, client);存入了客户端的ip。当我们的程序中某一个步骤启动了,要触发到集体推送消息的时候,我们可以在这个步骤中添加一段代码:
for (SocketIOClient client: SocketIoServerMapUtil.getValues()) {
client.sendEvent("quntui", "新年快乐");
}
然后在前端代码需要接受这个群推的登录者中加入以下代码就可以得到这个消息:
socket.on('quntui', function (data) {
//输出服务端响应了数据
alert(data);
});
本次分享到此结束。。。欢迎大家留言评论和互相交流。
原文作者技术博客:https://www.jianshu.com/u/ac4daaeecdfe
95后前端妹子一枚,爱阅读,爱交友,将工作中遇到的问题记录在这里,希望给每一个看到的你能带来一点帮助。