前言

最近在学习netty这个网络编程框架,对这个框架加以运用,手撸了一个WebSocket服务器并写了一些在这个过程中的一些疑问和思考。

首先介绍一下WebSocket

WebSocket

WebSocket简介

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

Websocket 使用 ws 或 wss 的统一资源标志符(URI),其中 wss 表示使用了 TLS 的 Websocket。

如:

ws://echo.websocket.org wss://echo.websocket.org

WebSocket 与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制。

WebSocket原理

WebSocket是一种基于TCP的全双工通信协议,它在HTTP基础上进行了扩展,通过在握手阶段升级协议,实现了服务器与客户端之间的双向通信。下面是WebSocket的工作原理:

  1. 握手阶段(Handshake):客户端发起WebSocket连接请求时,会发送一个特殊的HTTP请求,包含一些特殊的头部信息,如Upgrade和Connection字段。服务器收到这个请求后,如果支持WebSocket协议,会返回一个特殊的HTTP响应,表示协议升级成功。这个过程也被称为握手阶段。

  2. 建立连接:握手成功后,客户端和服务器之间建立了一条持久化的TCP连接。这个连接可以保持打开状态,而不需要像传统的HTTP请求那样频繁地建立和关闭连接。

  3. 数据传输:在连接建立后,客户端和服务器可以随时发送消息给对方。客户端可以通过WebSocket API发送消息给服务器,服务器也可以通过WebSocket发送消息给客户端。这些消息可以是文本、二进制数据或者其他格式的数据。

  4. 关闭连接:当需要关闭WebSocket连接时,客户端或服务器可以发送一个特殊的关闭帧来表示关闭连接的意图。双方会进行一系列的关闭握手,最终关闭连接。

WebSocket的工作原理可以总结为以下几个关键点:

  • 使用HTTP进行握手,协商升级到WebSocket协议。
  • 建立基于TCP的持久化连接,实现全双工通信。
  • 双方可以随时发送消息给对方,实现实时的双向通信。
  • 关闭连接时,发送关闭帧进行关闭握手。

这种基于WebSocket的双向通信方式可以在Web应用中实现实时推送、即时聊天、实时数据更新等功能,提供更高效和实时的通信能力。

写到这里我突然产生了一个疑问:WebSocket与http这两个协议有什么区别与联系呢?于是我又查阅资料大概了解了一下。

Websocket与http的区别与联系

历史背景

早期,很多网站为了实现推送技术,所用的技术都是轮询(也叫短轮询)。轮询是指由浏览器每隔一段时间向服务器发出 HTTP 请求,然后服务器返回最新的数据给客户端。

这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而 HTTP 请求与响应可能会包含较长的头部,其中真正有效的数据可能只是很小的一部分,所以这样会消耗很多带宽资源。

比较新的轮询技术是 Comet。这种技术虽然可以实现双向通信,但仍然需要反复发出请求。而且在 Comet 中普遍采用的 HTTP 长连接也会消耗服务器资源。并且HTTP长连接仍然是基于请求-响应模式的,服务器只能在收到客户端请求后才能发送响应,无法实现服务器主动推送数据给客户端。

在这种情况下,HTML5 定义了 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯和服务端向客户端推送消息。

格式区别

HTTP和WebSocket在数据格式上存在一些区别。

1.HTTP格式

  • 请求格式:HTTP请求由请求行、请求头部和请求体组成。请求行包含请求方法、URI和HTTP协议版本;请求头部包含各种头部字段,如Host、User-Agent、Content-Type等;请求体包含实际的请求数据。
  • 响应格式:HTTP响应由状态行、响应头部和响应体组成。状态行包含状态码和状态消息;响应头部包含各种头部字段,如Content-Type、Content-Length等;响应体包含实际的响应数据。

