WebSocket知识概括

288 阅读16分钟

@TOC

连接与轮询

短连接和长连接:

  • 短连接:每次Http请求都会建立Tcp连接,管理容易
  • 长连接︰ ①只需要建立一次Tcp连接,以后Http请求重复使用同一个Tcp连接,管理难 ②在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。
  • 区分:HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短连接如果服务器没有告诉客户端超时时间也没关系,服务端可能主动发起四次握手断开TCP连接,客户端能够知道该TCP连接已经无效;另外TCP还有心跳包来检测当前连接是否还活着,方法很多,避免浪费资源。
connection: keep-alive
Keep-Alive: timeout=20
  • 选择:长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择
  • 应用场景区别: ①一般长连接(追求实时性高的场景)用于少数client-end to server-end的频繁的通信,例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。 ②而像WEB网站的http服务一般都用短链接(追求资源易回收场景),因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源。

短轮询和长轮询:

  • 轮询和短连接与长连接有本质区别,轮询是为了实现服务器推送技术。
  • 短轮询:重复发送Http请求,查询目标事件是否完成。 ①优点:编写简单 ②缺点:浪费带宽和服务器资源 ③短轮询应用:http 短轮询一般用在实时性要求不高的地方, 比如新浪微薄的未读条数查询就是浏览器端每隔一段时间查询的.
  • 长轮询:在服务端hold住Http请求(死循环或者sleep等等方式),等到目标时间发生,返回Http响应。 ①优点:在无消息的情况下不会频繁的请求 ②缺点:编写复杂 ③长轮询应用:长轮询一般用在 web im, im 实时性要求高, http 长轮询的控制权一直在服务器端, 而数据是在服务器端的, 因此实时性高。 <1>像新浪微薄的im, 朋友网的 im 以及 webQQ 都是用 http 长轮询实现的; <2>NodeJS 的异步机制貌似可以很好的处理 http 长轮询导致的服务器瓶颈问题, 这个有待研究。

轮询和连接区别:

  • 轮询也是连接,只是不保持连接,得到数据就关闭。轮询是单向的。长连接是双向的。
  • 但是轮询的好处是不占用端口和连接资源,如果你只有很少的数据需要获取,并且单向访问,就用轮询。
  • 短轮询:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。 ①优点:后端程序编写比较容易。 ②缺点:请求中有大半是无用,浪费带宽和服务器资源。 ③实例:适于小型应用。
  • 长轮询:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。 ①优点:在无消息的情况下不会频繁的请求。 ②缺点:服务器hold连接会消耗资源。 ③实例:WebQQ、Hi网页版、Facebook IM。

WebSocket简介

websocket简述:

  • WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 ①WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 ②在 WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
  • 现在很多网站为了实现推送技术,所用的技术都是 Ajax轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。 ①这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
  • HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。 ①浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。 ②当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage事件来接收服务器返回的数据。 在这里插入图片描述

Websocket 执行流程:

  • 对于 WebSocket 的整个生命周期,主要由以下几个事件组成: ①连接建立:客户端向服务端请求建立连接并完成连接建立 ②数据上行:客户端通过已经建立的连接向服务端发送数据 ③数据下行:服务端通过已经建立的连接向客户端发送数据 ④客户端断开:客户端要求断开已经建立的连接 ⑤服务端断开:服务端要求断开已经建立的连接 在这里插入图片描述

WebSocket 协议:

  • WebSocket 协议使用 ws 和 wss URL协议,以分别代表不安全和安全的WebSocket请求。
  • 使用WebSocket构造函数来创建一个WebSocket连接,构造函数会返回一个WebSocket实例,可以用来监听事件。以下代码用于创建WebSocket连接 : ①以下代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。
// 创建一个新的WebSocket. 
var websocket = new WebSocket(url, [protocol] );
  • websocket使用场景分享: ①如弹幕, ②网页聊天系统, ③实时监控, ④股票行情推送等

