一文带你快速入门socket.io

背景

前面我们在一文带你快速入门websocket中给大家分享了如何springboot项目中使用websocket,但是这样的websocket是有缺陷的,那就是它缺乏我们在工作中需要的一些高级特性,比如心跳检测,自动重连,以及自适应降级和消息路由等功能。这也就意味着如果我们需要使用这些功能,就需要开发者在websocket的基础上手动实现,而实现这些并不简单。

说下这些高级功能的作用:

  1. 自动重连:当websocket客户端和服务端断开连接后,客户端能够自动重试,重新连接websocket服务端。
  2. 心跳检测:用于检测websocket服务端是否还活着,是否能继续提供服务。
  3. 自适应降级:当浏览器不支持websocket协议时,会自动将协议降级为HTTP长轮训,也就是通过HTTP长轮训来模拟websocket,从而让我们的应用有更好的兼容性,可以支持各种客户端。
  4. 消息路由:这个有点像spring中的@RequestMapping注解,即将请求转发给哪个handler去处理。消息路由就是客户端可以通过不同的路由将消息发送给不同的websocket处理器。

那么有没有什么好用的第三方库在websocket的基础上将这些功能封装好了供我们开箱即用呢?这就是我们今天要讲的主题,那就是如何在springboot项目中使用升级版的websocket。

经过一番调研,我发现有两个技术满足需求,一个是socket.io,另一个则是STOMP协议。这两个框架都实现了自动重连,消息路由,以及自适应降级的功能,区别在于STOMP协议需要用到消息中间件,而socket.io不需要。我的理解是:如果业务中需要使用消息中间件来实现业务解耦,则使用STOMP协议,否则就使用socket.io。今天我们要讲的是socket.io,主要是带大家快速入门,了解socket.io的基本使用。

使用socket.io

引入socket.io依赖

首先我们需要引入socket.io的依赖,如下:

<!-- 添加 Socket.IO 依赖, 仅支持socket-io server 2.x 版本 -->
<dependency>
    <groupId>com.corundumstudio.socketio</groupId>
    <artifactId>netty-socketio</artifactId>
    <version>1.7.25</version>
</dependency>

将socket.io服务器注册到spring容器中

引入依赖后,我们需要创建socket.io服务器,考虑到我们只需要创建一个实例对象,所以我们将其注册为spring中的bean,这是使用单例模式最简单的方式,而且后面我们可以通过@Autowire在任何地方注入这个socket实例对象,用起来也更方便。

package com.lizemin.socketIo.config;

import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 配置和管理 Socket.IO 服务器
 */
@Configuration
public class SocketIoConfig {

    @Bean
    public SocketIOServer socketIOServer() {
        // 创建 SocketConfig 实例,用于配置底层 Socket 连接
        SocketConfig socketConfig = new SocketConfig();

        // 设置 TCP 无延迟,禁用 Nagle 算法,数据会立即发送,减少延迟
        socketConfig.setTcpNoDelay(true);

        // 设置 Socket 关闭时的延迟时间为 0 秒,即立即关闭
        socketConfig.setSoLinger(0);

        // 创建 netty - socketio 的 Configuration 实例,用于配置 Socket.IO 服务器
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        // 将 SocketConfig 配置应用到 Socket.IO 服务器配置中
        config.setSocketConfig(socketConfig);

        // 设置 Socket.IO 服务器监听的主机名
        config.setHostname("localhost");

        // 设置 Socket.IO 服务器监听的端口号
        config.setPort(9092);

        // 设置跨域
        config.setOrigin("http://localhost:8401");
     
        // 使用配置创建并返回一个 SocketIOServer 实例
        return new SocketIOServer(config);
    }

    /**
     * 创建 SpringAnnotationScanner 实例,用于扫描@OnConnect, @OnEvent, @OnDisconnect等注解
     *
     * @param socketServer  SocketIOServer 实例
     * @return  SpringAnnotationScanner 实例
     */
    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
        return new SpringAnnotationScanner(socketServer);
    }
}

启动socket.io服务器

然后我们需要在启动springboot应用的同时,也启动socket.io服务器,具体实现如下:

package com.lizemin.socketIo;

import com.corundumstudio.socketio.SocketIOServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import javax.annotation.PreDestroy;

@Slf4j
@SpringBootApplication
public class SocketIoApplication implements CommandLineRunner {

    @Autowired
    private SocketIOServer socketIOServer;

    public static void main(String[] args) {
        SpringApplication.run(SocketIoApplication.class, args);
    }