2.WebSocket格式:

  • 握手格式:WebSocket在握手阶段使用HTTP协议进行协商,因此握手阶段的数据格式与HTTP请求和响应相同。
  • 数据帧格式:在建立WebSocket连接后,双方之间传输的数据使用数据帧进行封装。数据帧包含了控制帧和数据帧两种类型。
    • 控制帧:用于控制连接的开启、关闭和错误处理等,如握手确认、连接关闭等。
    • 数据帧:用于实际的数据传输,可以是文本帧或二进制帧。数据帧中包含了有效载荷(Payload),即实际传输的数据。

总结起来,HTTP和WebSocket在握手阶段的数据格式相同,都是基于HTTP的请求和响应格式。而在建立连接后,WebSocket使用数据帧进行数据的传输,其中控制帧用于控制连接,数据帧用于实际的数据传输。相比之下,HTTP在每次请求和响应时都需要发送完整的头部信息,而WebSocket通过建立持久化连接,可以在多个数据帧中传输数据,减少了头部信息的重复发送,提高了效率。

联系

db20843a30fb44e19671bfbc98ca07cc.png

 相同点:

  1. 都是基于tcp的,都是可靠性传输协议
  2. 都是应用层协议

不同点:

  1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息
  2. HTTP是单向的
  3. WebSocket是需要浏览器和服务器握手进行建立连接的
  4. 而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接,并且该连接是无状态的。

 联系:

  • WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的

搭建WebSocket

引入Netty依赖

引入 Netty 依赖:首先,在 Maven 中引入 Netty 的依赖。可以在 pom.xml 文件中添加以下内容:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.63.Final</version>
</dependency>

创建 WebSocket 服务器

创建一个 WebSocket 服务器,并设置相关的参数。可以使用以下代码创建一个 WebSocket 服务器:

package com.kjz.server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;


@Component("nettyServer")
public class NettyServer {

    private Logger logger = LoggerFactory.getLogger(NettyServer.class);

    //配置服务端NIO线程组
    private final EventLoopGroup parentGroup = new NioEventLoopGroup();
    private final EventLoopGroup childGroup = new NioEventLoopGroup();
    private Channel channel;

    public ChannelFuture bing(InetSocketAddress address) {
        ChannelFuture channelFuture = null;
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(parentGroup, childGroup)
                    .channel(NioServerSocketChannel.class)    //非阻塞模式
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new MyChannelInitializer());
            channelFuture = b.bind(address).syncUninterruptibly();
            channel = channelFuture.channel();
        } catch (Exception e) {
            logger.error(e.getMessage());
        } finally {
            if (null != channelFuture && channelFuture.isSuccess()) {
                logger.info("kjz-demo-netty server start done. {关注CSDN编程小猹,获取源码}");
            } else {
                logger.error("kjz-demo-netty server start error. {关注CSDN编程小猹,获取源码}");
            }
        }
        return channelFuture;
    }

    public void destroy() {
        if (null == channel) return;
        channel.close();
        parentGroup.shutdownGracefully();
        childGroup.shutdownGracefully();
    }

    public Channel getChannel() {
        return channel;
    }

}

添加WebSocket 编解码器和处理器

在管道初始化器中,我们需要添加 WebSocket 编解码器和处理器。此处为了加强理解我没有用io.netty.handler.codec.http.websocketx包中提供的编解码器和处理器而是自己手写了一个

管道初始化类

package com.kjz.server;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;import java.nio.charset.Charset;

