这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战
基本概念
- WebSocket时HTML中新增的协议,目的是在浏览器和服务器之间建立一个不受限的双向通信的通道
- 传统的HTTP协议不能实现浏览器和服务器之间双向通信的问题:
- HTTP协议是一个请求-响应协议,请求必须先由浏览器发送给服务器,服务器才能响应这个请求.也就是说 ,HTTP协议是由浏览器到服务器的单向请求协议,浏览器不主动请求,服务器是无法主动发数据给浏览器的
- HTTP协议也可以使用轮询和Comet实现类似双向通信的功能:
- 轮询:
- 浏览器通过JavaScript启动一个定时器,然后以固定的间隔向服务器发送请求获取服务器的新消息响应
- 轮询的缺点是消息无法保证实时性并且频繁的请求会给服务器带来极大的压力
- Comet:
- Comet本质上也是轮询,在没有新消息的情况下,服务器会延迟等待一些时间,等到有消息了再响应
- Comet解决了实时性的问题,但也引发新的问题:
- 以多线程运行的服务器会让大部分线程大部分时间都处于挂起状态,极大浪费服务器资源
- 一个HTTP连接在长时间没有数据传输的情况下,链路上的网关会关闭这个连接,而且这个网关是不可控的,所以要求Comet连接必须定时发一些ping数据表示正常工作
- 轮询:
- HTML5推出的WebSocket标准,可以让浏览器和服务器之间建立无限制的全双工通信,任何一方都可以主动发请求消息给对方
- WebSocket在首先建立长连接时,必须先由浏览器发起,因为WebSocket请求协议也是一个标准的HTTP请求
- WebSocket请求和普通HTTP请求比较:
- GET请求的地址不是以http:// 开头,而是以ws:// 开头
- 请求头中包含Connection:Upgrade和Upgrade:websocket来表明这个连接将要被转换为WebSocket连接
- Sec-WebSocket-Key用于标识这个连接,这是一个BASE64的密文,服务器也会响应一个对应加密的Sec-WebSocket-Accept头信息作为应答,这里的Sec-WebSocket-Accept是服务器和客户端一致的密钥计算出来的值
- Sec-WebSocket-Version指定了WebSocket的协议版本
- 服务器会响应的101的HTTP状态码来表明服务器已经识别并切换WebSocket协议
WebSocket的使用
- Java WebSocket应用由一系列WebSocket Endpoint组成:
- Endpoint是一个Java对象,表示WebSocket连接的一端
- 对于服务端而言 ,Endpoint可以看作是处理具体WebSocket消息的接口,类似与Servlet和HTTP请求的关系一样
- 定义Endpoint的方式有两种:
- 注解式: 定义一个POJO, 并添加 @ServerEndpoint注解
- 编程式: 继承类javax.websocket.Endpoint并实现类中的方法
- Endpoint实例在WebSocket握手时创建,并在客户端与服务端连接过程中有效,最后在连接关闭时结束
- Endpoint类中定义了Endpoint相关的生命周期方法,规范了实现者确保在生命周期的各个阶段调用实例的方法:
| 方法 | 注解 | 描述 |
|---|---|---|
| OnOpen() | @OnOpen | 开启一个新会话时调用 该方法是客户端与服务端握手成功后调用的方法 |
| OnClose() | @OnClose | 关闭一个会话时调用 |
| OnError() | @OnError | 连接过程异常时调用 |
- Websocket处理消息:
- WebSocket接收消息:
- 采用注解式定义Endpoint时: 通过 @OnMessage指定接收消息的方法
- 采用编程式定义Endpoint时: 通过为参数Session添加MessageHandler消息处理器接收消息
- WebSocket发送消息:
- WebSocket发送消息由RemoteEndpoint完成:
- 由Session维护一个RemoteEndpoint实例
- 根据使用情况,可以通过Session.getBasicRemote获取同步消息发送的实例
- 然后调用获取的实例的sendXxx() 方法发送消息
- 根据使用情况,可以通过Session.getAsyncRemote获取异步消息发送的实例
- WebSocket发送消息由RemoteEndpoint完成:
- WebSocket接收消息:
WebSocket使用示例
- WebSocket示例的实现流程:
- 消息格式:
- 客户端 -> 服务端:
{ // 消息内容 "content":"", // 发送人 "fromUser":"", // 接收人 "toUser":"", }- 服务端 -> 客户端:
// 用户类型数据:type = user { // 用户列表数据 "data":"", // 数据类型 - 用户数据 "type":"user", } // 消息类型数据:type = message { // 消息数据 "data":"", // 发送人 "fromUser":"", // 接收人 "toUser":"", // 数据类型 - 消息数据 "type":"message", }