文章目录
  1. 1. WebSocket 协议介绍
  2. 2. WebSocket 建立连接的过程
    1. 2.1. 握手阶段( handshake )
    2. 2.2. 数据传输阶段( data transfer )
  3. 3. Netty 实现 WebSocket 服务器
  4. 4. 握手完成事件监听
  5. 5. 参考
  6. 6. 本文源码

WebSocket 协议介绍

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

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

WebSocket 建立连接的过程

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

握手阶段( handshake )

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

1
2
3
4
5
6
7
8
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。服务器进行如下响应完成握手:

1
2
3
4
5
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 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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));
}
}
}

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

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

握手完成事件监听

如果想要获取客户端连接 WebSocket 服务器使用的请求 URL,包括 URL 中的参数,以及请求的 Header,例如想要通过请求 Header 中的 token 对用户进行认证,可以在握手成功后处理这些。

在 Netty 源码 WebSocketServerProtocolHandler.java 第 216 行 代码中可以看到,WebSocketServerProtocolHandler 会动态在 pipeline 中添加一个 WebSocketServerProtocolHandshakeHandler,用于处理握手阶段。WebSocketServerProtocolHandshakeHandler 第 112 行 处理握手成功后,会触发一个 WebSocketServerProtocolHandler.HandshakeComplete 事件。

下面在 MyWebSocketServerHandler 中添加重写 userEventTriggered 方法,监听握手成功后的事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyWebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

@Override
protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
// ......
}

@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
WebSocketServerProtocolHandler.HandshakeComplete handshakeCompletedEvent = (WebSocketServerProtocolHandler.HandshakeComplete) evt;
String uri = handshakeCompletedEvent.requestUri(); // 握手请求 URI
HttpHeaders headers = handshakeCompletedEvent.requestHeaders(); // 握手请求头
}
}
}

参考

本文源码

文章目录
  1. 1. WebSocket 协议介绍
  2. 2. WebSocket 建立连接的过程
    1. 2.1. 握手阶段( handshake )
    2. 2.2. 数据传输阶段( data transfer )
  3. 3. Netty 实现 WebSocket 服务器
  4. 4. 握手完成事件监听
  5. 5. 参考
  6. 6. 本文源码