Websocket入门教程


在网络通讯领域,我们最熟悉的通讯协议即 HTTP,其在 TCP 的基础上进一步扩展使之更为的简单易用。而 WebSocket 作为一种通讯方式在某些应用场景下也是一种不错的选择。

WebSocket 是一种在单个 TCP 连接上进行全双工通信的通信协议,它提供了一个持久的连接(即长链接),允许客户端和服务器之间进行实时、双向的数据传输。WebSocket 的出现解决了传统的 HTTP 协议的一些限制,特别是对于需要频繁更新的实时应用程序(如聊天应用、在线游戏等)而言,WebSocket 提供了更低的延迟和更高的效率。

Websocket 一个经典应用场景即消息的主动推送,即由后端向主动推送讯息,在通知与聊天场景中应用广泛。本文即着重介绍如何在前后端分离项目中实现后端的消息主动推送。

一、后端配置

1. 依赖导入

老规矩,在后端的 Spring 项目工程中导入对应的依赖,内容如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2. 配置管理

在项目中注入 WebSocket Bean 对象,代码如下:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3. 服务启动

新建 WebSocketServer 类用于管理请求的连接与关闭,当接收到新的请求时存入缓存容器 websocketMap,同时计数器 onlineCount 自增用于记录当前的连接数。

为了区分不同的连接这里通过请求参数的第一个值作为唯一标识符,即请求地址格式为:ws://${host}:${port}/websocket?userId=1,从而使后续能够精准的推送消息给指定连接用户。

其中 @ServerEndpoint 注解可以理解为 HTTP 接口中 @RequstMapping@RestController 的复合体, onMessage() 方法为首次连接时返回值,用于响应以便知晓是否连接成功。

@Component
@ServerEndpoint(value = "/websocket")
public class WebSocketServer {

    private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);

    /**
     * 当前在线连接数
     */
    private static final AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * 存放客户端对应的 WebSocket 对象
     */
    private static final Map<String, WebSocketServer> websocketMap = new ConcurrentHashMap<>();

    /**
     * 会话标识
     */
    private String flag;

    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */
    private Session session;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        // 添加会话至容器
        this.session = session;
        String queryString = session.getQueryString();
        flag = queryString.split("=")[1];
        websocketMap.put(flag, this);
        // 计数器自增
        onlineCount.incrementAndGet();

        try {
            log.info("Receive new session, current connected number: " + getOnlineCount());
            sendMessage("You have successfully connected.");
        } catch (IOException e) {
            log.error("Websocket IO exception, stack tree: ", e);
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        // 从容器中删除会话
        websocketMap.remove(flag);
        // 计数器自减
        onlineCount.decrementAndGet();
        log.info("New session close, current connected number: " + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("Receive from client, message: {}", message);

        // 群发消息
        for (WebSocketServer item : websocketMap.values()) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                log.error("Session onMessage, stack tree: ", e);
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("Session onError, stack tree: ", error);
    }

    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote()
                .sendText(message);
    }


    public int getOnlineCount() {
        return onlineCount.get();
    }
}

4. 消息推送

当连接建立之后即可向连接主动推送数据消息,这里定义了两个方法 send()batchSend() 用于单点推送与消息群发,其仍定义于上述的 WebSocketServer 类中,限于篇幅故分点介绍。

其中单点推送方法 send() 第一个参数 key 即建立连接时传入的唯一标识符,batchSend() 则会推送消息至所有当前的连接的用户,注意其中输入的内容为字符串,可使用 Jackson 等类库序列化为 Json 字符串后传输。

/**
 * 群发自定义消息
 */
public boolean send(String key, String message) {
    if (websocketMap.isEmpty()) {
        log.info("Currently didn't have valid session.");
        return false;
    }

    boolean success = false;
    try {
        WebSocketServer server = websocketMap.get(key);
        if (Objects.isNull(server)) {
            log.info("The session of [{}] not existed.", key);
            return false;
        }

        server.sendMessage(message);
        success = true;
    } catch (IOException e) {
        log.error("Session send() error, stack tree: ", e);
    }
    return success;
}

public boolean batchSend(String message) {
    if (websocketMap.isEmpty()) {
        log.info("Currently didn't have valid session.");
        return false;
    }

    boolean success = true;
    for (WebSocketServer item : websocketMap.values()) {
        try {
            item.sendMessage(message);
        } catch (IOException e) {
            success = false;
            log.error("Session batchSend() error, stack tree: ", e);
        }
    }
    return success;
}

二、前端应用

1. 依赖安装

Vue 前端项目中同样需要安装 WebSocket 依赖,命令如下:

npm install vue-websocket

2. 应用监听

完成依赖安装后即可在项目中建立依赖,通过 new WebSocket(url) 即可相对较为简单。

连接建立之后通过 addEventListener 回调函数监听不同的连接事件,代码如下不详细介绍。

created() {
    const host = 127.0.0.1
    const port = 9090
    const url = `ws://${host}:${port}/websocket?userId=1`
    const socket = new WebSocket(url);

    // 添加WebSocket事件监听器
    socket.addEventListener('open', (event) => {
      console.log('WebSocket 连接已打开', event);
    })

    socket.addEventListener('message', (event) => {
      const data = event.data
      console.log('接受到后端信息: ', data)
      if (data !== null) {
        this.$message.success('接受到后端消息: ' + data)
      }
    })

    socket.addEventListener('close', (event) => {
      console.log('WebSocket 连接已关闭', event.data);
    })

    socket.addEventListener('error', (event) => {
      console.error('WebSocket 错误', event);
    })
  }
}

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录