WebSocket笔记

WebSocket笔记

介绍

使用场景

常用用于网络通讯,比如微信、QQ

对比之前的

使用 Http 请求响应式需要主动发起请求
Http是基于TCP协议,每一个Http都会创建一个TCP连接

问题

1、网络通讯,如何保证连接请求的用户
2、每一次信息都需要一个请求

解决方法

使用 WebSocket 协议,而 WebSocket 分成 Clinet 和 Server,WebSocket 协议是靠 Web容器 实现的。
WebSocket 的第一次是基于Http协议发起请求,而Server 接收到请求后升级成ws(TCP)的连接,会在 http 的请求头中带有 Upgrade:websocket

配置

Maven 依赖

原生 Servlet 配置

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
</dependency>

整合 Spring

Spring WebSocket在Spring4.0版本开始支持的

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.3.23</version>
</dependency>

原生 Servlet 使用注解

@ServerEndpoint("/connect")
表示当前类是 服务端(WebSocket) 端点
@ServerEndpoint(value = "/connect", configurator = WebSocketHandshake.class)

属性介绍

value:指定一个连接服务端的url地址
configurator:指定握手处理类

握手处理类

Servlet的session和webSocket的session没有关联,但是第一次请求是基于http协议进行握手,所以可以写一个握手处理类用于在第一次连接时获取 HttpSession。
将 HttpSession 中的特定信息保存到 WebSocket 的 session 中。

/**
 * 第一次连接时获取HttpServlet的Session
 */
public class WebSocketHandshake extends Configurator {
    /**
     * 重写握手处理方法
     *
     * @param sec      the configuration object involved in the handshake
     * @param request  请求对象
     * @param response 响应对象
     */
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        // 获取 HttpSession
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        // 将用户名保存到webSocket的session中
        sec.getUserProperties().put("user", httpSession.getAttribute("user"));
    }
}

Spring 整合

需要Socket Server 继承 TextWebSocketHandler
基于 Servlet 实现 WebSocket 会话共享,也对 Socket 的 Session 进行封装成 WebSocketSession

WebSocket配置类

@Configuration
// 启动 WebSocket
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    /**
    * 装配服务端
    */
    @Bean
    public WebSocketHandler webSocketHandler() {
        return new ChatServer();
    }

    /**
     * 装配 HttpSession 的拦截器,这样就可以在握手阶段获取 HttpSession 的内容,在使用 webSocketSession 时,就能直接得到 httpSession 的数据
     * @return
     */
    public HandshakeInterceptor handshakeInterceptor(){
        return new HttpSessionHandshakeInterceptor();
    }

    /**
     * 给服务端注册请求的端点(映射连接地址)
     *
     * @param registry
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 给特定服务端绑定端点(入口)。注册端点,设置连接的url
        registry.addHandler(webSocketHandler(),"/connect")
        // 设置握手拦截器
                .addInterceptors(handshakeInterceptor());
    }
}

生命周期

原生 Servlet 使用注解

都可以接收 webSocket的session

开启

和服务端连接的方法,只有第一次使用Http连接时使用
@OnOpen
标注服务端连接的方法,只有第一次使用Http连接时调用

@OnOpen
public void onOpen(Session session) {
    // 方法体
}

接收消息

@OnMessage
标注接收客户端信息的信息方法,被标注的方法可以使用 String 和 byte[] 接收数据

@OnMessage
public void OnMessage(String messageString, Session sessionMessage) {
    // 方法体
}

关闭

@OnClose
断开连接时调用

@OnClose
public void onClose(Session session) {
    // 方法体
}

使用 Spring 整合

/**
 * Spring 封装的 WebSocket 服务端,可以继承一个 TextWebSocketHandler,表示这个服务端用于处理文本数据的消息
 */
public class ChatServer extends TextWebSocketHandler {
    /**
     * 用户列表
     * 每一次连接都会创建一个 WebSocketSession(Spring对Socket的Session封装) 对象
     */
    private static final Map<String, WebSocketSession> users = new HashMap<>();

    /**
     * 建立连接后,对应 OnOpen
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 获取登录的用户信息
        String userName = (String) session.getAttributes().get("user");
        users.put(userName,session);
    }

    /**
     * 接收客户端消息,对应 OnMessage
     * @param session
     * @param messageText
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage messageText) throws Exception {
        Message message = new Message();
        message.setFromUser((String) session.getAttributes().get("user"));
        message.setSendTime(new SimpleDateFormat("hh:mm").format(new Date()));
        message.setContent(messageText.getPayload());
        for (WebSocketSession socketSession:users.values()){
            // 必须是 TextMessage 的对象
            socketSession.sendMessage(new TextMessage(JSONUtil.toJsonStr(message)));
        }
    }

    /**
     * 关闭连接后,对应 OnClose
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        users.remove(session.getAttributes().get("user"));
    }
}

前端案例

let ws = new WebSocket("ws://localhost:8080/connect");

构建 webSocket 实例,连接后台 Server 的请求地址
每一个客户端就会创建一个 Socket 会话

TCP介绍