在web开发过程中,多数情况下是前端主动向服务端发起请求,但也有需要服务端通知前端的场景,最最典型的例子就是网页版的客服系统,聊天是需要服务端将消息传给另外一个人;本文将使用netty-socketio演示如何向前端断送消息,读者需具备基本的软件开发能力
netty-socketio 是一个开源的Socket.io 服务器端的一个Java的实现,他基于Netty框架,web可以通过websocket和socketio建立连接,双方基于事件进行互相监听
以下为pom文件当中的一些坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>netty-socketio</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.17</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.67</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
@SpringBootApplication
@Slf4j
public class NettySocketioApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(NettySocketioApplication.class, args);
}
@Autowired
private SocketIOServer socketIOServer;
@Override
public void run(String... strings) {
socketIOServer.start();
log.info("socket.io启动成功!");
}
}
@Configuration
public class NettySocketioConfig {
/**
* netty-socketio服务器
* 主类的socketio
*/
@Bean
public SocketIOServer socketIOServer() {
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
config.setHostname("localhost");
config.setPort(9092);//监听的socket端口
SocketIOServer server = new SocketIOServer(config);
return server;
}
/**
* 用于扫描netty-socketio的注解,比如 @OnConnect、@OnEvent
*/
@Bean
public SpringAnnotationScanner springAnnotationScanner() {
return new SpringAnnotationScanner(socketIOServer());
}
}
**
* 客户端和服务端都是通过事件来交互的
* 用于监听客户端websocket的事件
* 同时也可以往客户端发送事件(客户端自己可以监听)
*/
@Component
@Slf4j
public class MessageEventHandler {
@Autowired
private SocketIOServer socketIoServer;
/**
* 线程安全的map,用于保存和客户端的回话
*
* 如果是使用集群部署的情况下则不能这么使用,
* 因为客户端每次命中的服务不一定是上次命中那个
* 集群解决方案:使用redis的发布订阅或者消息中间件的发布订阅
* 这样,每个服务都有listener监听着,然后可以拿到对应的客户端socketclient
*/
public static ConcurrentMap<String, SocketIOClient> socketIOClientMap = new ConcurrentHashMap<>();
/**
* 客户端连接的时候触发
* @param client
*/
@OnConnect
public void onConnect(SocketIOClient client) {
//
String mac = client.getHandshakeData().getSingleUrlParam("mac");
//存储SocketIOClient,用于发送消息
socketIOClientMap.put(mac, client);
//通过client.sendEvent可以往客户端回发消息
client.sendEvent("message", "onConnect back");
log.info("客户端:" + client.getSessionId() + "已连接,mac=" + mac);
}
/**
* 客户端关闭连接时触发
*
* @param client
*/
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
SocketIOClient socketIOClient = socketIOClientMap.get(client.getHandshakeData().getSingleUrlParam("mac"));
if (null != socketIOClient){
socketIOClientMap.remove(socketIOClient);
}
log.info("客户端:" + client.getSessionId() + "断开连接");
}
/**
* 监听客户端事件messageevent
*
* @param client 客户端信息
* @param request 请求信息
* @param data 客户端发送数据
*/
@OnEvent(value = "messageevent")
public void onEvent(SocketIOClient client, AckRequest request, Message data) {
log.info("发来消息:" + data);
//回发消息
client.sendEvent("messageevent", "我是服务器都安发送的信息==" + data.getMsgContent());
//广播消息
sendBroadcast();
}
/**
* 监听客户端事件messageevent
*
* @param client 客户端信息
* @param data 客户端发送数据
*/
@OnEvent(value = "messageevent2")
public void messageevent2(SocketIOClient client, JSONObject data) {
log.info("发来消息:" + data);
//回发消息
client.sendEvent("messageevent2", "我是服务器都安发送的信息==" + data.getString("FirstName"));
}
/**
* 广播消息
*/
public void sendBroadcast() {
for (SocketIOClient client : socketIOClientMap.values()) {
if (client.isChannelOpen()) {
client.sendEvent("Broadcast", "当前时间", System.currentTimeMillis());
}
}
}
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, user-scalable=no">
<title>websocket-java-socketio</title>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
</head>
<body>
<h1>Socket.io Test</h1>
<div><p id="status">Waiting for input</p></div>
<div><p id="message">hello world!</p></div>
<button id="connect" onClick='connect()'/>Connect</button>
<button id="disconnect" onClick='disconnect()'>Disconnect</button>
<button id="send" onClick='send()'/>Send Message</button>
</body>
<script type="text/javascript">
/**
* 前端js的 socket.emit("事件名","参数数据")方法,是触发后端自定义消息事件的时候使用的,
* 前端js的 socket.on("事件名",匿名函数(服务器向客户端发送的数据))为监听服务器端的事件
**/
var socket = io.connect("http://localhost:9092?mac=2");
var firstconnect = true;
function connect() {
socket.socket.reconnect();
}
//监听服务器连接事件
socket.on('connect', function(){ status_update("Connected to Server"); });
//监听服务器关闭服务事件
socket.on('disconnect', function(){ status_update("Disconnected from Server"); });
//监听服务器端发送消息事件
socket.on('messageevent', function(data) {
message(data)
//console.log("服务器发送的消息是:"+data);
});
socket.on('messageevent2', function(data) {
message(data)
//console.log("服务器发送的消息是:"+data);
});
//断开连接
function disconnect() {
socket.disconnect();
}
function message(data) {
document.getElementById('message').innerHTML = "Server says: " + data;
}
function status_update(txt){
document.getElementById('status').innerHTML = txt;
}
function esc(msg){
return msg.replace(/</g, '<').replace(/>/g, '>');
}
//点击发送消息触发
function send() {
console.log("点击了发送消息,开始向服务器发送消息")
var jsonObj = {'FirstName':'xu','LastName':'Xiang'};
//socket.emit('messageevent', {msgContent: msg});//{msgContent: msg}是一个json对象,到了服务端会反序列化
//socket.emit('messageevent2', {msgContent: msg});//{msgContent: msg}是一个json对象,到了服务端会反序列化
socket.emit('messageevent2',jsonObj);//{msgContent: msg}是一个json对象,到了服务端会反序列化
};
</script>
</html>
本文只是一个快速入门的案例,但已经满足了博主在项目中的需求,不足之处有一下几点:
1.客户端和服务端建立连接的对象是保存在服务本地内存中的,如果服务使用集群的方式部署,则客户端的请求不一定每次都落在同一台机器上,所以有可能会找不到对应的client对像,客户端无法收到消息;可以使用redis,mq等消息中间件来解决
2.不支持图片的等文件的发送
代码地址:https://gitee.com/supermanAndBoy/nettty-socketio-demo.git
如有侵权,请联系删除