文章目录
前言
我们在使用 Spring 做网页开发的时候,我们后端服务器的主要作用就是处理前端、客户端发送来的请求,也就是说我们的服务器是被动接受请求的一方,而在某些时候,需要我们的服务器主动向客户端发送网络数据包,例如购物软件的降价通知,聊天软件别人发送消息时候的通知,这些都需要服务器主动向客户端发送网络数据包。
浏览器像服务器发送网络请求,应用层使用的协议往往是 HTTP 或者 HTTPS 协议,而 HTTP 协议在发展的时候却没有涉及到服务器主动向客户端发送网络数据包的功能,因为 HTTP 的发展初衷就是为了人们通过网络能够看报纸、新闻的,也就没想到服务器能够通过 HTTP 协议主动向客户端发送网络数据包,所以通过 HTTP 协议是无法实现服务器主动向客户端发送网络数据包的功能的。
那么如何实现服务器主动向客户端发送请求的功能呢?这就需要使用到另外一种应用层协议——WebSocket 了。
什么是 WebSocket
来看看百度的解释:
WebSocket是一种在单个TCP连接上进行全双工通信的协议。它允许服务端主动向客户端推送数据,同时也支持客户端向服务端发送数据,实现了真正的双向通信。WebSocket协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。WebSocket API也被W3C定为标准,使得浏览器和服务器之间的数据交换变得更加简单和高效。
传统的应用层使用其他协议的 web 程序,都是属于”一问一答“形式,客户端给服务器发送 HTTP 请求之后,服务器给客户端返回一个 HTTP 响应,在这种情况下,服务器是属于被动的一方,如果客户端不主动发起请求,服务器无法主动给客户端响应。而 WebSocket 则是更接近于 TCP 这种级别的通信方式,一旦建立连接,客户端和服务端都可以主动向对方发送数据。
WebSocket 协议和 HTTP 协议的区别
- 协议性质
- WebSocket:是一种在单个TCP连接上进行全双工通信的协议。它允许服务端主动向客户端推送数据,使得客户端和服务器之间的数据交换变得更加简单和高效。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。(来源:百度百科)
- HTTP:全称超文本传输协议(HyperText Transfer Protocol),是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP是一个基于TCP/IP通信协议来传递数据的协议,客户端发送请求,服务器返回响应。HTTP是无连接的,即每次连接只处理一个请求,服务器处理完客户请求,并收到客户的应答后,就断开连接。(来源:知乎专栏)
- 通信方式
- WebSocket:支持双向通信,即客户端和服务器可以同时向对方发送数据。这种全双工的通信方式使得WebSocket特别适用于需要实时数据交换的场景,如在线聊天、实时数据更新等。
- HTTP:是单向的、请求-响应模式的协议。客户端发起请求,服务器返回响应,然后连接断开。如果需要再次交换数据,必须重新建立连接。
- 连接状态
- WebSocket:是有状态的协议。一旦建立了WebSocket连接,客户端和服务器之间的通信就可以持续进行,直到连接被关闭。这种持久连接减少了因频繁建立连接而产生的开销。
- HTTP:是无状态的协议。HTTP协议对事务处理没有记忆能力,即服务器不会记住任何关于客户端请求的信息。如果需要处理多个请求之间的关联,必须在每个请求中携带必要的状态信息。
- 数据传输效率
- WebSocket:由于使用了长连接和较少的控制开销(如头部信息较小),WebSocket在数据传输效率上优于HTTP。特别是在需要频繁交换小量数据的场景中,WebSocket能够显著减少网络带宽的消耗。
- HTTP:每次请求都需要携带完整的头部信息,这可能导致在传输小量数据时头部信息的开销占比较大。此外,HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,从而浪费了网络带宽。
WebSocket 原理解析
WebSocket 本质上是一个基于 TCP 的协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加的头信息,通过这些附加的头信息来完成握手过程。
那么这些附加的头信息是哪些呢?
对于浏览器发送的请求数据包的头信息中,附加的信息有:
- Connection: upgrade。表示我需要升级协议
- Sec-WebSocket-Accept: xxxxxx。服务端与该客户端通讯的钥匙
- Sec-WebSocket-Version: 13。升级的协议的版本
- Upgrade: websocket。升级的协议格式
WebSocket 报文格式
WebSocket 协议的相关信息大家可以去官方文档中查看:https://www.rfc-editor.org/rfc/rfc6455
- FIN 表示是否要关闭 WebSocket,为 1 表示断开 WebSocket 连接,这里的 FIN 和 TCP 报文中的 FIN 不是一个概念。
- RSV1/RSV2/RSV3:保留位,现在先不用,但是不保证后面可能会用到,值一般为 0。
- opcode:操作代码,决定了如何理解后面的数据载荷。
-
- %x0:表示这是一个延续帧。当 opcode 为0,表示本次数据传输采用了数据分片,当前收到的帧为其中一个分片
-
- %x1:表示这是文本帧,也就是载荷中的数据是文本类型
-
- %x2:表示这是二进制帧,也就是载荷中的数据是二进制类型
-
- %x3-7:保留,暂未使用
-
- %x8:表示连接断开
-
- %x9:表示 ping 帧
-
- %xA:表示 pong 帧
-
- %xB-F:保留,暂未使用
- mask:表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作
- Payload length:数据载荷的长度,单位是字节,能表示的范围是0-127
- Extended Payload length:扩展的载荷长度,127字节的大小肯定是不够用的,所以就出现了扩展载荷,当Payload length的值0-125的时候表示扩展载荷的长度为0,Payload length的值为126时,表示扩展载荷的长度为16位,值为127时,表示扩展载荷的长度为64位
- Masking-key:0或者4字节(32位)所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,mask 为 1,且携带了4字节的Masking-key。如果 mask 为0.则没有 Masking-key
- Payload Data:报文携带的载荷数据
websocket 载荷的长度可以是 6比特位,单位是字节能表示的大小,也可以是 16比特位、或者64比特位能表示的范围大小。
使用掩码算法的目的主要是从安全角度考虑,避免一些缓冲区溢出攻击。
我们可以直接使用 tomcat 提供的 WebSocket 的原生 API,也可以使用 Spring 内置的 WebSocket,其实这两者区别不大。
Spring 中 WebSocket 的使用
首先我们在创建 Spring 项目的时候需要添加进去 WebSocket 依赖。
也可以自己手动添加 websockt 依赖。
添加完依赖之后,我们创建一个类,继承TextWebSocketHandler
类,如果你的 websocket 中的载荷的数据类型是二进制类型的话,就继承 BinaryWebSocketHandler
类:
package com.example.websocket.component;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.handler.TextWebSocketHandler;
@Component
public class TestWebSocketComponent extends TextWebSocketHandler {
}
TextWebSocketHandler 父类中的方法有很多:
我们需要重写的方法主要就是:afterConnectionEstablished
、handleTextMessage
、handleTransportError
、afterConnectionClosed
方法。
- afterConnectionEstablished: 方法表示客户端和服务端建立 websocket 连接之后执行的方法
- handleTextMessage:方法表示对方传来 websocket 数据帧的时候执行的方法
- handleTransportError:方法表示 websocket 连接出现异常的时候执行的方法
- afterConnectionClosed: 方法表示关闭 websocket 连接后执行的方法
重写完成 TextWebSocketHandler 类之后,就需要将这个实例给注册到 spring 中,进行路由的配置:
创建一个类,实现 WebSocketConfigurer
接口,并且实现接口中的 registerWebSocketHandlers
方法:
package com.example.websocket.config;
import com.example.websocket.component.TestWebSocketComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.