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: Upgrade
和 Upgrade: 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):包括
Close
、Ping
、Pong
帧,Close
用于关闭 WebSocket 连接,Ping
和 Pong
用于心跳检测
- 数据帧(Data Frame):包括
Text
和 Binary
帧,分别用于传输文本和二进制数据
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()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new WebSocketServerCompressionHandler()); pipeline.addLast(new WebSocketServerProtocolHandler("/", null, true)); 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) { String request = ((TextWebSocketFrame) frame).text(); ctx.channel().writeAndFlush(new TextWebSocketFrame("收到: " + request)); } } }
|
WebSocketServerProtocolHandler
会帮我们处理握手、Close
、Ping
、Pong
帧等 WebSocket 协议底层,并且将 Text
和 Binary
数据帧传递给 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(); HttpHeaders headers = handshakeCompletedEvent.requestHeaders(); } } }
|
参考
本文源码