Websocket前端简介:

  • WebSocket 属性: ①binaryType:使用二进制的数据类型连接。 ②bufferedAmount(只读):未发送至服务器的字节数。 ③extensions(只读):服务器选择的扩展。 ④onclose:用于指定连接关闭后的回调函数。 ⑤onerror:用于指定连接失败后的回调函数。 ⑥onmessage:用于指定当从服务器接受到信息时的回调函数。 ⑦onopen:用于指定连接成功后的回调函数。 ⑧protocol(只读):用于返回服务器端选中的子协议的名字。 ⑨readyState(只读):返回当前 WebSocket 的连接状态,共有 4 种状态: <1>CONNECTING — 正在连接中,对应的值为 0; <2>OPEN — 已经连接并且可以通讯,对应的值为 1; <3>CLOSING — 连接正在关闭,对应的值为 2; <4>CLOSED — 连接已关闭或者没有连接成功,对应的值为 3。

  • WebSocket 事件:WebSocket 是纯事件驱动,通过监听事件可以处理到来的数据和改变的链接状态。以下是 WebSocket 对象的相关事件。 ①webSocket.onopen:在客户端和服务器之间建立连接后,将从Web Socket实例触发 open 事件,它被称为客户端和服务器之间的初始握手。连接建立后引发的事件称为 onopen ,open事件触发并建立了一个连接,用于指定连接成功后的回调函数。 ②webSocket.onclose:关闭事件标志着服务器和客户端之间通信结束,无法进一步传输消息。实例对象的onclose属性,用于指定连接关闭后的回调函数。 ③webSocket.onmessage:消息事件通常发生当服务器发送一些数据时,无论何时发送数据,都会触发 onmessage 函数。实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。 <1>注意:服务器发送给客户端的消息可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。 ④webSocket.onerror:响应意外故障的时候触发,用于指定报错时的回调函数,是调用服务器重连逻辑以及处理来自 WebSocket 对象的异常的最佳场所。

  • WebSocket 方法: ①webSocket.send:此操作通常首选用于与服务器的某些通信,包括发送消息,包括文本文件,二进制数据或图像.。 ②webSocket.close:此方法代表再见握手.它完全终止连接,在重新建立连接之前不会传输任何数据。

  • 前端代码演示:

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>菜鸟教程(runoob.com)</title>
    
      <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               alert("您的浏览器支持 WebSocket!");
               
               // 打开一个 web socket
               var ws = new WebSocket("ws://localhost:9998/echo");
                
               ws.onopen = function()
               {
                  // Web Socket 已连接上,使用 send() 方法发送数据
                  ws.send("发送数据");
                  alert("数据发送中...");
               };
                
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  alert("数据已接收...");
               };
                
               ws.onclose = function()
               { 
                  // 关闭 websocket
                  alert("连接已关闭..."); 
               };
            }
            
            else
            {
               // 浏览器不支持 WebSocket
               alert("您的浏览器不支持 WebSocket!");
            }
         }
      </script>
        
   </head>
   <body>
   
      <div id="sse">
         <a href="javascript:WebSocketTest()">运行 WebSocket</a>
      </div>
      
   </body>
</html>

websocket 跟 socket 的区别:

  • 软件通信有七层结构,下三层结构偏向与数据通信,上三层更偏向于数据处理,中间的传输层则是连接上三层与下三层之间的桥梁,每一层都做不同的工作,上层协议依赖与下层协议。基于这个通信结构的概念。
  • Socket 其实并不是一个协议,是应用层与 TCP/IP 协议族通信的中间软件抽象层,它是一组接口。当两台主机通信时,让 Socket去组织数据,以符合指定的协议。TCP 连接则更依靠于底层的 IP 协议,IP 协议的连接则依赖于链路层等更低层次。
  • WebSocket 则是一个典型的应用层协议

Websocket使用

J2EE 方式:

  • 先来 J2EE,一般我把 javax 包里面对 JavaWeb 的扩展都叫做J2EE,这个定义是否完全正确我觉得没必要深究,只是一种个人习惯,而本章节所介绍的 J2EE 方式则是指 Tomcat 为 WS所做的支持,这套代码的包名前缀叫做: javax.websocket 。这套代码中定义了一套适用于 WS 开发的注解和相关支持。
  • 在下上代码中,我们着重关心 WS 相关的注解,主要有以下四个: ①@ServerEndpoint : 这里就像 RequestMapping 一样,放入一个 WS 服务器监听的 URL。 ②@OnOpen :这个注解修饰的方法会在 WS 连接开始时执行。 ③@OnClose :这个注解修饰的方法则会在 WS 关闭时执行。 ④@OnMessage :这个注解则是修饰消息接受的方法,并且由于消息有文本和二进制两种方式,所以此方法参数上可以使用 String 或者二进制数组的方式。
