本文最后更新于 2023-12-09,文章内容可能已经过时。

WebSocket:

1.介绍:

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 image-20231124233923683

1.1 与Http的关系:

同为应用层协议,共享传输层,网络层,网络接口层的协议.

image-20231124234045776

1.2 通信模型:

image-20231124234151748

2.SpringBoot集成:

2.1 依赖:

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

2.2 配置类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
​
/**
 * 开启WebSocket支持
 */
@Configuration  
public class WebSocketConfig {  
    
    @Bean  
    public ServerEndpointExporter serverEndpointExporter() {  
        return new ServerEndpointExporter();  
    }  
  
} 

固定的配置代码,不需要修改的.

2.3 操作类:

​
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
​
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
​
import org.springframework.stereotype.Component;
​
import lombok.extern.slf4j.Slf4j;
​
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")  // 接口路径 ws://localhost:8087/webSocket/userId;
​
public class WebSocket {
    
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
        /**
     * 用户ID
     */
    private String userId;
    
    //concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    //虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
    //  注:底下WebSocket是当前类名
    private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>();
    // 用来存在线连接用户信息
    private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();
    
    /**
     * 链接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value="userId")String userId) {
        try {
            this.session = session;
            this.userId = userId;
            webSockets.add(this);
            sessionPool.put(userId, session);
            log.info("【websocket消息】有新的连接,总数为:"+webSockets.size());
        } catch (Exception e) {
        }
    }
    
    /**
     * 链接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        try {
            webSockets.remove(this);
            sessionPool.remove(this.userId);
            log.info("【websocket消息】连接断开,总数为:"+webSockets.size());
        } catch (Exception e) {
        }
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     * @param session
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端消息:"+message);
    }
    
      /** 发送错误时的处理
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
​
        log.error("用户错误,原因:"+error.getMessage());
        error.printStackTrace();
    }
​
    
    // 此为广播消息
    public void sendAllMessage(String message) {
        log.info("【websocket消息】广播消息:"+message);
        for(WebSocket webSocket : webSockets) {
            try {
                if(webSocket.session.isOpen()) {
                    webSocket.session.getAsyncRemote().sendText(message);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    // 此为单点消息
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null&&session.isOpen()) {
            try {
                log.info("【websocket消息】 单点消息:"+message);
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    // 此为单点消息(多人)
    public void sendMoreMessage(String[] userIds, String message) {
        for(String userId:userIds) {
            Session session = sessionPool.get(userId);
            if (session != null&&session.isOpen()) {
                try {
                    log.info("【websocket消息】 单点消息:"+message);
                    session.getAsyncRemote().sendText(message);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

2.3.1 方法介绍:

  1. @ServerEndpoint("/websocket/{userId}")

    • 该注解将类标记为一个WebSocket端点,即处理WebSocket连接的类。

    • {userId}是一个占位符,表示WebSocket连接的路径中可以携带一个名为userId的参数。

  2. @OnOpen注解的方法:

    • 该方法用于处理WebSocket连接建立时的事件。

    • Session session参数表示与客户端的连接会话,通过它可以向客户端发送消息。

    • @PathParam(value="userId") String userId注解表示从连接路径中提取的userId参数。

  3. @OnClose注解的方法:

    • 该方法用于处理WebSocket连接关闭时的事件。

    • 在连接关闭时,从webSocketssessionPool中移除对应的信息。

  4. @OnMessage注解的方法:

    • 该方法用于处理接收到客户端发送的消息的事件。

    • String message参数表示接收到的消息内容。

  5. @OnError注解的方法:

    • 该方法用于处理WebSocket连接发生错误时的事件。

    • Session session参数表示出现错误的连接会话,Throwable error参数表示发生的错误。

  6. sendAllMessage方法:

    • 用于向所有连接的客户端广播消息。

    • 遍历webSockets中的所有WebSocket实例,向每个实例发送消息。

  7. sendOneMessage方法:

    • 用于向指定用户发送单点消息。

    • sessionPool中获取指定用户的Session对象,然后向该对象发送消息。

  8. sendMoreMessage方法:

    • 用于向指定多个用户发送单点消息。

    • 遍历指定的用户ID数组,从sessionPool中获取每个用户的Session对象,然后向每个对象发送消息。

2.3.2 工作流程:

  1. 客户端建立连接:

    • 客户端通过WebSocket协议连接到服务器的/websocket/{userId}路径,其中{userId}是一个占位符,表示用户的ID。

    • 服务器接收到连接请求后,执行@OnOpen注解的onOpen方法。

  2. onOpen方法:

    • onOpen方法负责处理WebSocket连接建立时的事件。

    • 该方法获取了与客户端的连接会话(Session session)和从连接路径中提取的用户ID(String userId)。

    • 将当前WebSocket实例加入webSockets中,表示该连接已建立。

    • 将用户ID和连接会话信息存储在sessionPool中,以便后续根据用户ID找到对应的连接。

  3. 客户端发送消息:

    • 当客户端通过连接发送消息时,服务器执行@OnMessage注解的onMessage方法。

    • 该方法负责处理接收到的客户端消息,目前只是打印消息内容。

  4. 客户端关闭连接:

    • 当客户端关闭WebSocket连接时,服务器执行@OnClose注解的onClose方法。

    • 该方法将当前WebSocket实例从webSockets中移除,表示该连接已关闭。

    • 同时,从sessionPool中移除对应的用户ID和连接会话信息。

  5. 服务器向客户端发送消息:

    • 服务器可以通过调用sendAllMessagesendOneMessagesendMoreMessage方法,向客户端发送消息。

    • sendAllMessage方法用于向所有连接的客户端广播消息。

    • sendOneMessage方法用于向指定用户发送单点消息。

    • sendMoreMessage方法用于向指定多个用户发送单点消息。

  6. 错误处理:

    • 当WebSocket连接发生错误时,服务器执行@OnError注解的onError方法。

    • 该方法用于处理连接错误,打印错误信息。

在建立了这个WebSocket配置类之后,你可以通过诸如定时任务或者接口访问的各种方式在特定时刻下向客户端发送消息。

2.3.3 举例:

以常见的在饮品店下单点奶茶为例.

1.当用户下单成功的一刻,前端向奶茶店的后端服务器发送请求,获取相关的订单信息,然后访问建立websocket的接口(也就是上文的/websocket/{userId}),建立与后端的websocket连接.后端储存与这个userid相关的订单信息.

2.当奶茶制作完成时,工作人员在工作台上点击完成,相当于访问了一个完成接口,并传入完成的订单信息,然后后端这个接口内根据这个信息找到相对应的用户id,然后调用上面的发送单点消息的方法(sendOneMessage),向前端发送了一条订单完成的消息.

3.此时前端收到消息后展示相应的画面,用户就能看到自己手机上的点单页面发生了变化,变成了已完成.

4.然后客户端调用带有@OnClose注解的方法,关闭连接,一次完整的websocket通信就这么结束了.