一个简单的netty-socketio


前言

在web开发过程中,多数情况下是前端主动向服务端发起请求,但也有需要服务端通知前端的场景,最最典型的例子就是网页版的客服系统,聊天是需要服务端将消息传给另外一个人;本文将使用netty-socketio演示如何向前端断送消息,读者需具备基本的软件开发能力

一、netty-socetio是什么?

netty-socketio 是一个开源的Socket.io 服务器端的一个Java的实现,他基于Netty框架,web可以通过websocket和socketio建立连接,双方基于事件进行互相监听

二、使用步骤

1.引入相关jar包

以下为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>

2.启动类

@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启动成功!");
    }
}

3.netty-socketio的配置类

@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());
    }
}

4.事件处理类

**
 * 客户端和服务端都是通过事件来交互的
 * 用于监听客户端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());
            }
        }
    }
}

5.前端html页面

<!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
如有侵权,请联系删除

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值