基于 Netty 实现 WebSocket 服务器

叉叉哥

WebSocket 协议介绍

WebSocket 协议是一种在单个 TCP 连接上进行全双工通信的协议,在建立连接完成握手阶段后,服务端也可以主动推送数据给客户端,使得 Web 浏览器和服务器之间的交互性更强大。

目前 WebSocket 协议应用非常广泛,大部分浏览器均已支持 WebSocket,不仅仅在 Web 应用中,其他很多类型应用(例如游戏)也经常用到 WebSocket 协议。

WebSocket 建立连接的过程

WebSocket 分为握手阶段( handshake )和数据传输阶段( data transfer )。

握手阶段( handshake )

在客户端和服务器建立 WebSocket 连接之前,客户端首先要发送一个 HTTP 协议的握手请求:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

其中请求头 Connection: UpgradeUpgrade: websocket 表示客户端想要升级协议为 WebSocket。服务器进行如下响应完成握手:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

完成握手后,接下来就是双向的数据传输的过程。

数据传输阶段( data transfer )

数据传输阶段传输的内容以帧( frame )为单位,其中分为控制帧(Control Frame)和数据帧(Data Frame):

  • 控制帧(Control Frame):包括 ClosePingPong 帧,Close 用于关闭 WebSocket 连接,PingPong 用于心跳检测
  • 数据帧(Data Frame):包括 TextBinary 帧,分别用于传输文本和二进制数据

Netty 实现 WebSocket 服务器

public class WebSocketServer {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new HttpServerCodec()); // HTTP 协议解析,用于握手阶段
                            pipeline.addLast(new HttpObjectAggregator(65536)); // HTTP 协议解析,用于握手阶段
                            pipeline.addLast(new WebSocketServerCompressionHandler()); // WebSocket 数据压缩扩展
                            pipeline.addLast(new WebSocketServerProtocolHandler("/", null, true)); // WebSocket 握手、控制帧处理
                            pipeline.addLast(new MyWebSocketServerHandler());
                        }
                    });
            ChannelFuture f = b.bind(8080).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

class MyWebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
        if (frame instanceof TextWebSocketFrame) { // 此处仅处理 Text Frame
            String request = ((TextWebSocketFrame) frame).text();
            ctx.channel().writeAndFlush(new TextWebSocketFrame("收到: " + request));
        }
    }
}

以上是 Netty 实现的一个简单的 WebSocket 的服务器。启动成功后,可以网上搜索 WebSocket 在线测试工具连接 ws://localhost:8080/ 进行测试。

源代码分析

WebSocketServerProtocolHandler 会帮我们处理握手、ClosePingPong 帧等 WebSocket 协议底层,并且将 TextBinary 数据帧传递给 pipeline 中下一个 handler。也就是在下一个 handler 中,我们只需要实现业务逻辑而无需关注 WebSocket 协议本身的细节。

WebSocketServerProtocolHandler.java 216 行代码中,会在 pipeline 中添加一个 WebSocketServerProtocolHandshakeHandler,用于处理握手阶段。具体代码位置:
https://github.com/netty/nett...

WebSocketServerProtocolHandshakeHandler 处理握手请求并响应,同时它会将自身从 pipeline 中移除,因为握手在建立 TCP 连接后仅需要处理一次。具体代码位置:
https://github.com/netty/nett...

本文源码

参考

关注我的公众号

扫码关注

阅读 762

Java论道
定期分享Java技术文章,囊括Spring全家桶、Linux、架构设计、分布式、微服务、消息中间件、面试题等。微...
3.7k 声望
52 粉丝
0 条评论
你知道吗?

3.7k 声望
52 粉丝
宣传栏