基于 Netty 实现 WebSocket 服务器

WebSocket 协议介绍

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

目前 WebSocket 协议应用很是普遍,大部分浏览器均已支持 WebSocket,不单单在 Web 应用中,其余不少类型应用(例如游戏)也常常用到 WebSocket 协议。java

WebSocket 创建链接的过程

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

握手阶段( handshake )

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

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。服务器进行以下响应完成握手:web

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

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

数据传输阶段( 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 协议自己的细节。websocket

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

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

参考

关注个人公众号

扫码关注