websocket的原理
websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接 websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"
websocket的心跳机制和重连机制
心跳机制:客户端每隔一段时间向服务端发送一个特有的心跳消息,每次服务端收到消息后只需将消息返回,此时,若二者还保持连接,则客户端就会收到消息,若没收到,则说明连接断开,此时,客户端就要主动重连,完成一个周期
断线重连:若某时间段内客户端发送了消息,而服务端未返回,则认定为断线;这个时候会触发到websocket中的onclose事件,需要重新连接服务
实现步骤
后端实现
后端实现WebSocket通信通常涉及以下几个步骤:
- 选择框架或库:根据所使用的后端技术栈,选择合适的WebSocket库。例如,对于Node.js,可以使用
ws或socket.io;对于Java,可以使用Spring Framework的WebSocket支持。 - 建立连接:服务器需要监听客户端的连接请求。在客户端发起WebSocket连接时,服务器接受连接并建立一个WebSocket会话。
- 处理消息:服务器需要能够接收客户端发送的消息,并根据需要对其进行处理。这可能包括解析消息内容、更新服务器状态、向一个或多个客户端广播消息等。
- 维护连接:WebSocket连接一旦建立,就会保持开放,直到客户端或服务器决定关闭连接。服务器需要能够处理连接关闭的请求,并在必要时主动关闭连接。
客户端实现
客户端实现WebSocket通信也需要几个步骤:
- 建立连接:客户端通过创建一个指向服务器的WebSocket URL来初始化WebSocket连接。
- 发送和接收消息:一旦连接建立,客户端就可以发送消息给服务器,并监听从服务器接收到的消息。
- 关闭连接:当不再需要WebSocket连接时,客户端应该关闭连接,以释放资源。
注意事项
- 安全:使用WebSocket Secure(wss://),而不是不加密的ws://协议,以确保数据传输的安全。
- 心跳机制:由于某些网络环境或中间件可能会关闭空闲连接,因此实现心跳机制(定期发送小数据包)以保持连接活跃是很有必要的。
- 错误处理:合理处理网络波动或其他导致的连接中断问题,包括自动重连机制的实现。
- 性能考虑:对于高并发的应用,需要考虑服务器的负载能力,可能需要采取负载均衡等措施。
【后端代码实现】
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 配置【
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();
}
}
- 定义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);
}
}
}