/**
 * 消息传输协议
 */
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel channel) {
        /**HTTP编解码器HttpServerCodec()
         *HttpServerCodec是Netty提供的一个HTTP请求和响应的编解码器,它将HTTP消息转换为字节流进行传输,
         * 并将字节流解析为HTTP消息。
         *
         * 具体来说,HttpServerCodec实现了ChannelInboundHandler和ChannelOutboundHandler接口,
         * 因此可以同时处理入站和出站的数据流。在入站方向,它将字节流解码为HttpRequest对象和HttpContent对象;
         * 在出站方向,它将HttpResponse对象和HttpContent对象编码为字节流。
         *
         * 当HttpServerCodec添加到ChannelPipeline中时,它会被自动分成两个独立的ChannelHandler,
         * 分别是HttpRequestDecoder和HttpResponseEncoder。
         *
         * HttpRequestDecoder负责将字节流解码为HttpRequest对象和HttpContent对象。
         * 它会将接收到的字节流按照HTTP协议的规范进行解析,并生成对应的HttpRequest对象表示HTTP请求头部信息,
         * 以及HttpContent对象表示HTTP请求体内容。
         *
         * HttpResponseEncoder负责将HttpResponse对象和HttpContent对象编码为字节流。
         * 它会将要发送的HttpResponse对象和HttpContent对象按照HTTP协议的规范进行编码,转换为字节流形式,
         * 以便进行网络传输。
         *
         * 通过使用HttpServerCodec,可以简化在Netty中处理HTTP请求和响应的编解码过程。它隐藏了底层的字节流操作,
         * 使开发者可以更方便地处理HTTP消息。
         *
         */
        channel.pipeline().addLast("http-codec", new HttpServerCodec());
        /**HttpObjectAggregator简介
         *HttpObjectAggregator是Netty提供的一个HTTP请求和响应聚合器,
         * 它可以将多个HTTP消息片段聚合成一个完整的FullHttpRequest或FullHttpResponse对象。
         *
         * 在HTTP协议中,请求和响应消息通常会被分成多个消息片段进行传输。
         * 例如,在使用HTTP/1.1协议时,客户端可以通过一个TCP连接发送多个请求,这些请求可能会被分成多个消息片段进行传输。
         * 同样地,服务器也可以通过一个TCP连接发送多个响应,这些响应也可能会被分成多个消息片段进行传输。(
         * 此处使用的是http长连接在HTTP/1.1协议中,引入了持久连接(Persistent Connection)的概念,也被称为HTTP长连接。
         * 通过使用持久连接,客户端和服务器可以在单个TCP连接上发送多个HTTP请求和响应,而不需要为每个请求和响应都建立
         * 和关闭一个独立的TCP连接。在HTTP长连接中,客户端和服务器之间的TCP连接会保持打开状态,
         * 直到满足一定的条件才会关闭。这样可以避免频繁地进行TCP连接的建立和关闭,从而减少了网络延迟和连接建立的开销。
         * 当客户端发送多个HTTP请求时,这些请求可以被分成多个消息片段进行传输,但仍然在同一个TCP连接上进行。
         * 服务器也可以通过同一个TCP连接发送多个HTTP响应,同样可以将响应分成多个消息片段进行传输。
         * 使用HTTP长连接可以提高性能和效率,特别是在需要发送多个请求或响应的场景下。不过,需要注意的是,
         * HTTP长连接并不意味着无限期的连接,服务器或客户端可能会在一定的时间内关闭长连接以释放资源。)
         * HttpObjectAggregator可以将这些消息片段聚合成一个完整的FullHttpRequest或FullHttpResponse对象,
         * 方便后续处理。具体来说,当接收到一个HTTP请求或响应消息时,HttpObjectAggregator会将这些消息片段缓存起来
         * ,直到收到了一个包含LastHttpContent的消息片段,表示该消息的最后一个片段已经到达。
         * 此时,HttpObjectAggregator将会将所有缓存的消息片段合并成一个完整的FullHttpRequest或FullHttpResponse对象,
         * 并将该对象传递给下一个ChannelHandler进行处理。
         * 需要注意的是,HttpObjectAggregator只能处理HTTP消息的内容部分,而不能处理HTTP消息的头部信息。
         * 因此,在添加HttpObjectAggregator到ChannelPipeline之前,需要先添加一个HttpServerCodec来处理
         * HTTP消息的编解码。
         * */
        channel.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
        /**ChunkedWriteHandler介绍
         *ChunkedWriteHandler是Netty中的一个处理器,用于将数据按照指定大小分块传输。
         * 它通常用于处理大文件或流式数据的传输,可以将数据流分成多个块,逐个发送到目标节点,
         * 以避免一次性将整个数据流发送到目标节点导致内存溢出或阻塞的问题。
         *
         * 具体来说,ChunkedWriteHandler的主要作用是将一个完整的数据流拆分成多个ChunkedInput对象,
         * 并通过ChannelPipeline逐个写入到目标节点。它提供了以下几个重要的方法:
         *
         * write:将一个ChunkedInput对象的数据块写入到目标节点。它会自动处理数据块的传输顺序、内存管理和流控制等问题。
         *
         * isEndOfInput:判断是否已经到达数据流的末尾。可以通过该方法在数据传输完成后进行相关的处理操作。
         *
         * handlerAdded:在处理器被添加到ChannelPipeline时调用的回调方法。可以在该方法中进行一些初始化操作。
         *
         * exceptionCaught:在异常发生时调用的回调方法。可以在该方法中处理异常情况。
         *
         * 使用ChunkedWriteHandler时,需要注意以下几点:
         *
         * 数据块大小:可以通过构造方法或属性设置数据块的大小。根据实际情况进行调整,以达到最佳的性能和效率。
         *
         * 内存占用:由于ChunkedWriteHandler是逐块发送数据,不会一次性加载整个数据流到内存中。
         * 这样可以避免内存溢出或阻塞的问题,但也可能会增加网络传输的开销。
         *
         * 传输顺序:ChunkedWriteHandler会按照数据块的顺序进行传输,确保数据的完整性和正确性。
         * 需要注意数据块的顺序是否正确,以免导致数据无法正确解析或处理。
         *
         * 总之,ChunkedWriteHandler是Netty中一个非常有用的处理器,可用于高效地传输大文件或流式数据。
         * 它通过将数据流拆分成多个块,并逐个发送到目标节点,避免了一次性发送整个数据流导致的问题。
         * 在使用时,需要根据实际情况进行配置和调整,以获得最佳的性能和效率。
         */
        channel.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
        // 在管道中添加我们自己的接收数据实现方法
        channel.pipeline().addLast(new MyServerHandler());

           }

}

