介绍
使用场景
常用用于网络通讯,比如微信、QQ
对比之前的
使用 Http 请求响应式需要主动发起请求
Http是基于TCP协议,每一个Http都会创建一个TCP连接
问题
1、网络通讯,如何保证连接请求的用户
2、每一次信息都需要一个请求
解决方法
使用 WebSocket 协议,而 WebSocket 分成 Clinet 和 Server,WebSocket 协议是靠 Web容器 实现的。
WebSocket 的第一次是基于Http协议发起请求,而Server 接收到请求后升级成ws(TCP)的连接,会在 http 的请求头中带有 Upgrade:websocket
配置
Maven 依赖
原生 Servlet 配置
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
整合 Spring
Spring WebSocket在Spring4.0版本开始支持的
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.3.23</version>
</dependency>
原生 Servlet 使用注解
@ServerEndpoint("/connect")
表示当前类是 服务端(WebSocket) 端点
@ServerEndpoint(value = "/connect", configurator = WebSocketHandshake.class)
属性介绍
value:指定一个连接服务端的url地址
configurator:指定握手处理类
握手处理类
Servlet的session和webSocket的session没有关联,但是第一次请求是基于http协议进行握手,所以可以写一个握手处理类用于在第一次连接时获取 HttpSession。
将 HttpSession 中的特定信息保存到 WebSocket 的 session 中。
/**
* 第一次连接时获取HttpServlet的Session
*/
public class WebSocketHandshake extends Configurator {
/**
* 重写握手处理方法
*
* @param sec the configuration object involved in the handshake
* @param request 请求对象
* @param response 响应对象
*/
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// 获取 HttpSession
HttpSession httpSession = (HttpSession) request.getHttpSession();
// 将用户名保存到webSocket的session中
sec.getUserProperties().put("user", httpSession.getAttribute("user"));
}
}
Spring 整合
需要Socket Server 继承 TextWebSocketHandler
类
基于 Servlet 实现 WebSocket 会话共享,也对 Socket 的 Session 进行封装成 WebSocketSession
WebSocket配置类
@Configuration
// 启动 WebSocket
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
/**
* 装配服务端
*/
@Bean
public WebSocketHandler webSocketHandler() {
return new ChatServer();
}
/**
* 装配 HttpSession 的拦截器,这样就可以在握手阶段获取 HttpSession 的内容,在使用 webSocketSession 时,就能直接得到 httpSession 的数据
* @return
*/
public HandshakeInterceptor handshakeInterceptor(){
return new HttpSessionHandshakeInterceptor();
}
/**
* 给服务端注册请求的端点(映射连接地址)
*
* @param registry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 给特定服务端绑定端点(入口)。注册端点,设置连接的url
registry.addHandler(webSocketHandler(),"/connect")
// 设置握手拦截器
.addInterceptors(handshakeInterceptor());
}
}
生命周期
原生 Servlet 使用注解
都可以接收 webSocket的session
开启
和服务端连接的方法,只有第一次使用Http连接时使用
@OnOpen
标注服务端连接的方法,只有第一次使用Http连接时调用
@OnOpen
public void onOpen(Session session) {
// 方法体
}
接收消息
@OnMessage
标注接收客户端信息的信息方法,被标注的方法可以使用 String 和 byte[] 接收数据
@OnMessage
public void OnMessage(String messageString, Session sessionMessage) {
// 方法体
}
关闭
@OnClose
断开连接时调用
@OnClose
public void onClose(Session session) {
// 方法体
}
使用 Spring 整合
/**
* Spring 封装的 WebSocket 服务端,可以继承一个 TextWebSocketHandler,表示这个服务端用于处理文本数据的消息
*/
public class ChatServer extends TextWebSocketHandler {
/**
* 用户列表
* 每一次连接都会创建一个 WebSocketSession(Spring对Socket的Session封装) 对象
*/
private static final Map<String, WebSocketSession> users = new HashMap<>();
/**
* 建立连接后,对应 OnOpen
* @param session
* @throws Exception
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 获取登录的用户信息
String userName = (String) session.getAttributes().get("user");
users.put(userName,session);
}
/**
* 接收客户端消息,对应 OnMessage
* @param session
* @param messageText
* @throws Exception
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage messageText) throws Exception {
Message message = new Message();
message.setFromUser((String) session.getAttributes().get("user"));
message.setSendTime(new SimpleDateFormat("hh:mm").format(new Date()));
message.setContent(messageText.getPayload());
for (WebSocketSession socketSession:users.values()){
// 必须是 TextMessage 的对象
socketSession.sendMessage(new TextMessage(JSONUtil.toJsonStr(message)));
}
}
/**
* 关闭连接后,对应 OnClose
* @param session
* @param status
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
users.remove(session.getAttributes().get("user"));
}
}
前端案例
let ws = new WebSocket("ws://localhost:8080/connect");
构建 webSocket 实例,连接后台 Server 的请求地址
每一个客户端就会创建一个 Socket 会话