WebSocket实现长连接

前言

什么是WebSocket?

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket 与 HTTP的区别

相同点:

  1. 都是 TCP 协议;

  2. 都使用 Request/Response 模型进行连接的建立;

  3. websocket 是基于 http 的,他们的兼容性都很好;

  4. 在连接的建立过程中对错误的处理方式相同;

  5. 都可以在网络中传输数据。

不同点:

  1. websocket 是持久连接,http 是短连接;
  2. websocket 的协议是以 ws/wss 开头,http 对应的是 http/https;
  3. websocket 是有状态的,http 是无状态的;
  4. websocket 连接之后服务器和客户端可以双向发送数据,http 只能是客户端发起一次请求之后,服务器才能返回数据;
  5. websocket 是可以跨域的;
  6. websocket 连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
应用场景
  1. 即时通讯
  2. 消息推送弹幕
  3. 媒体聊天
  4. 协同编辑
  5. 基于位置的应用
  6. 体育实况更新
  7. 股票基金报价实时更新

Java集成WebSocket服务

1. 导入依赖
org.springframework.bootspring-boot-starter-websocket
2. 编写WebSocket处理器

连接Websocket前首先会进入到WebsocketHandler进行业务逻辑处理,服务端的参数在拦截器中获取之后通过attributes传递给WebSocketHandler

/** * @Author zzw * @Create 2022/1/20 - 21:57 * @Description 连接Websocket前首先会进入到WebsocketHandler进行业务逻辑处理,服务端的参数在拦截器中获取之后通过 attributes传递给WebSocketHandler */@Slf4j@Componentpublic class MyWebSocketHandler implements WebSocketHandler {@Overridepublic void afterConnectionEstablished(WebSocketSession session) {//连接成功之后,应该在这个方法中将session保存起来,通常都用//一个Map来保存,key是用户ID(或其他唯一标识),这样的话想//给哪个用户发送消息,那么就直接拿到那个用户的session,然后//调用session.sendMessage方法发送消息就可以了log.info("连接成功");}@Overridepublic void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {log.info("客户端过来一条消息,内容是:" + message.getPayload().toString());//TextMessage textMessage = new TextMessage(JSONObject.toJSONString("客户端你好,我已经收到你的消息"));//session.sendMessage(textMessage);}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {log.info("错误处理");}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {log.info("连接关闭");}@Overridepublic boolean supportsPartialMessages() {return false;}}
3. 编写WebSocket配置类

注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定,拦截WebSocket服务将在这里执行

/*** @Author zzw* @Create 2022/1/20 - 21:57* @Description WebSocket 配置类实现(注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定)*/@Configuration@Slf4jpublic class WebSocketConfig implements WebSocketConfigurer { private final MyWebSocketHandler wsHandler;/** * WebSecoket握手拦截器(执行相关业务逻辑) */private HandshakeInterceptor handshakeInterceptor = new HandshakeInterceptor() {/** * 在获取请求或响应之前进行拦截,获取一些请求或响应的数据 * @param request * @param response * @param wsHandler * @param attributes 如果该方法通过,可以在监听器或controller层拿到这里设置的数据 * @return 返回false则拦截,返回true则通过 * @throws Exception */@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception { // 进行业务逻辑处理return true; // 返回true} /** * 在通过请求或响应之后被调用 * @param request * @param response * @param wsHandler * @param exception */@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {}}; public WebSocketConfig(MyWebSocketHandler wsHandler) {this.wsHandler = wsHandler;} /*** websocket拦截器(springsecurity需要配置,因为springsecurity拦截不到websocket)* @ServerEndpoint("/websocket") 拦截的是websocket连接的服务* @param registry*/@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(wsHandler, "/websocket") // 拦截路径.setAllowedOrigins("*").addInterceptors(handshakeInterceptor); // 拦截之后进去的拦截器}/*** 配置ServerEndpointExporter,配置后会自动注册所有“@ServerEndpoint”注解声明的Websocket Endpoint* @return*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
4. 编写WebSocket服务类

进行消息的接收与发送

/** * 核心配置 * * @author Zzw * @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, * * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端 * @date 2021/6/9 16:03 */@Slf4j@Component@ServerEndpoint("/websocket")public class WebSocketServer{ /** * 当前在线人数 */ private static AtomicInteger onlineCount = new AtomicInteger(0); /** * 存放每个客户端对应的 WebSocketServer 对象 */ private static CopyOnWriteArrayList<WebSocketServer> webSocketServers = new CopyOnWriteArrayList<>(); private Session session; /** * 连接建立成功调用的方法 */ @OnOpen public void onOpen(Session session) { this.session = session; // 加入集合中 webSocketServers.add(this); // 在线人数+1 int count = onlineCount.incrementAndGet(); log.info("有新的窗口开始监听:, 当前在线人数为:{}", count); } /** * 收到客服端消息后调用的方法 * * @param message 客服端发送过来的消息 */ @OnMessage public void onMessage(String message, Session session) { log.info("收到来自窗口:{}, 消息内容:{}", message,session.getMessageHandlers()); if (!StringUtils.isNullOrEmpty(message)) { // 进行业务逻辑处理 } // 群发消息 //for (WebSocketServer webSocketServer : getWebSocketServers()) { //webSocketServer.sendMessage(message); //} } /** * 发生错误时调用的方法 */ @OnError public void onError(Session session, @NotNull Throwable throwable) { log.error("发生错误"); throwable.printStackTrace(); } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { // 从集合中移除 webSocketServers.remove(this); // 在线人数-1 int count = onlineCount.decrementAndGet(); log.info("释放的sid为:{}"); log.info("有一连接关闭!当前在线人数为:{}", count); } /** * 实现服务器主动推送消息 * * @param message 消息内容 */ public void sendMessage(String message) { try { this.session.getBasicRemote().sendText(message); log.info("message:{}",message); } catch (IOException e) { log.error("websocket IO Exception"); e.printStackTrace(); } } /** * 发送消息到指定窗口 null则群发 */ public void sendInfo(String message) { for (WebSocketServer webSocketServer : getWebSocketServers()) { webSocketServer.sendMessage(message); } return; } public static CopyOnWriteArrayList<WebSocketServer> getWebSocketServers() { return webSocketServers; }}
5. 启动类上添加相关依赖

启动类上添加 @EnableWebSocket 依赖,表示启用webSocket服务

6. 进行WebSocket测试

WebSocket测试工具:http://www.jsons.cn/websocket/
@ServerEndpoint(“/websocket”)
ws://127.0.0.1:端口/ServerEndpoint配置的路径

集成完毕

非常感谢以下博主:

https://blog.csdn.net/weixin_43299180/article/details/117027846

https://blog.csdn.net/weixin_47428270/article/details/126639625