背景介绍
早期,很多网站为了实现推送技术,所用的技术都是轮询(也称为短轮询)。轮询是指浏览器每隔一段时间向服务器发送 HTTP 请求,然后服务器返回最新的数据给客户端。
常见的轮询方式分为轮询和长轮询,他们的区别如下图所示:
为了更加直观的感受轮询与短轮询之间的区别,我们来看一下具体代码:
短轮询的方式有一个明显的缺点,即浏览器需要不断的向服务器发出请求,然而 HTTP 请求与响应可能会包含较长的头部,其中真正有效的数据可能只是一小部分,所以这样会浪费很多的带宽资源。
在下棋游戏过程中轮询操作就会出现下图情况,
很明显,像这样的轮询操作,开销是比较大的,而且成本也比较高。如果轮询间隔时间太长,玩家1 落子之后,玩家2 就不能及时拿到结果,如果轮询间隔时间太短,虽然即时性得到改善,但是玩家2 会多次发送请求,这将会浪费更多的机器资源。
WebSocket简介
websocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。
websocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 websocket API中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
接下来我们用一张图看一下 XHR Polling(短轮询)与 websocket 之间的区别。
websocket 优点
- 较少的控制开销,在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据报头部相对较小。
- 更强的实时性,由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求,服务端才能响应,延迟明显更少。
- 保持连接状态,与 HTTP 不同的是,websocket 需要先创建连接,这就使得其成为一种有状态的协议(之后通信时可以省略部分状态信息)。
- 更好的二进制支持,websocket 定义了二进制帧,相对 HTTP 而言,可以更轻松的处理二进制内容。
- 可以支持扩展,websocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。
WebSocket报文格式
websocket也是一个应用层协议,下层是基于TCP的。
**FIN:**标识是否结束
**RSV:**保留位,未来可能会用到,但是现在没有用
**opcode:**描述了当前 websocket 报文的类型,(文本帧、二进制帧、ping帧、pong帧...)
**payload len:**表示的是当前数据报携带的数据载荷的长度,这个字段本身就是一个变长的,一个 websocket 数据报能承载的载荷长度是非常非常长的。
**payload data:**实际报文要传输的数据载荷
WebSocket握手过程(建立连接的过程)
使用网页端,尝试和 服务器建立 websocket 连接。网页端首先给服务器发送一个 HTTP 请求,这个 HTTP 请求中会带有特殊的 header
Connection:Upgrade
Upgrade:Websocket
这两个 header 的作用就是告诉服务器,我要进行协议升级。
如果服务器支持 websocket,就会返回一个特殊的 HTTP 响应,这个响应的状态码是 101(切换协议)。然后客户端和服务器之间就开始使用 websocket 来进行通信了。
WebSocket示例
java
@Component
public class TestAPI extends TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("连接成功");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("收到信息:" + message.getPayload());
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("连接异常");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("连接关闭");
}
}
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private TestAPI testAPI;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(testAPI, "/test");
}
}
**afterConnectionEstablished:**WebSocket 建立连接之后执行
**handleTextMessage:**收到前端发送的信息之后执行,可以处理接收的信息
**handleTransportError:**传输过程中遇到错误时执行
**afterConnectionClosed:**连接关闭之后执行,可以释放一些不需要的资源