WebSocket是HTML5开始提供的一种浏览器与服务器之间进行全双工通信的网络技术。
在WebSocketAPI中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道,两者就可以直接互相传送数据了。WebSocket基于TCP双向全双工进行消息传递,在同一时刻,既可以发送消息,也可以接收消息,相比于HTTP的半双工协议,性能得到很大提升。
与WebSocket相比,HTTP协议的弊端:
(1)HTTP协议为半双工协议。半双工协议指数据可以在客户端和服务端两个方向上传输,但是不能同时传输。它意味着在同一时刻,只有一个方向上的数据传送。
(2)HTTP消息冗长而繁琐。HTTP包含消息头、消息体、换行符等,通常情况下采用文本方式传输,相比于其他的二进制通信协议,冗长而繁琐。
(3)想要实现服务器推送只能使用长轮询。
WebSocket连接建立
建立WebSocket连接时,需要通过客户端或者浏览器发出握手请求,请求消息如下:
为了建立一个WebSocket连接,客户端浏览器首先要向服务器发起一个HTTP请求,这个请求和通常的HTTP请求不同,其中附加头信息“Upgrade:websocket”表明这是一个申请协议升级的HTTP请求。
服务端解析这些请求附加的头消息,然后生成应答消息返回客户端,客户端和服务端的WebSocket连接就建立起来了,双方可以通过这个连接自由地传递信息,并且这个连接会一直存在知道客户端或者服务端的任意一方主动关闭连接。
请求消息中的“Sec-WebSocket-Key”是随机的,服务器端会用这些数据来构造出一个SHA-1的信息摘要,把“Sec-WebSocket-Key”加上一个字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,使用SHA-1加密,然后进行BASE-64编码,将结果作为“Sec-WebSocket-Accept”头的值,返回客户端。
WebSocket连接关闭
为了关闭WebSocket连接,客户端和服务端需要安全的关闭底层TCP连接以及TLS会话。
底层的TCP连接,在正常情况下,应该首先由服务器关闭。在异常情况下(例如在一个合理的时间周期后没有接收到服务器的TCPClose),客户端可以发起TCP Close。因此,当服务器被指示关闭WebSocket连接时,它应该立即发起一个TCPClose操作;客户端应该等待服务器的TCPClose。
WebSocket的握手关闭消息带有一个状态码和一个可选的关闭原因,它必须按照协议要求发送一个Close控制帧,当对端接收到关闭控制帧指令时,需要主动关闭WebSocket连接。
Netty构建WebSocket服务端示例
启动类WebSocketServer:
package com.netty.websocket.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
public class WebSocketServer {
public void run(int port) throws Exception {
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
protected void initChannel(SocketChannel ch) throws Exception {
// 创建一个管道,每一个通道都有一个管道
ChannelPipeline pipeline = ch.pipeline();
// 添加http-codec处理程序,它包含http-encoder和http-decoder
pipeline.addLast("http-codec", new HttpServerCodec());
// 添加构建一个指定maxContentLength大小的HttpObjectAggregator
// 其将HttpMessage及其后续的HttpContent汇总为单个FullHttpRequest或FullHttpResponse
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
// 添加一个支持异步写入大数据流的handler
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
// 添加自定义的websocket处理程序
pipeline.addLast("handler", new WebSocketServerHandler());
}
});
// b.bind(port)将绑定端口port,并返回一个ChannelFuture对象
// 调用sync()方法将会等待这个ChannelFuture运行,直到完成
// 调用channel()方法将返回此ChannelFuture关联的Channel
Channel ch