自定义WebSocket处理器

package com.kjz.server;

import com.alibaba.fastjson.JSON;
import com.kjz.domain.ClientMsgProtocol;
import com.kjz.util.ChannelHandler;
import com.kjz.util.MsgUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * WebSocket处理器
 */
public class MyServerHandler extends ChannelInboundHandlerAdapter {

    private Logger logger = LoggerFactory.getLogger(MyServerHandler.class);
    /**WebSocketServerHandshaker简介
     * WebSocketServerHandshaker是Netty框架中用于处理WebSocket协议的握手和消息处理的重要组件之一。
     * 它负责与客户端进行握手,建立WebSocket连接,并处理双向通信的消息。
     *
     * WebSocketServerHandshaker提供了以下主要功能:
     *
     * 握手处理:WebSocketServerHandshaker负责处理WebSocket连接的握手过程。在接收到WebSocket连接请求后,
     * 它会根据协议规范进行握手验证,验证通过后建立WebSocket连接。
     *
     * 消息处理:一旦WebSocket连接建立成功,WebSocketServerHandshaker就可以处理双向的WebSocket消息。
     * 它可以接收来自客户端的消息,并根据业务逻辑进行处理。同时,它也可以发送消息给客户端。
     *
     * 协议版本支持:WebSocketServerHandshaker能够支持多个WebSocket协议版本。在握手过程中,
     * 它会根据客户端请求的协议版本选择合适的协议进行握手。如果客户端请求的协议版本不被支持,
     * 它可以发送一个不支持的版本响应给客户端。
     *
     * 扩展支持:WebSocketServerHandshaker还提供了对WebSocket扩展(Extensions)的支持。
     * 它可以根据客户端请求的扩展进行协商,并在握手过程中进行扩展的处理。
     *
     * WebSocketServerHandshaker的具体使用方式会依赖于所使用的框架和实际业务需求。一般来说,
     * 需要在服务器端实现一个WebSocket握手处理器,并使用WebSocketServerHandshaker来进行握手和消息处理。
     *
     * 需要注意的是,WebSocketServerHandshaker是服务器端使用的组件,用于处理客户端的连接请求和消息。
     * 在客户端,可以使用WebSocketClientHandshaker来处理与服务器端的握手和消息交互。
     */
    private WebSocketServerHandshaker handshaker;

