Bootstrap

Java集成WebSocket服务

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. 导入依赖
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. 编写WebSocket处理器

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

/**
 * @Author zzw
 * @Create 2022/1/20 - 21:57
 * @Description 连接Websocket前首先会进入到WebsocketHandler进行业务逻辑处理,服务端的参数在拦截器中获取之后通过 attributes传递给WebSocketHandler
 */
@Slf4j
@Component
public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        //连接成功之后,应该在这个方法中将session保存起来,通常都用
        //一个Map来保存,key是用户ID(或其他唯一标识),这样的话想
        //给哪个用户发送消息,那么就直接拿到那个用户的session,然后
        //调用session.sendMessage方法发送消息就可以了
        log.info("连接成功");
    }


    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        log.info("客户端过来一条消息,内容是:" + message.getPayload().toString());
//        TextMessage textMessage = new TextMessage(JSONObject.toJSONString("客户端你好,我已经收到你的消息"));
//        session.sendMessage(textMessage);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        log.info("错误处理");
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
        log.info("连接关闭");
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}
3. 编写WebSocket配置类

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

/**  
  * @Author zzw  
  * @Create 2022/1/20 - 21:57
  * @Description WebSocket 配置类实现(注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定)
  */
@Configuration
@Slf4j
public 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
         */
        @Override
        public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
           // 进行业务逻辑处理
            return true; // 返回true
        }

	   /**
         * 在通过请求或响应之后被调用
         * @param request
         * @param response
         * @param wsHandler
         * @param exception
         */
        @Override
        public 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
      */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(wsHandler, "/websocket") // 拦截路径
                .setAllowedOrigins("*")
                .addInterceptors(handshakeInterceptor); // 拦截之后进去的拦截器
    }

    /**
      * 配置ServerEndpointExporter,配置后会自动注册所有“@ServerEndpoint”注解声明的Websocket Endpoint
      * @return
      */
    @Bean
    public 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配置的路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6xeR66L-1681971260621)(E:\PRD\Images\image-20230420141320302.png)]

集成完毕

非常感谢以下博主:

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

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

;