当前位置: 首页 > news >正文

免费手机h5模板网站模板下载网站标题有图片要怎么做

免费手机h5模板网站模板下载,网站标题有图片要怎么做,国外网站建设 网站,推广网站发布文章#x1f3af; 本文档详细介绍了如何使用WebSocket协议优化客户端与服务端之间的通信#xff0c;特别是在处理异步订单创建通知的场景中。通过引入WebSocket代替传统的HTTP请求-响应模式#xff0c;实现了服务器主动向客户端推送数据的功能#xff0c;极大地提高了实时性和效… 本文档详细介绍了如何使用WebSocket协议优化客户端与服务端之间的通信特别是在处理异步订单创建通知的场景中。通过引入WebSocket代替传统的HTTP请求-响应模式实现了服务器主动向客户端推送数据的功能极大地提高了实时性和效率。文中首先概述了WebSocket的优势随后深入探讨了其在分布式系统中的具体实现包括依赖管理、网关配置、WebSocket服务类的设计以及消息队列的使用等关键环节。特别地针对分布式架构下WebSocket连接状态同步问题提出了一种基于消息队列广播机制的解决方案确保了系统的可扩展性和稳定性。同时还强调了心跳检测机制的重要性以维护连接的有效性。 ️ HelloDam/场快订场馆预定 SaaS 平台 文章目录 前言WebSocket 介绍流程图具体实现依赖网关配置WebSocket配置类WebSocket服务类MQ消费者启动类配置文件 注意事项登录验证WebSocket 配置类token校验 分布式 WebSocket心跳检测 前言 在时间段预定接口 V2 中用户预定之后会发送一个消息让消息队列异步创建订单。此时客户端是无法知道服务端什么时候完成订单创建的因此需要服务端告知客户端。但是以往都是客户端给服务端发 http 请求但是服务端如何主动告知客户端呢 这个时候就需要请出我们今天的主角 WebSocket 了 WebSocket 介绍 WebSocket是一种在单个TCP连接上进行全双工通信的协议。它使得客户端和服务器之间的数据交换变得更加简单允许服务器直接向客户端推送数据而不必由客户端发起请求。这种特性让实时性要求较高的应用如即时通讯工具、在线游戏以及实时交易系统等能够更加高效地进行数据交互。通过WebSocket开发者可以构建响应更快、性能更高的网络应用同时减少不必要的网络开销和延迟。相比传统的HTTP请求-响应模式WebSocket提供了更低的延迟和更高的效率特别是在需要频繁更新数据的应用场景中表现出色。 因此使用了 WebSocket 一旦客户端和服务端建立了连接当订单创建成功之后服务端直接别订单数据推送给客户端即可。 流程图 user1、user2 和 user3 分别发起 WebSocket 连接首先经过网关连接请求被分发到不同的服务中。WebSocket 服务接收到连接请求之后对其进行登录校验如果校验成功将其 Session 信息存储在服务器的内存中如果校验失败直接关闭 Session 。其中 user1、user2 的Session信息被存储在 WebSocket 服务1 中user3 的Session信息被存储在 WebSocket 服务2 中。 当用户预定时间段生成订单之后场馆服务向消息队列中发生订单数据。接着消息队列将订单数据广播到 WebSocket 服务1 和 WebSocket 服务2中。WebSocket 服务2 发现自己的内存中存有 user3 的Session因此将订单数据通过该 Session 发送给 user3 。 暂时无法在飞书文档外展示此内容 具体实现 为了解耦 WebSocket 和其他服务单独创建一个 WebSocket 服务。 依赖 dependenciesdependencygroupIdcom.vrs/groupIdartifactIdvrs-web/artifactIdversion1.0-SNAPSHOT/version/dependencydependencygroupIdorg.dam/groupIdartifactIdvrs-rocketmq/artifactIdversion1.0-SNAPSHOT/version/dependencydependencygroupIdcom.vrs/groupIdartifactIdvrs-common/artifactId/dependencydependencygroupIdcom.vrs/groupIdartifactIdvrs-idempotent/artifactIdversion1.0-SNAPSHOT/version/dependencydependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependency!-- websocket --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-websocket/artifactId/dependency /dependencies网关配置 当访问 /websocket/** 路径时将请求转化到 WebSocket 服务注意转发的时候添加了前缀ws: - id: vrs-websocketuri: lb:ws://vrs-websocketpredicates:- Path/websocket/**filters:- name: TokenValidateargs:whitePathList:- /websocket/**【去除默认过滤器】 如果像这样全局配置了默认过滤器DedupeResponseHeader过滤器的作用是对指定的响应头在这个例子中为Vary、Access-Control-Allow-Origin和Access-Control-Allow-Credentials进行去重。当有多个相同名称的响应头时它会按照给定的策略保留其中的一个。这里的策略是RETAIN_FIRST意味着它将保留这些头部中第一次出现的那个而删除后续出现的重复头部。 spring:cloud:gateway:default-filters:- DedupeResponseHeaderVary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST发起 WebSocket 连接的时候会报如下错误这是因为修改了只读的请求头 java.lang.UnsupportedOperationException: nullat org.springframework.http.ReadOnlyHttpHeaders.set(ReadOnlyHttpHeaders.java:108) ~[spring-web-6.0.9.jar:6.0.9]Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s):*__checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]*__checkpoint ⇢ HTTP GET /websocket/admin?tokeneyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAA_6tWKi5NUrJScgwN8dANDXYNUtJRSq0oULIyNDe2NDMyNrYw0lEqLU4t8kwBilmYmZgZm5sbG5mbGViYGpgYQyX9EnNTgYYkpuRm5ilBhEIqC4BCRrUAvgeVqmEAAAA.e7wanr0gKu4FD-Y_afO2MEIECxZ6oMKGlf8zarZp-GOmzqL5n354gasKr7GKKs4H3Pq0CYJQECO_Rv9ixGsvZQ [ExceptionHandlingWebHandler] Original Stack Trace:at org.springframework.http.ReadOnlyHttpHeaders.set(ReadOnlyHttpHeaders.java:108) ~[spring-web-6.0.9.jar:6.0.9]因此需要将上述配置删除如果还需要这些默认配置可以到具体的路由下面设置就像下面一样 spring:cloud:gateway:routes:- id: vrs-adminuri: lb://vrs-adminpredicates:- Path/admin/**filters:- DedupeResponseHeaderVary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST- name: TokenValidateargs:whitePathList:- /admin/user/v1/login- /admin/user/v1/wechatLogin- ...WebSocket配置类 配置类 WebSocketConfig 主要用于配置和初始化 WebSocket 服务器端点并处理与 WebSocket 连接相关的操作具体功能如下 Spring Bean 注册通过 Configuration 注解标明这是一个 Spring 配置类。在该类中定义了一个 Bean 方法 serverEndpointExporter()它返回一个 ServerEndpointExporter 实例。这个实例的作用是自动注册使用了 ServerEndpoint 注解声明的 WebSocket 端点对象到 Spring 容器中。握手请求修改modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) 方法重写了父类中的同名方法用于在建立 WebSocket 连接前对握手请求进行自定义修改。在这个例子中方法尝试从握手请求参数中获取名为 “token” 的参数并将其存储在 ServerEndpointConfig 对象的用户属性中即 sec.getUserProperties().put(token, token);。这使得后续逻辑可以通过访问端点配置对象来获取令牌信息。端点实例化getEndpointInstance(ClassT clazz) 方法重写了父类的方法用于提供自定义逻辑来实例化被 ServerEndpoint 标注的 WebSocket 端点类。在这个实现中它直接调用了父类的实现 super.getEndpointInstance(clazz) 来创建端点实例。通常情况下除非需要特别的实例化逻辑否则可以直接使用父类的默认实现。 package com.vrs.config;import jakarta.websocket.HandshakeResponse; import jakarta.websocket.server.HandshakeRequest; import jakarta.websocket.server.ServerEndpointConfig; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter;import java.util.List; import java.util.Map;/*** Author dam* create 2025/1/24 15:25*/ Configuration public class WebSocketConfig extends ServerEndpointConfig.Configurator {/*** 这个bean会自动注册使用了ServerEndpoint注解声明的对象** return*/Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}/*** 建立握手时连接前的操作*/Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {// 获取请求参数MapString, ListString parameterMap request.getParameterMap();ListString tokenList parameterMap.get(token);if (tokenList ! null !tokenList.isEmpty()) {String token tokenList.get(0);sec.getUserProperties().put(token, token);}}/*** 初始化端点对象,也就是被ServerEndpoint所标注的对象*/Overridepublic T T getEndpointInstance(ClassT clazz) throws InstantiationException {return super.getEndpointInstance(clazz);} }WebSocket服务类 WebSocketServer 类是为实现实时通信而设计的能够有效地管理多个客户端之间的双向通信以及保持这些通信的稳定性和可靠性。它通过 Spring 的 Component 和 Jakarta WebSocket 的 ServerEndpoint 注解被注册为一个 Spring Bean并监听路径为 /websocket/{username} 的 WebSocket 请求。该类利用一个静态的 ConcurrentHashMap 来存储每个用户的会话 (Session) 和最后一次活动时间以跟踪在线用户和他们的活跃状态。它实现了以下关键功能 连接管理处理用户的连接建立 (onOpen) 和关闭 (onClose) 事件包括校验用户提供的 token 是否有效。消息处理接收来自客户端的消息 (onMessage) 并据此更新用户的最后活动时间支持发送 PING/PONG 心跳消息来维持连接。心跳检测通过定时任务每30秒检查一次用户的心跳若某用户超过60秒未活动则自动断开其连接确保资源的有效利用。消息发送提供了一个方法用于向特定用户发送消息。 package com.vrs.controller;import com.vrs.config.WebSocketConfig; import com.vrs.constant.RedisCacheConstant; import com.vrs.utils.JwtUtil; import jakarta.websocket.*; import jakarta.websocket.server.PathParam; import jakarta.websocket.server.ServerEndpoint; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils;import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit;/*** Author dam* create 2024/1/24 14:32*/ // 将WebSocketServer注册为spring的一个bean ServerEndpoint(value /websocket/{username}, configurator WebSocketConfig.class) Component Slf4j(topic WebSocketServer) public class WebSocketServer {/*** 心跳检查间隔时间单位秒*/private static final int HEARTBEAT_INTERVAL 30;/*** 心跳超时时间单位秒*/private static final int HEARTBEAT_TIMEOUT 60;/*** 记录当前在线连接的客户端的session*/private static final MapString, Session usernameAndSessionMap new ConcurrentHashMap();/*** 记录用户最后一次活动时间*/private static final MapString, Long lastActivityTimeMap new ConcurrentHashMap();/*** 直接通过 Autowired 注入的话redisTemplate为null因此使用这种引入方式*/private static StringRedisTemplate redisTemplate;Autowiredpublic void setRabbitTemplate(StringRedisTemplate redisTemplate) {WebSocketServer.redisTemplate redisTemplate;}/*** 定时任务线程池用于心跳检查*/private static final ScheduledExecutorService scheduler Executors.newScheduledThreadPool(1);// 初始化心跳检查任务static {scheduler.scheduleAtFixedRate(WebSocketServer::checkHeartbeat, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL, TimeUnit.SECONDS);}/*** 浏览器和服务端连接建立成功之后会调用这个方法*/OnOpenpublic void onOpen(Session session, PathParam(username) String username, EndpointConfig config) {// 校验 token 是否有效String token (String) config.getUserProperties().get(token);boolean validToken validToken(token);if (!validToken) {try {session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, 无效的token请先登录));} catch (IOException e) {e.printStackTrace();}}// 如果用户已存在关闭旧连接if (usernameAndSessionMap.containsKey(username)) {Session oldSession usernameAndSessionMap.get(username);if (oldSession ! null oldSession.isOpen()) {try {oldSession.close();} catch (IOException e) {log.error(关闭旧连接时发生错误, e);}}}// 记录新连接usernameAndSessionMap.put(username, session);// 记录用户活动时间lastActivityTimeMap.put(username, System.currentTimeMillis());log.info(有新用户加入username{}, 当前在线人数为{}, username, usernameAndSessionMap.size());}/*** 连接关闭调用的方法*/OnClosepublic void onClose(Session session, PathParam(username) String username) throws IOException {try {if (session ! null session.isOpen()) {session.close();}} catch (IOException e) {log.error(关闭连接时发生错误, e);} finally {usernameAndSessionMap.remove(username);lastActivityTimeMap.remove(username);log.info(有一连接关闭移除username{}的用户session, 当前在线人数为{}, username, usernameAndSessionMap.size());}}/*** 发生错误的时候会调用这个方法*/OnErrorpublic void onError(Session session, Throwable error) {log.error(发生错误原因 error.getMessage());error.printStackTrace();}/*** 收到客户端消息时调用*/OnMessagepublic void onMessage(String message, Session session, PathParam(username) String username) {// 更新用户最后一次活动时间lastActivityTimeMap.put(username, System.currentTimeMillis());if (PING.equals(message)) {log.debug(收到来自 {} 的心跳检测请求, username);} else {log.info(收到来自 {} 的消息: {}, username, message);}}/*** 服务端发送消息给客户端*/public void sendMessage(String toUsername, String message) {try {Session toSession usernameAndSessionMap.get(toUsername);if (toSession ! null toSession.isOpen()) {toSession.getBasicRemote().sendText(message);} else {log.warn(用户 {} 的会话已关闭或不存在, toUsername);}} catch (Exception e) {log.error(服务端发送消息给客户端失败, e);}}/*** 关闭心跳检测超时的 session*/private static void checkHeartbeat() {long currentTime System.currentTimeMillis();for (Map.EntryString, Long entry : lastActivityTimeMap.entrySet()) {String username entry.getKey();long lastActivityTime entry.getValue();if (currentTime - lastActivityTime HEARTBEAT_TIMEOUT * 1000) {log.info(用户 {} 心跳超时关闭连接, username);Session session usernameAndSessionMap.get(username);if (session ! null) {try {session.close();} catch (IOException e) {log.error(关闭连接时发生错误, e);}}usernameAndSessionMap.remove(username);lastActivityTimeMap.remove(username);}}}/*** 校验 token 有效** param token* return*/private boolean validToken(String token) {String userName ;try {// 如果从 token 中解析用户名错误说明 token 是捏造的或者已经失效userName JwtUtil.getUsername(token);} catch (Exception e) {return false;}if (StringUtils.hasText(userName) StringUtils.hasText(token) (redisTemplate.opsForHash().get(RedisCacheConstant.USER_LOGIN_KEY userName, token)) ! null) {// --if-- 如果可以通过 token 从 Redis 中获取到用户的登录信息说明通过校验return true;}return false;}}MQ消费者 package com.vrs.rocketMq.listener;import com.vrs.constant.RocketMqConstant; import com.vrs.controller.WebSocketServer; import com.vrs.domain.dto.mq.WebsocketMqDTO; import com.vrs.templateMethod.MessageWrapper; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.spring.annotation.MessageModel; import org.apache.rocketmq.spring.annotation.RocketMQMessageListener; import org.apache.rocketmq.spring.annotation.SelectorType; import org.apache.rocketmq.spring.core.RocketMQListener; import org.springframework.stereotype.Component;/*** 执行预订流程 消费者** Author dam* create 2024/9/20 21:30*/ Slf4j(topic RocketMqConstant.VENUE_TOPIC) Component RocketMQMessageListener(topic RocketMqConstant.VENUE_TOPIC,consumerGroup RocketMqConstant.VENUE_CONSUMER_GROUP - RocketMqConstant.WEBSOCKET_SEND_MESSAGE_TAG,// 需要使用广播模式messageModel MessageModel.BROADCASTING,// 监听tagselectorType SelectorType.TAG,selectorExpression RocketMqConstant.WEBSOCKET_SEND_MESSAGE_TAG ) RequiredArgsConstructor public class WebSocketSendMessageListener implements RocketMQListenerMessageWrapperWebsocketMqDTO {private final WebSocketServer webSocketServer;/*** 消费消息的方法* 方法报错就会拒收消息** param messageWrapper 消息内容类型和上面的泛型一致。如果泛型指定了固定的类型消息体就是我们的参数*/SneakyThrowsOverridepublic void onMessage(MessageWrapperWebsocketMqDTO messageWrapper) {// 开头打印日志平常可 Debug 看任务参数线上可报平安比如消息是否消费重新投递时获取参数等log.info([消费者] websocket发生消息给{}, messageWrapper.getMessage().getToUsername());webSocketServer.sendMessage(messageWrapper.getMessage().getToUsername(), messageWrapper.getMessage().getMessage());} }启动类 package com.vrs;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;/*** Author dam* create 2025/01/24 16:34*/ SpringBootApplication(exclude {DataSourceAutoConfiguration.class}) public class VrsWebSocketApplication {public static void main(String[] args) {SpringApplication.run(VrsWebSocketApplication.class, args);} }配置文件 server:port: 7054 spring:profiles:active: damapplication:name: vrs-websocketcloud:nacos:discovery:server-addr: 127.0.0.1:8848data:redis:host: 127.0.0.1port: 6379password: 12345678database: 0timeout: 1800000jedis:pool:max-active: 20 #最大连接数max-wait: -1 #最大阻塞等待时间(负数表示没限制)max-idle: 5 #最大空闲min-idle: 0 #最小空闲 rocketmq:# rocketMq的nameServer地址name-server: 127.0.0.1:9876producer:# 生产者组别group: vrs-websocket-group# 消息发送的超时时间send-message-timeout: 10000# 异步消息发送失败重试次数retry-times-when-send-async-failed: 1# 发送消息的最大大小单位字节这里等于4Mmax-message-size: 999999999注意事项 登录验证 为了防止被人恶意发生大量 WebSocket 连接占用服务器资源因此在建立连接的时候需要进行登录验证用户登录了才可以建立 WebSocket 连接。 由于建立 WebSocket 连接时无法像之前的 http 请求一样在请求头携带 token 信息因此之前网关实现的登录校验机制不生效需要我们针对 WebSocket 连接额外实现一套登录验证方式。 假设前端发起 WebSocket 连接的代码如下 new WebSocket(ws://localhost:7049/websocket/admin?tokendahidaho);WebSocket 配置类 在modifyHandshake中将客户端发起连接请求时的 token 设置到属性中这样后面就可以将 token 获取出来进行校验如果说校验不通过就关闭 WebSokcet 连接 token校验 代码位于WebSocketServer类中当调用validToken校验失败之后通过session.close来关闭连接 /*** 校验 token 有效** param token* return*/ private boolean validToken(String token) {String userName ;try {// 如果从 token 中解析用户名错误说明 token 是捏造的或者已经失效userName JwtUtil.getUsername(token);} catch (Exception e) {return false;}if (StringUtils.hasText(userName) StringUtils.hasText(token) (redisTemplate.opsForHash().get(RedisCacheConstant.USER_LOGIN_KEY userName, token)) ! null) {// --if-- 如果可以通过 token 从 Redis 中获取到用户的登录信息说明通过校验return true;}return false; }/*** 浏览器和服务端连接建立成功之后会调用这个方法*/ OnOpen public void onOpen(Session session, PathParam(username) String username, EndpointConfig config) {// 校验 token 是否有效String token (String) config.getUserProperties().get(token);boolean validToken validToken(token);if (!validToken) {try {session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, 无效的token请先登录));} catch (IOException e) {e.printStackTrace();}}// 如果用户已存在关闭旧连接if (usernameAndSessionMap.containsKey(username)) {Session oldSession usernameAndSessionMap.get(username);if (oldSession ! null oldSession.isOpen()) {try {oldSession.close();} catch (IOException e) {log.error(关闭旧连接时发生错误, e);}}}// 记录新连接usernameAndSessionMap.put(username, session);// 记录用户活动时间lastActivityTimeMap.put(username, System.currentTimeMillis());log.info(有新用户加入username{}, 当前在线人数为{}, username, usernameAndSessionMap.size()); }分布式 WebSocket 由于我们的项目是分布式架构的如果vrs-websocket启动多个服务的话需要处理如下问题 WebSocketServer中的用户名及其对应的session信息usernameAndSessionMap是存储在本地的假设发起连接的时候session被存储在机器 1 上面。后续服务端要通知客户端时怎么知道当前用户的信息是存储在机器1、机器 2 还是机器 3 呢 由于 Session 无法直接序列化存储到 Redis 中为了解决这个问题本文通过借助消息队列来解决。 服务端要发送消息给客户端时先将消息发送至消息队列中消息设置为广播模式。后续多台部署了vrs-websocket的机器去消息队列中获取消息来消费如果机器检查到了这条消息的接收者 session 就在机器上则执行发送否则直接 return 即可。 【消息生产者】 package com.vrs.rocketMq.producer;import cn.hutool.core.util.StrUtil; import com.vrs.constant.RocketMqConstant; import com.vrs.domain.dto.mq.WebsocketMqDTO; import com.vrs.templateMethod.AbstractCommonSendProduceTemplate; import com.vrs.templateMethod.BaseSendExtendDTO; import com.vrs.templateMethod.MessageWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.rocketmq.common.message.MessageConst; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; import org.springframework.stereotype.Component;import java.util.UUID;/*** websocket发送消息 生产者** Author dam* create 2024/9/20 16:00*/ Slf4j Component public class WebsocketSendMessageProducer extends AbstractCommonSendProduceTemplateWebsocketMqDTO {Overrideprotected BaseSendExtendDTO buildBaseSendExtendParam(WebsocketMqDTO messageSendEvent) {return BaseSendExtendDTO.builder().eventName(执行时间段预定).topic(RocketMqConstant.VENUE_TOPIC).tag(RocketMqConstant.WEBSOCKET_SEND_MESSAGE_TAG).sentTimeout(2000L).build();}Overrideprotected Message? buildMessage(WebsocketMqDTO messageSendEvent, BaseSendExtendDTO requestParam) {String keys StrUtil.isEmpty(requestParam.getKeys()) ? UUID.randomUUID().toString() : requestParam.getKeys();return MessageBuilder.withPayload(new MessageWrapper(keys, messageSendEvent)).setHeader(MessageConst.PROPERTY_KEYS, keys).setHeader(MessageConst.PROPERTY_TAGS, requestParam.getTag()).build();} }【消息消费者】 消费者的代码就在具体实现中这里不重复放 【使用】 // 通过 websocket 发送消息通知前端 websocketSendMessageProducer.sendMessage(WebsocketMqDTO.builder().toUsername(orderDO.getUserName()).message(JSON.toJSONString(orderDO)).build());心跳检测 用户建立 WebSocket 连接之后的 session 数据是存储在服务器本地的随着连接数量的增加session会占用大量的内存心跳检测是为了定期清理那些无效的连接。 在WebSocketServer中通过定时任务每30秒检查一次客户端的心跳状态记录每个用户的最后活动时间。如果当前时间与某用户最后活动时间之差超过60秒则认为该用户心跳超时服务端将关闭其WebSocket连接并清理相关记录。客户端需定期向服务端发送PING消息以维持连接活跃确保不会因超时而被服务端断开。
http://www.dnsts.com.cn/news/17469.html