-------websocket类----
@Component
@ServerEndpoint("/j2ee-ws/{msg}")
public class WebSocketServer {

    //建立连接成功调用
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "msg") String msg){
        System.out.println("WebSocketServer 收到连接: " + session.getId() + ", 当前消息:" + msg);
    }

    //收到客户端信息
    @OnMessage
    public void onMessage(Session session, String message) throws IOException {
        message = "WebSocketServer 收到连接:" + session.getId() +  ",已收到消息:" + message;
        System.out.println(message);
        session.getBasicRemote().sendText(message);
    }

    //连接关闭
    @OnClose
    public void onclose(Session session){
        System.out.println("连接关闭");
    }
}

------websoket配置类--------
@Configuration
public class WebSocketConfig {

 @Bean
 public ServerEndpointExporter serverEndpointExporter() {
     return new ServerEndpointExporter();
 }
}

Spring 方式:

  • Spring 作为 Java 开发界的老大哥,几乎封装了一切可以封装的,对于 WS 开发呢 Spring也提供了一套相关支持,而且从使用方面我觉得要比 J2EE 的更易用。使用它的第一步我们先引入 SpringBoot - WS 依赖。
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  • Spring 为此提供了一个接口—— WebSocketHandler,我们可以通过实现此接口重写其接口方法的方式自定义逻辑。
@Component
public class SpringSocketHandle implements WebSocketHandler {

 @Override
 public void afterConnectionEstablished(WebSocketSession session) throws Exception {
     System.out.println("SpringSocketHandle, 收到新的连接: " + session.getId());
 }

 @Override
 public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
     String msg = "SpringSocketHandle, 连接:" + session.getId() +  ",已收到消息。";
     System.out.println(msg);
     session.sendMessage(new TextMessage(msg));
 }

 @Override
 public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
     System.out.println("WS 连接发生错误");
 }

 @Override
 public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
     System.out.println("WS 关闭连接");
 }

 // 支持分片消息
 @Override
 public boolean supportsPartialMessages() {
     return false;
 }
}
  • 上面这个例子很好的展示了 WebSocketHandler 接口中的五个函数,通过名字我们就应该知道它具有什么功能了: ①afterConnectionEstablished :连接成功后调用。 ②handleMessage :处理发送来的消息。 ③handleTransportError: WS 连接出错时调用。 ④afterConnectionClosed :连接关闭后调用。 ⑤supportsPartialMessages :是否支持分片消息。
  • 以上这几个方法重点可以来看一下 handleMessage 方法,handleMessage 方法中有一个WebSocketMessage 参数,这也是一个接口,我们一般不直接使用这个接口而是使用它的实现类,它有以下几个实现类: ①BinaryMessage :二进制消息体 ②TextMessage :文本消息体 ③PingMessage: Ping 消息体 ④PongMessage: Pong 消息体
  • Spring 给我们定义了一个 AbstractWebSocketHandler ,它已经封装了这些重复劳动,我们可以直接继承这个类然后重写我们想要处理的消息类型:
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
}

protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
}

protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
}
  • 上面这部分都是对于 Handle 的操作,有了 Handle 之后我们还需要将它绑定在某个 URL 上,或者说监听某个 URL,那么必不可少的需要以下配置:
@Configuration
@EnableWebSocket
public class SpringSocketConfig implements WebSocketConfigurer {

    @Autowired
    private SpringSocketHandle springSocketHandle;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(springSocketHandle, "/spring-ws").setAllowedOrigins("*");
    }
}