    /**
     * 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        SocketChannel channel = (SocketChannel) ctx.channel();
        logger.info("链接报告开始");
        logger.info("链接报告信息:有一客户端链接到本服务端");
        logger.info("链接报告IP:{}", channel.localAddress().getHostString());
        logger.info("链接报告Port:{}", channel.localAddress().getPort());
        logger.info("链接报告完毕");
        ChannelHandler.channelGroup.add(ctx.channel());
    }

    /**
     * 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.info("客户端断开链接{}", ctx.channel().localAddress().toString());
        ChannelHandler.channelGroup.remove(ctx.channel());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        //判断是否为完整的http
        if (msg instanceof FullHttpRequest) {

            FullHttpRequest httpRequest = (FullHttpRequest) msg;
            //判断是否解码成功
            if (!httpRequest.decoderResult().isSuccess()) {
                //如果不成功,返回一个400 Bad Request响应给客户端
                DefaultFullHttpResponse httpResponse =
                        new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);

                // 如果响应失败将错误信息写入响应体
                if (httpResponse.status().code() != 200) {
                    ByteBuf buf = Unpooled.copiedBuffer(httpResponse.status().toString(), CharsetUtil.UTF_8);
                    httpResponse.content().writeBytes(buf);
                    buf.release();
                }
                // 如果是非Keep-Alive,关闭连接
                ChannelFuture f = ctx.channel().writeAndFlush(httpResponse);
                if (httpResponse.status().code() != 200) {
                    f.addListener(ChannelFutureListener.CLOSE);
                }

                return;
            }
            /**WebSocketServerHandshakerFactory简介
             * WebSocketServerHandshakerFactory是Netty框架中用于创建WebSocketServerHandshaker实例的工厂类。
             * 它提供了一些静态方法和构造函数,用于创建不同类型的WebSocketServerHandshaker实例。
             *
             * WebSocketServerHandshaker是Netty中用于处理WebSocket握手和消息处理的核心组件之一。
             * 它负责处理WebSocket连接的握手过程,并提供了一些方法用于发送和接收WebSocket消息。
             *
             * WebSocketServerHandshakerFactory的构造函数接受三个参数:
             *
             * webSocketURL:WebSocket的URL地址,例如"ws://localhost:8080/websocket"。
             * subprotocols:可选参数,表示支持的子协议(Subprotocol),用于在握手过程中进行协商。默认为null,表示不进行子协议的协商。
             * allowExtensions:可选参数,表示是否允许使用WebSocket扩展。默认为false,表示不允许使用扩展。
             * WebSocketServerHandshakerFactory提供了两个主要的静态方法来创建WebSocketServerHandshaker实例:
             *
             * newHandshaker(webSocketURL, subprotocols, allowExtensions):
             * 根据给定的WebSocket URL、子协议和扩展设置,创建一个WebSocketServerHandshaker实例。
             * 如果无法创建合适的实例,则返回null。
             * sendUnsupportedVersionResponse(channel):发送一个不支持的WebSocket版本响应给客户端。
             * 当无法创建WebSocketServerHandshaker实例时,可以使用此方法发送一个不支持的版本响应给客户端。
             * 通过WebSocketServerHandshakerFactory创建的WebSocketServerHandshaker实例可以用于处理WebSocket
             * 连接的握手和消息,实现双向通信。具体的握手过程和消息处理逻辑需要在实际业务中进行编写。
             *
             */
            //WebSocketServerHandshaker实例的工厂类用于管理WebSocketServerHandshaker
            WebSocketServerHandshakerFactory wsFactory =
                    new WebSocketServerHandshakerFactory("ws:/" +
                            ctx.channel() + "/websocket", null, false);
            //根据http请求创建合适的WebSocketServerHandshaker来进行后续的握手处理和消息交互
            handshaker = wsFactory.newHandshaker(httpRequest);
            if (null == handshaker) {
                //如果版本不支持,发送一个不支持的WebSocket版本响应给客户端
                WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            } else {
                //执行握手操作建立连接
                handshaker.handshake(ctx.channel(), httpRequest);
            }

