WebSocket-学习调研
一、什么是WebSocket
WebSocket是一种基于单个TCP的全双工通信协议,它为客户端和服务端之间提供了一种双向的,高效的通信手段。WebSocket在第一次TCP握手后,会建立一个长连接,客户端和服务端可以通过这个长连接实现实时的双向通信。WebSocket协议最初由W3C开发,并于2011年成为标准。
二、WebSocket的优缺点
1、优点
实时性:WebSocket在第一次TCP连接后,会建立一个长连接,客户端和服务端之间可以通过这个长连接直接传输数据,避免了像Http多次请求的消耗,WebSocket是有状态的协议,而Http是无状态的协议。
双向性:WebSocket协议中,允许服务端主动像客户端发送消息,可以避免客户端请求才能发送数据,提高效率。
减少网络负载:由于WebSocket的长连接,可以避免很多Http请求的开销,从而减少负载。
无同源限制:WebSocket相比较Http没有同源限制,因为WebSocket是用于实时通信,浏览器不会阻止其跨域连接建立,但是会发送Origin头部。
与 HTTP 协议有着良好的兼容性:默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
2、缺点
(1)支持程度:由于WebSocket是2011年在兴起的新协议,部分老的浏览器和服务器可能不支持WebSocket协议。
(2)额外开销:WebSocket协议建立后会维护一个长连接,这个长连接需要额外的CPU、内存等开销
(3)安全问题:WebSocket是以实时性优先,像一些跨域连接的安全问题等,都需要应用层自主实现。
三、WebSocket的生命周期
1、连接建立阶段
在这个阶段,WebSocket会建立起连接,有客户端发送一个握手请求,服务端响应一个握手响应。响应之后,两者之间就会建立起长连接。
2、连接开放阶段
在这个阶段客户端和服务端之间已经建立起了连接,可以进行双向通信,客户端发送消息给服务端,服务端发送消息给客户端。
3、连接关闭阶段
此时,双方通信结束,有其中一方发送一个关闭帧,来关闭两者之间的连接,这个发送方可以是客户端也可以是服务端。
4、连接关闭完成阶段
这个时候,一方接受到另一方发送过来的关闭帧,也会关闭连接,通信双方都关闭了连接,WebSocket连接彻底关闭。

WebSocketsh生命周期
四、WebSocket应用场景
1、实时聊天:WebSocket能够提供双向、实时的通信机制,使得实时聊天应用能够快速、高效地发送和接收消息,实现即时通信。
2、实时协作:用于实时协作工具,如协同编辑文档、白板绘画、团队任务管理等,团队成员可以实时地在同一页面上进行互动和实时更新。
3、实时数据推送:用于实时数据推送场景,如股票行情、新闻快讯、实时天气信息等,服务器可以实时将数据推送给客户端,确保数据的及时性和准确性。
4、多人在线游戏:实时的双向通信机制,适用于多人在线游戏应用,使得游戏服务器能够实时地将游戏状态和玩家行为传输给客户端,实现游戏的实时互动。
5、在线客服:WebSocket可以用于在线客服和客户支持系统,实现实时的客户沟通和问题解决,提供更好的用户体验,减少等待时间。
五、WebSocket简单使用(基于SpringBoot)
1、引入依赖

WebSocket依赖
2、编写配置类
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
WebSocket配置类,帮助SpringBoot扫描所有带有@ServerEndpoint注解的类
3、编写WebSocket服务代码
@Component
@Slf4j
@ServerEndpoint("/api/pushMessage/{userId}")
public class WebSocketServer {/**静态变量,用来记录当前在线连接数*/private static final AtomicInteger onlineCount = new AtomicInteger(0);/**concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。*/private static final ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();/**与某个客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/**接收userId*/private String userId;/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam("userId") String userId) {this.session = session;this.userId = userId;if (webSocketMap.containsKey(userId)) {webSocketMap.remove(userId);// 加入map中webSocketMap.put(userId, this);} else {// 加入map中webSocketMap.put(userId, this);// 在线数加1onlineCount.incrementAndGet();}log.info("用户连接:" + userId + ",当前在线人数为:" + onlineCount);sendMessage("连接成功");}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {if (webSocketMap.containsKey(userId)) {webSocketMap.remove(userId);// 在线人数减1onlineCount.decrementAndGet();}log.info("用户退出:" + userId + ",当前在线人数为:" + onlineCount);}/*** 收到客户端消息后调用的方法**/@OnMessagepublic void onMessage(String message, Session session) {log.info("用户消息:" + userId + ",报文:" + message);// 解析发送的报文JSONObject jsonObject = JSON.parseObject(message);// 获取需要转发的用户idString toUserId = jsonObject.getString("toUserId");// 传送给对应toUserId用户的websocketif (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {webSocketMap.get(toUserId).sendMessage(message);} else {log.error("请求的userId:" + toUserId + "不在该服务器上");}}/*** 发生异常调用方法*/@OnErrorpublic void onError(Session session, Throwable error) {log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());error.printStackTrace();}/*** 实现服务器主动推送*/public void sendMessage(String message) {this.session.getAsyncRemote().sendText(message);}/***发送自定义消息**/public static void sendInfo(String message, String userId) {log.info("发送消息到:" + userId + ",报文:" + message);if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {webSocketMap.get(userId).sendMessage(message);} else {log.error("用户" + userId + ",不在线!");}}
}
4、测试

用户一号发送数据给二号

用户二号收到数据

用户二号发送数据给用户一号