    @Override
    public void run(String... args) {
        // 启动 SocketIOServer
        socketIOServer.start();
        log.info("Socket.IO 服务器已启动,端口: 9092");
    }

    @PreDestroy
    public void stop() {
        // 关闭 SocketIOServer
        if (socketIOServer != null) {
            socketIOServer.stop();
        }
    }
}

org.springframework.boot.CommandLineRunner接口就是专门用来做项目启动后的一些初始化工作的,我们实现这个接口中的方法,用来启动socket.io服务器。

接下来我们还需要编写websocket消息的处理逻辑,比如监听客户端的消息发送事件,建立连接的事件,断开连接的事件,这些和我们之前在springboot中使用原生的websocket是一样的。

编写socket.io服务端处理消息的逻辑

下面是我编写的websocket服务端的消息处理逻辑:

package com.lizemin.socketIo.simple;

import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIONamespace;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Component
public class MessageHandler {

    /**
     * 构造函数,用于定时发送消息
     *
     * @param server  SocketIOServer 实例
     */
    @Autowired
    public MessageHandler(SocketIOServer server) {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        scheduler.scheduleAtFixedRate(() -> {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String currentTime = dateFormat.format(new Date());
            // 向所有连接的客户端发送时间
            server.getAllClients().forEach(client -> {
            	// 往time事件发送消息,只有监听time事件的socket.io客户端才会收到这条消息
                client.sendEvent("time", currentTime);
            });
        }, 0, 1, TimeUnit.SECONDS); // 初始延迟0秒,之后每隔1秒执行
    }

    // 监听连接建立的事件
    @OnConnect
    public void onConnect(SocketIOClient client) {
        System.out.println("新客户端连接: " + client.getSessionId());
    }

    // 监听断开连接的事件
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        System.out.println("客户端断开: " + client.getSessionId());
    }

    // 监听客户端发过来的消息, 监听message事件
    @OnEvent("message")
    public void onMessage(SocketIOClient client, AckRequest ackRequest, String message) {
        System.out.println("收到消息: " + message);

        // 给所有socket-io的客户端发送消息
        client.getNamespace().getAllClients().forEach(cli -> {
        	// 给message事件发消息,只有监听message事件的socket.io客户端才会收到这条消息
            cli.sendEvent("message", "服务器收到: " + message);
        });

        // 如果客户端要求服务器回复,则给客户端一个确认消息
        if (ackRequest.isAckRequested()) {
            ackRequest.sendAckData("消息已处理");
        }
    }

    // 监听onPlayGames事件
    @OnEvent("onPlayGames")
    public void onPlayGames(SocketIOClient client, AckRequest ackRequest, String message) {
        System.out.println("收到消息: " + message);
        // 给所有socket-io的客户端发送消息
        SocketIONamespace namespace = client.getNamespace();
        
        namespace.getAllClients().forEach(cli -> {
        	// 给onPlayGames事件发消息,只有监听onPlayGames事件的socket.io客户端才会收到这条消息
            cli.sendEvent("onPlayGames", "服务器收到: " + message);
        });

        // 如果客户端要求服务器回复,则给客户端一个确认消息
        if (ackRequest.isAckRequested()) {
            // 回复客户端
            ackRequest.sendAckData("消息已处理");
        }
    }
}

简单说下这个类的代码逻辑:

  1. @OnEvent("事件名称"):表示监听客户端的哪个事件,有点类似于springMVC中的@RequestMapping注解,当用户往这个事件发消息时,请求会转发给监听该事件的方法。
  2. 通过ScheduledExecutorService和socker.io server的实例对象创建了一个定时任务,每秒中往time事件发送消息,这样监听time事件的socket.io客户端就会每秒中收到服务端发过来的时间。

编写socket.io客户端

使用socket.io客户端同样需要引入依赖,这里我引入的客户端的版本是2.4.0

<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.4.0/socket.io.js"></script>

需要注意的是:引入的socket.io的客户端和服务端的版本要兼容,不然会出问题。

下面是socket.io客户端的完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Netty-SocketIO Demo</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.4.0/socket.io.js"></script>
    <meta charset="UTF-8">
</head>
<body>
<label for="message">输入消息:</label><input type="text" id="message" placeholder="输入消息">
<button onclick="sendMessage()">发送</button>
<div id="output"></div>