            return;
        }
        /**WebSocketFrame
         * WebSocketFrame是WebSocket协议中的基本数据传输单元,用于在客户端和服务器之间传递数据。
         * WebSocketFrame包含了一些元数据(如操作码、负载长度、掩码等)和负载(即实际的数据)。
         *
         * WebSocketFrame有6种操作码:
         *
         * Continuation Frame:用于将一个完整的消息分成多个帧进行传输。
         * Text Frame:用于传输文本消息。
         * Binary Frame:用于传输二进制数据。
         * Close Frame:用于关闭连接。
         * Ping Frame:用于检测连接是否存活。
         * Pong Frame:对Ping Frame的响应。
         * 除了Continuation Frame外,其他5种操作码都可以携带负载数据。负载长度字段表示负载数据的长度,
         * 可以是0到2^64-1之间的任意值。如果负载长度小于126,则该字段就是实际的负载长度;
         * 如果负载长度大于等于126小于等于2^16-1,则该字段的值为126,后续的2个字节表示实际的负载长度;
         * 如果负载长度大于2^16-1,则该字段的值为127,后续的8个字节表示实际的负载长度。
         *
         * WebSocketFrame还有一个掩码字段,用于对负载数据进行掩码处理,以保证数据传输的安全性。
         * 掩码字段的值是4个随机字节,客户端发送的帧需要进行掩码处理,服务器接收到的帧需要进行反掩码处理。
         *
         * 总之,WebSocketFrame是WebSocket协议中的基本数据传输单元,通过不同的操作码和负载数据来实现不同的功能。
         * 在实际应用中,我们通常使用WebSocket客户端库或服务器库来封装WebSocketFrame的细节,以方便进行WebSocket通信。
         */
        if (msg instanceof WebSocketFrame) {

            WebSocketFrame webSocketFrame = (WebSocketFrame) msg;

            //关闭请求
            if (webSocketFrame instanceof CloseWebSocketFrame) {
                handshaker.close(ctx.channel(), (CloseWebSocketFrame) webSocketFrame.retain());
                return;
            }

            //ping请求
            if (webSocketFrame instanceof PingWebSocketFrame) {
                //回复一个Pong帧作为响应
                ctx.channel().write(new PongWebSocketFrame(webSocketFrame.content().retain()));
                return;
            }

            //只支持文本格式,不支持二进制消息
            if (!(webSocketFrame instanceof TextWebSocketFrame)) {
                throw new Exception("仅支持文本格式");
            }

            String request = ((TextWebSocketFrame) webSocketFrame).text();
            System.out.println("服务端收到:" + request);
            ClientMsgProtocol clientMsgProtocol = JSON.parseObject(request, ClientMsgProtocol.class);
            //1请求个人信息
            if (1 == clientMsgProtocol.getType()) {
                ctx.channel().writeAndFlush(MsgUtil.buildMsgOwner(ctx.channel().id().toString()));
                return;
            }
            //群发消息
            if (2 == clientMsgProtocol.getType()) {
                TextWebSocketFrame textWebSocketFrame = MsgUtil.buildMsgAll(ctx.channel().id().toString(),
                        clientMsgProtocol.getMsgInfo());
                ChannelHandler.channelGroup.writeAndFlush(textWebSocketFrame);
            }

        }

    }

    /**
     * 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        logger.info("异常信息:\r\n" + cause.getMessage());
    }

}

主要代码大概就是这样了,如果想看完整代码的可以看绑定资源

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