SocketIO 方式:

  • SocketIO 方式和上面两种有点不太一样,因为 SocketIO诞生初就是为了兼容性作为考量的,前端的读者们应该对它更熟悉,因为它是一个 JS 库,我们先来看一下维基百科对它的定义: ①Socket.IO 是一个面向实时 web 应用的 JavaScript 库。它使得服务器和客户端之间实时双向的通信成为可能。他有两个部分:在浏览器中运行的客户端库,和一个面向Node.js的服务端库,两者有着几乎一样的API。
  • 所以我觉得使用它更多是因为兼容性,因为 HTML5 之后原生的 WS 应该也够用了,然而它是一个前端库,所以 Java语言这块并没有官方支持,好在民间大神已经以 Netty 为基础开发了能与它对接的 Java 库: netty-socketio 。不过我要先给大家提个醒,不再建议使用它了,不是因为它很久没更新了,而是因为它支持的 Socket-Client 版本太老了,截止到 2022-04-29 日,SocketIO 已经更新到 4.X 了,但是 NettySocketIO 还只支持 2.X 的 Socket-Client 版本。
<dependency>
	<groupId>com.corundumstudio.socketio</groupId>
	<artifactId>netty-socketio</artifactId>
	<version>1.7.19</version>
</dependency>
@Configuration
public class SocketIoConfig {

    @Bean
    public SocketIOServer socketIOServer() {
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();

        config.setHostname("127.0.0.1");
        config.setPort(8001);
        config.setContext("/socketio-ws");
        SocketIOServer server = new SocketIOServer(config);
        server.start();
        return server;
    }

    @Bean
    public SpringAnnotationScanner springAnnotationScanner() {
        return new SpringAnnotationScanner(socketIOServer());
    }
}
@Component
public class SocketIoHandle {

    /**
     * 客户端连上socket服务器时执行此事件
     * @param client
     */
    @OnConnect
    public void onConnect(SocketIOClient client) {
        System.out.println("SocketIoHandle 收到连接:" + client.getSessionId());
    }

    /**
     * 客户端断开socket服务器时执行此事件
     * @param client
     */
    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        System.out.println("当前链接关闭:" + client.getSessionId());
    }

    @OnEvent( value = "onMsg")
    public void onMessage(SocketIOClient client, AckRequest request, Object data) {
        System.out.println("SocketIoHandle 收到消息:" + data);
        request.isAckRequested();
        client.sendEvent("chatMsg", "我是 NettySocketIO 后端服务,已收到连接:" + client.getSessionId());
    }
}

Netty 方式:

  • Netty 作为 Java 界大名鼎鼎的开发组件,对于常见协议也全部进行了封装,所以我们可以直接在 Netty 中去很方便的使用WebSocket,接下来我们可以看看 Netty 怎么作为 WS 的服务器进行开发。
<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>4.1.75.Final</version>
</dependency>
  • 启动一个 Netty 容器了,配置很多,但是比较关键的也就那几个:
public class WebSocketNettServer {
    public static void main(String[] args) {

        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup work = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap
                    .group(boss, work)
                    .channel(NioServerSocketChannel.class)
                    //设置保持活动连接状态
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .localAddress(8080)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline()
                                    // HTTP 请求解码和响应编码
                                    .addLast(new HttpServerCodec())
                                    // HTTP 压缩支持
                                    .addLast(new HttpContentCompressor())
                                    // HTTP 对象聚合完整对象
                                    .addLast(new HttpObjectAggregator(65536))
                                    // WebSocket支持
                                    .addLast(new WebSocketServerProtocolHandler("/ws"))
                                    .addLast(WsTextInBoundHandle.INSTANCE);
                        }
                    });

            //绑定端口号,启动服务端
            ChannelFuture channelFuture = bootstrap.bind().sync();
            System.out.println("WebSocketNettServer启动成功");

            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            boss.shutdownGracefully().syncUninterruptibly();
            work.shutdownGracefully().syncUninterruptibly();
        }

    }
}
  • 以上代码我们主要关心端口号和重写的 ChannelInitializer 就行了,里面我们定义了五个过滤器(Netty使用责任链模式),前面三个都是 HTTP 请求的常用过滤器(毕竟 WS 握手是使用 HTTP 头的所以也要配置 HTTP支持),第四个则是 WS 的支持,它会拦截 /ws 路径,最关键的就是第五个了过滤器它是我们具体的业务逻辑处理类,效果基本和 Spring那部门中的 Handle 差不多,我们来看看代码:
@ChannelHandler.Sharable
public class WsTextInBoundHandle extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private WsTextInBoundHandle() {
        super();
        System.out.println("初始化 WsTextInBoundHandle");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("WsTextInBoundHandle 收到了连接");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

        String str = "WsTextInBoundHandle 收到了一条消息, 内容为:" + msg.text();

        System.out.println(str);

        System.out.println("-----------WsTextInBoundHandle 处理业务逻辑-----------");

        String responseStr = "{\"status\":200, \"content\":\"收到\"}";

        ctx.channel().writeAndFlush(new TextWebSocketFrame(responseStr));
        System.out.println("-----------WsTextInBoundHandle 数据回复完毕-----------");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {

        System.out.println("WsTextInBoundHandle 消息收到完毕");
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("WsTextInBoundHandle 连接逻辑中发生了异常");
        cause.printStackTrace();
        ctx.close();
    }
}

聊天室

WebSocket打造在线聊天室:

  • 依赖:
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
  • 配置类:
//配置类
@Configuration
public class WebSocketConfig {
    @Bean
    //注入ServerEndpointExporter bean对象,自动注册使用注解@ServerEndpoint的bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}
  • 需要的工具类,用于转化信息格式:
public class MessageUtils {
    public static String getMessage(boolean isSystemMessage,String fromName,Object message){
        try {
            ResultMessage result = new ResultMessage();
            result.setSystem(isSystemMessage);
            result.setMessage(message);
            if (fromName!=null){
                result.setFromName(fromName);
            }
            //把字符串转成json格式的字符串
            ObjectMapper mapper = new ObjectMapper();
            return mapper.writeValueAsString(result);
        }catch (JsonProcessingException e){
            e.printStackTrace();
        }
        return null;
    }
}
  • 获取HttpSession对象的类
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //获取HttpSession对象
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
}
  • 通信类:
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfigurator.class)
@Component
public class ChatEndpoint {

    //用来存储每个用户客户端对象的ChatEndpoint对象
    private static Map<String,ChatEndpoint> onlineUsers = new ConcurrentHashMap<>();

    //声明session对象,通过对象可以发送消息给指定的用户
    private Session session;

    //声明HttpSession对象,我们之前在HttpSession对象中存储了用户名
    private HttpSession httpSession;

    //连接建立
    @OnOpen
    public void onOpen(Session session, EndpointConfig config){
        this.session = session;
        HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
        this.httpSession = httpSession;
        //存储登陆的对象
        String username = (String)httpSession.getAttribute("user");
        onlineUsers.put(username,this);

        //将当前在线用户的用户名推送给所有的客户端
        //1 获取消息
        String message = MessageUtils.getMessage(true, null, getNames());
        //2 调用方法进行系统消息的推送
        broadcastAllUsers(message);
    }

    private void broadcastAllUsers(String message){
        try {
            //将消息推送给所有的客户端
            Set<String> names = onlineUsers.keySet();
            for (String name : names) {
                ChatEndpoint chatEndpoint = onlineUsers.get(name);
                chatEndpoint.session.getBasicRemote().sendText(message);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    //返回在线用户名
    private Set<String> getNames(){
        return onlineUsers.keySet();
    }

    //收到消息
    @OnMessage
    public void onMessage(String message,Session session){
        //将数据转换成对象
        try {
            ObjectMapper mapper =new ObjectMapper();
            Message mess = mapper.readValue(message, Message.class);
            String toName = mess.getToName();
            String data = mess.getMessage();
            String username = (String) httpSession.getAttribute("user");
            String resultMessage = MessageUtils.getMessage(false, username, data);
            //发送数据
            onlineUsers.get(toName).session.getBasicRemote().sendText(resultMessage);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    //关闭
    @OnClose
    public void onClose(Session session) {
        String username = (String) httpSession.getAttribute("user");
        //从容器中删除指定的用户
        onlineUsers.remove(username);
        MessageUtils.getMessage(true,null,getNames());
    }}

websocket总结

websocket广播、单播、组播介绍和使用场景说明:

  • 单播(Unicast):点对点,私信私聊
  • 广播(Broadcast)(所有人):游戏公告,发布订阅
  • 多播,也叫组播(Multicast)(特地人群):多人聊天,发布订阅 在这里插入图片描述