WebSocket前后端交互

741 阅读4分钟

websocket的原理

websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接 websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"

websocket的心跳机制和重连机制

心跳机制:客户端每隔一段时间向服务端发送一个特有的心跳消息,每次服务端收到消息后只需将消息返回,此时,若二者还保持连接,则客户端就会收到消息,若没收到,则说明连接断开,此时,客户端就要主动重连,完成一个周期

断线重连:若某时间段内客户端发送了消息,而服务端未返回,则认定为断线;这个时候会触发到websocket中的onclose事件,需要重新连接服务

实现步骤

后端实现

后端实现WebSocket通信通常涉及以下几个步骤:

  • 选择框架或库:根据所使用的后端技术栈,选择合适的WebSocket库。例如,对于Node.js,可以使用wssocket.io;对于Java,可以使用Spring Framework的WebSocket支持。
  • 建立连接:服务器需要监听客户端的连接请求。在客户端发起WebSocket连接时,服务器接受连接并建立一个WebSocket会话。
  • 处理消息:服务器需要能够接收客户端发送的消息,并根据需要对其进行处理。这可能包括解析消息内容、更新服务器状态、向一个或多个客户端广播消息等。
  • 维护连接:WebSocket连接一旦建立,就会保持开放,直到客户端或服务器决定关闭连接。服务器需要能够处理连接关闭的请求,并在必要时主动关闭连接。

客户端实现

客户端实现WebSocket通信也需要几个步骤:

  • 建立连接:客户端通过创建一个指向服务器的WebSocket URL来初始化WebSocket连接。
  • 发送和接收消息:一旦连接建立,客户端就可以发送消息给服务器,并监听从服务器接收到的消息。
  • 关闭连接:当不再需要WebSocket连接时,客户端应该关闭连接,以释放资源。

注意事项

  • 安全:使用WebSocket Secure(wss://),而不是不加密的ws://协议,以确保数据传输的安全。
  • 心跳机制:由于某些网络环境或中间件可能会关闭空闲连接,因此实现心跳机制(定期发送小数据包)以保持连接活跃是很有必要的。
  • 错误处理:合理处理网络波动或其他导致的连接中断问题,包括自动重连机制的实现。
  • 性能考虑:对于高并发的应用,需要考虑服务器的负载能力,可能需要采取负载均衡等措施。

【后端代码实现】

  1. 添加依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  1. 配置【WebSoketConfig
@Configuration  
@EnableWebSocket  
@EnableWebSocketMessageBroker  
public class WebSocketConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<Session> {  
    /**  
    * 设置WebSocket请求的路径后缀,withSockJS(),代表支持sockjs  
    * @param registry  
    */  
    @Override  
    protected void configureStompEndpoints(StompEndpointRegistry registry) { //registerStompEndpoints(StompEndpointRegistry registry) {  
        registry.addEndpoint("/ws").setAllowedOriginPatterns("*").addInterceptors(new HttpSessionHandshakeInterceptor()).withSockJS();  
    }  


    @Override  
    public void configureMessageBroker(MessageBrokerRegistry registry) {  
        //客户端与服务端交互的前缀  
        registry.setApplicationDestinationPrefixes("/app");  
        //客户端订阅消息的前缀  
        registry.enableSimpleBroker("/topic", "/queue");  
        //用户级别的消息订阅的前缀,点对点时使用  
        registry.setUserDestinationPrefix("/user");  
    }  

    @Override  
    public void configureClientInboundChannel(ChannelRegistration registration) {  
        super.configureClientInboundChannel(registration);  
        registration.interceptors(getTopicSubscriptionInterceptor());  
    }  

    /**  
    * 这是用于配置服务器向浏览器发送的消息。  
    * clientOut就表示出出口。还有一个inBoundChannel用于处理浏览器向服务器发送的消息  
    * https://blog.csdn.net/cw_hello1/article/details/80929206  
    * Spring Boot+STOMP解决消息乱序问题  
    * websocket发送消息采用的是多线程发送消息的,造成有的先到的消息在一个线程中在阻塞,而后到的消息使用其他的线程发送出去了。  
    * 两个消息发送的时间差不多就造成了乱序  
    *  
    * @param registration  
    */  
    @Override  
    public void configureClientOutboundChannel(ChannelRegistration registration) {  
        registration.taskExecutor().corePoolSize(1).maxPoolSize(1);  
    }  

    @Bean  
    public TopicSubscriptionInterceptor getTopicSubscriptionInterceptor() {  
        return new TopicSubscriptionInterceptor();  
    }  
}
  1. 定义Websocket主业务类
@Slf4j  
@Controller  
public class ProjectMeterRealTimeDataWebSocketController {  
  
    @Autowired  
    private SimpMessagingTemplate simpMessagingTemplate;  

    /**  
    * 处理前台通过websocket的SEND发送的获取企业每种能源当前负载  
    * /app/projectMeterRealTimeData/{projectId}/{meterId}  
    *  
    * @param projectId  
    * @param meterId  
    */  
    @MessageMapping("/projectMeterRealTimeData/{projectId}/{meterId}")  
    public void currentLoad(@DestinationVariable String projectId, @DestinationVariable String meterId) {  

        try {  
            String stompTopic = ConstantUtil.STOMP_PROJECT_METER_REAL_TIME_VALUE_TOPIC  
            .replaceAll("\\{projectId\\}", projectId)  
            .replaceAll("\\{meterId\\}", meterId);  
            String data = "Test";  
            log.info("将消息发送至stomp:topic={}, message={}", stompTopic, data);  
            String s = JSONObject.toJSONString(data);  
            simpMessagingTemplate.convertAndSend(stompTopic, s);  
        } catch (MessagingException e) {  
            log.error("发送消息至stomp出现异常", e);  
        }  
    }  
}