相关文章:

  • 手机网站建设的背景自贸区注册公司有什么优势
  • 专业建设英文网站微信公众号做视频网站吗
  • 做设计找图片的网站有哪些wordpress 安装平台
  • 重庆电力建设公司网站企业网络构建
  • 江苏seo推广seo网上培训多少钱
  • 网站psd下载word可以制作网页
  • 货源网站网店设计教程
  • 备案号 不放在网站首页免费 网站
  • 广州网站设计服务商东莞常平招聘网最新招聘信息
  • 做网站背景企业网络安全管理制度和应急预案
  • 检查网站打开速度怎么自己做网站地图
  • 官方网站下载cad后台很慢wordpress
  • 新乡微网站建设百度指数明星人气榜
  • 虹口免费网站制作注册新公司流程
  • nas做流媒体网站网站搜索功能模块
  • 婚纱摄影网站的设计与实现论文河南智慧团建网站登录
  • joomla做类似赶集网的网站陕西营销型手机网站
  • 网站设计规划书怎么写公司网站的具体步骤
  • 温州建设局网站wordpress+adsense主题
  • 山东建设和城乡建设厅注册中心网站首页dedecms网站如何上线
  • 做的网站缩小内容就全乱了网页制作基础考什么
  • 自己做网站模版做网站应该学什么语言
  • 网站建设 鼠标商务网站如何推广
  • 南充市住房建设局网站青岛新公司网站建设推广
  • 企业网站怎么注册官网十堰网站建设专家
  • 网站开发后端有哪些网页版淘宝网登录入口
  • 网站开发多语言成都网站关键词优化
  • 无锡网站制作网站哪家好
  • 网站备案能查到什么建设网站用英文怎么说
  • 南京做网站建设有哪些网站建设内部需求调查表