<script>
    // 连接服务器
    const socket = io('http://localhost:9092');

    // 监听服务端发过来的消息,监听message事件,对其他事件漠不关心
    socket.on('message', (data) => {
        const div = document.createElement('div');
        div.textContent = data;
        document.getElementById('output').appendChild(div);
    });

    // 给socket-io服务端发送消息
    function sendMessage() {
        const input = document.getElementById('message');
        socket.emit('message', input.value);
        input.value = '';
    }

</script>
</body>
</html>

接下来,咱们测试一下上面的功能是否正常。

测试使用socket.io

首先我们启动springboot项目
在这里插入图片描述
然后咱们访问socket.io的客户端页面,输入消息,点击发送
在这里插入图片描述
可以看到服务器立刻就回复了我们
在这里插入图片描述
而且控制台的日志也符合代码的处理逻辑。
在这里插入图片描述
我们编写的socket.io客户端只监听了message事件,对于服务端的time事件和onPlayGames事件都没有监听,接下来咱们使用postman来测试下这两个事件,最新版的postman也可以作为socket.io的客户端。

首先通过如下几步创建socket.io客户端
在这里插入图片描述
下面是我的socket.io客户端的配置,我为这个客户端添加了几个它监听的事件,其中就包括咱们前面还没测过的time事件和onPlayGames事件
在这里插入图片描述
由于咱们的socket.io服务端使用的2.xx版本的,所以客户端也要设置使用socket.io 2版本的,不然是连不上的,如下图所示:
在这里插入图片描述
最后咱们点击connect按钮,可以看到服务端每隔一秒发过来的时间,如下图所示:
在这里插入图片描述
另外,咱们还可以通过postman给服务端的某个事件发送消息,如下图所示:
在这里插入图片描述
在上图中,我们通过postman给onPlayGames事件发送消息,消息的内容是:“玩游戏吗”,下面是服务器返回的结果:
在这里插入图片描述
这和我们的代码处理逻辑也是一致的
在这里插入图片描述

文章中完整示例代码的地址

文章中完整示例代码的地址

在这里插入图片描述

最后的总结

今天这篇文章中,我们主要带大家学习如何使用升级版的websocket,也就是socket.io,它相当于是在websocket的基础上增加了很多高级功能,比如自动重连,自适应降级,消息路由等等。

最后我们还手把手带大家如何通过postman来测试socket.io发送和接受消息的功能。

觉得有收获的朋友可以点个赞,您的鼓励就是我最大的动力!

### Socket.IO 版本 3.11 的依赖列表 Socket.IO 是一种用于实时 Web 应用程序开发的库,支持双向通信。以下是 `socket.io` 版本 3.11 的主要依赖项及其描述: #### 主要依赖包 - **engine.io**: 这是一个底层传输协议,提供了 WebSocket 和轮询功能的支持[^5]。 - **debug**: 用于调试日志输出的功能模块,帮助开发者跟踪运行时状态[^6]。 - **has-binary**: 检查数据对象是否包含二进制数据,这对于处理复杂的数据结构非常重要[^7]。 - **parseqs**: 解析查询字符串参数的一个轻量级工具,适用于 URL 参数解析场景[^8]。 - **ws**: 提供了一个纯 Node.js 实现的 WebSocket 客户端和服务端库,作为备用传输机制的一部分[^9]。 #### 可选或间接依赖 - **ms**: 将时间转换为人可读的形式(如秒到毫秒),通常用于超时设置和延迟控制[^10]。 - **component-emitter**: 简单事件发射器实现,允许订阅者模式下的消息传递[^11]。 - **to-array**: 转换类数组对象为真正的 JavaScript 数组,主要用于兼容性和扩展性需求[^12]。 这些依赖关系可以通过查看官方文档或者通过 npm 包管理器获取完整的依赖树来验证。例如,在命令行中可以执行以下操作以获得更详细的依赖信息: ```bash npm install socket.io@3.1.1 --dry-run --verbose ``` 此命令会打印出安装过程中涉及的所有直接和间接依赖项。 --- ### 示例代码:如何列出具体依赖 如果需要动态分析某个特定版本的依赖链,可以借助脚本来完成: ```javascript const { execSync } = require('child_process'); function getDependencies(packageName, version) { const command = `npm info ${packageName}@${version} dependencies`; try { const result = execSync(command).toString(); console.log(`Dependencies for ${packageName}@${version}:`, JSON.parse(result)); } catch (error) { console.error("Error fetching dependencies:", error.message); } } getDependencies('socket.io', '3.1.1'); ``` 上述脚本调用了 `npm info` 命令并解析其返回的结果,从而提取指定版本号的依赖清单。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值