WebSocket是一种基于HTTP的长链接技术。传统的HTTP协议是一种请求-响应模型,如果浏览器不发送请求,那么服务器无法主动给浏览器推送数据。如果需要定期或者不定期给浏览器推送数据,只能依靠浏览器的JavaScript定时轮询,效率很低且实时性不高。
WebSocket在HTTP协议的基础上做了一个简单的升级,即建立TCP连接后,浏览器发送请求时,附带几个头,表示客户端希望升级连接,变成长连接的WebSocket。
GET /chat HTTP/1.1 # /chat服务器上处理WebSocket连接的端点(Endpoint)地址
Host: www.example.com # 要访问的服务器主机名
Upgrade: websocket # Upgrade 头字段表示客户端想要将当前连接升级到其他协议
Connection: Upgrade # 把这个连接当做一次升级连接处理,不要按HTTP 请求关闭连接
服务器返回升级成功的响应
HTTP/1.1 101 Switching Protocols # 同意切换协议
Upgrade: websocket # 确认和回应websokect协议
Connection: Upgrade # 不要按HTTP 请求关闭连接
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
创建配置类
@Configuration
@ComponentScan
@EnableWebMvc
@EnableWebSocket // 启用WebSocket支持
public class AppConfig {
@Bean
WebSocketConfigurer createWebSocketConfigurer(
@Autowired ChatHandler chatHandler,
@Autowired ChatHandshakeInterceptor chatInterceptor){
return new WebSocketConfigurer() {
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 把URL与指定的WebSocketHandler关联,可关联多个:
registry
.addHandler(chatHandler, "/chat")
.addInterceptors(chatInterceptor);
}
};
}
}
当浏览器请求一个WebSocket连接后,如果成功建立连接,Spring会自动调用afterConnectionEstablished()方法,任何原因导致WebSocket连接中断时,Spring会自动调用afterConnectionClosed方法,
@Component
public class ChatHandler extends TextWebSocketHandler {
// 保存所有Client的WebSocket会话实例:
private Map<String, WebSocketSession> clients = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 新会话根据ID放入Map:
clients.put(session.getId(), session);
session.getAttributes().put("name", "Guest1");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
clients.remove(session.getId());
}
}
创建拦截器
@Component
public class ChatHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
public ChatHandshakeInterceptor() {
// 指定从HttpSession复制属性到WebSocketSession:
// 这样,在ChatHandler中,可以从WebSocketSession.getAttributes()中获取到复制过来的属性。
super(List.of(UserController.KEY_USER));
}
}