1

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...

本文源码

参考

关注我的公众号

扫码关注


叉叉哥
3.8k 声望60 粉丝