基于Netty的WebSocket服务端

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

前面用了两节内容介绍了基于Netty的TCP的Socket的相关内容。这一节开始我们介绍基于Netty的WebSocket的相关内容,我们同样可以按照服务端和客户端的方式分别介绍。本节我们介绍WebSocket的服务端。

02 示例代码

java 复制代码
@Slf4j
@Component
public class WebSocketServer {

    @Getter
    private ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    public void start() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workGroup);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>(){

            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();
                pipeline.addLast(new HttpServerCodec());
                pipeline.addLast(new HttpObjectAggregator(65535));
                pipeline.addLast(new WebSocketServerProtocolHandler("/testWs"));
                // 自定义的handler,处理业务逻辑
                pipeline.addLast(new WebBusinessHandler(channelGroup));
            }
        });

        // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
        ChannelFuture channelFuture = serverBootstrap.bind(9090).sync();
        log.info("Server started and listen on:{}",channelFuture.channel().localAddress());
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    }
}

2.1 引导类

引导类同样是io.netty.bootstrap.ServerBootstrap,和TCP协议的服务端一样,但是细节不同。

java 复制代码
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();

ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup);
serverBootstrap.channel(NioServerSocketChannel.class);

保活的配置一般会有心跳的代替,所以一般也就没有要设置了。线程组可以根据需要设置,一个接收任务,一个处理任务分工协作。

2.2 编解码器

java 复制代码
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(65535));
pipeline.addLast(new WebSocketServerProtocolHandler("/testWs"));
// 自定义的handler,处理业务逻辑
pipeline.addLast(new WebBusinessHandler(channelGroup));

编解码同样是WebSocket中的重要配置类。WebSocket握手阶段需要遵守HTTP协议,自然少不了请求请求的解码以及响应的解码。Netty框架本身就提供了这样的编解码器:

  • io.netty.handler.codec.http.HttpRequestDecoder:请求解码器
  • io.netty.handler.codec.http.HttpResponseEncoder:响应编码器

配置这样的编解码器自然没有问题。然而Netty框架还提供了更加简便的二合一编解码器:

  • io.netty.handler.codec.http.HttpServerCodec

注释中已经说明HttpServerCodecHttpRequestDecoderHttpResponseEncoder的结合。所以我们直接用它即可。

解码之后的数据分成两部分:

  • HttpMessage:包含HTTP请求的通用信息
  • LastHttpContent:最新具有标记的HttpContent

io.netty.handler.codec.http.HttpObjectAggregator是一个HTTP聚合器,可以将HttpMessageHttpContent聚合成FullHttpRequestFullHttpResponse

案例也给除了用在HttpServerCodec之后。

io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler专门处理WebSocket握手和帧,参数表示WebSocket路径。

这里所说的帧就是WebSocketFrame,主要常用的帧:

  • TextWebSocketFrame:处理文本
  • PingWebSocketFrame:握手请求
  • PongWebSocketFrame:握手响应
  • BinaryWebSocketFrame:处理二进制

我们常用的是TextWebSocketFrame文本帧。

2.3 自定义处理器

java 复制代码
@Slf4j
public class WebBusinessHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    private ChannelGroup channelGroup;

    public WebBusinessHandler(ChannelGroup channelGroup) {
        this.channelGroup = channelGroup;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // 建立客户端
        Channel channel = ctx.channel();
        log.info("客户端建立连接:channelId={}", channel.id());
        channelGroup.add(channel);

    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        // 断开链接
        Channel channel = ctx.channel();
        log.info("客户端断开连接:channelId={}", channel.id());
        channelGroup.remove(channel);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        // 接受消息
        Channel channel = ctx.channel();
        log.info("收到来自通道channelId[{}]发送的消息:{}", channel.id(), msg.text());

        // 广播通知所有的客户端
        channelGroup.writeAndFlush(new TextWebSocketFrame("收到来自channelId[" + channel.id() + "]发送的消息:" + msg.text() + "123_"));
    }
}

里面的方法和TCP协议的一致。但是要说明的就是里面的参数

io.netty.channel.group.ChannelGroup

这是一个Channel的通道组,用来管理所有的通道,包括通道的新增、剔除以及消息的发送。

SimpleChannelInboundHandler的泛型我们限制为TextWebSocketFrame,否则获取到的消息就是FullHttpRequest类型。

消息发送时,同样使用的TextWebSocketFrame文本帧。

2.4 其他编解码

HttpServerCodecHttpObjectAggregator之间还可以加入两个处理器:

  • ObjectEncoder
  • ChunkedWriteHandler

ObjectEncoder是为了将Java对象序列化成ByteBuf

ChunkedWriteHandler增加了对异步写入大型数据流的支持,既不会花费大量内存,也不会获得OutOfMemoryError

2.5 绑定端口

java 复制代码
// 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
ChannelFuture channelFuture = serverBootstrap.bind(9090).sync();
log.info("Server started and listen on:{}",channelFuture.channel().localAddress());
// 对关闭通道进行监听
channelFuture.channel().closeFuture().sync();

这个之前已经讲过,这里不在赘述。

03 测试

测试的之后,我们采用在线WebSocketk客户端:

webfem.com/tools/ws/in...

3.1 试错01

因为我们之前在自定义处理器的泛型是TextWebSocketFrame,我们改成Object,看看默认的类型到底是什么?发送数据我们也采用直接发送的形式,看看能否成功?

java 复制代码
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
    log.info("msg类型:{}", msg.getClass());
    // 接受消息
    Channel channel = ctx.channel();
    log.info("收到来自通道channelId[{}]发送的消息:{}", channel.id(), msg);

    // 广播通知所有的客户端
    channelGroup.writeAndFlush("收到来自channelId[" + channel.id() + "]发送的消息:" + msg + "123_");
}

我们可以看到,发出去的消息没有响应。打印Msg类型确实是io.netty.handler.codec.http.websocketx.TextWebSocketFrame。服务端像客户端发送的消息客户端也没有收到。

3.2 试错02

我们知道编解码是顺序执行的。有没有和我一样,有这样的疑问:

WebSocketServerProtocolHandler有没有可能不受顺序的影响,因为它是一个路径,我们改变一下试试。

效果:

结果肯定是不行的,从报错信息来看,WebSocketServerProtocolHandler不仅提供了连接的路径,还对ByteBuf做了一定的转化。顺序不可妄动。

04 小结

到这里WebSocket的服务端的内容就差不多了,本节的客户端采用了网上在线工具。下一节我们将通过两种方式手搓客户端连接我们的WebSocket服务。

相关推荐
m0_7482331715 小时前
C与C++:底层编程的六大核心共性
java·开发语言
坊钰15 小时前
【Rabbit MQ】Rabbit MQ 介绍
java·rabbitmq
雀啼春15 小时前
Java中的数据类型
java
80530单词突击赢16 小时前
C++关联容器深度解析:set/map全攻略
java·数据结构·算法
兩尛16 小时前
c++知识点1
java·开发语言·c++
ONE_PUNCH_Ge16 小时前
Go 语言泛型
开发语言·后端·golang
舟舟亢亢16 小时前
JVM复习笔记——下
java·jvm·笔记
rainbow688916 小时前
Python学生管理系统:JSON持久化实战
java·前端·python
良许Linux16 小时前
DSP的选型和应用
后端·stm32·单片机·程序员·嵌入式