一、什么是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、编写配置类
java
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocket配置类,帮助SpringBoot扫描所有带有@ServerEndpoint注解的类
3、编写WebSocket服务代码
java
@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;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public 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);
// 在线数加1
onlineCount.incrementAndGet();
}
log.info("用户连接:" + userId + ",当前在线人数为:" + onlineCount);
sendMessage("连接成功");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
// 在线人数减1
onlineCount.decrementAndGet();
}
log.info("用户退出:" + userId + ",当前在线人数为:" + onlineCount);
}
/**
* 收到客户端消息后调用的方法
**/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:" + userId + ",报文:" + message);
// 解析发送的报文
JSONObject jsonObject = JSON.parseObject(message);
// 获取需要转发的用户id
String toUserId = jsonObject.getString("toUserId");
// 传送给对应toUserId用户的websocket
if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
webSocketMap.get(toUserId).sendMessage(message);
} else {
log.error("请求的userId:" + toUserId + "不在该服务器上");
}
}
/**
* 发生异常调用方法
*/
@OnError
public 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、测试

用户一号发送数据给二号

用户二号收到数据

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