背景
长轮询: 客户端每隔很短时间,都会对服务器发送请求,采用阻塞模型查看是否有新的消息,只要轮询速度足够快,就会有一种交互上的实时的错觉。实际上对服务端、客户端都造成很大的浪费
长链接:客户端只请求一次,但是服务器将会保持持久链接,不会返回结果,当服务器有了新数据,才返回结果,一直保持挂起状态,对服务端造成很大的浪费
- 服务端被迫维持来自每个客户端的大量不同的连接
- 大量的轮询请求会造成高开销,比如会带上多余的header,造成了无用的数据传输
WebSocket是HTML5开始提供一种在单个TCP连接上进行全双工通信的协议 ,来弥补HTTP协议在持久通信能力上的不足,允许服务端主动向客户端推送数据,2011年成为国际标准
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输
推荐文档:https://tools.ietf.org/html/rfc6455
socket套接字
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个 socket(套接字),上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口
特点
- 通信的基石,是支持TCP/IP协议的路通信的基本操作单元
- 建立网络通信连接至少要一对端口号,一个运行于客户端,为 Client Socket。一个运行于服务器端,为Server Socket
socket并不是协议,本质是对 TCP/IP 协议栈的封装,它提供了一个针对 TCP 或者 UDP 编程的接口,并不是另一种协议
客户端API
WebSocket.readyState
0: 表示正在连接
- 1: 表示连接成功,可以通信了
- 2: 表示连接正在关闭
- 3: 表示连接已经关闭,或者打开连接失败
const ws = new WebSocket("ws//:xxx.xx", [protocol])
ws.onopen = () => {
ws.send('hello')
console.log('send')
}
ws.onmessage = (ev) =>{
console.log(ev.data)
ws.close()
}
ws.onclose = (ev) =>{
console.log('close')
}
ws.onerror = (ev) =>{
console.log('error')
}
ws.send()
ws.close()
WebSockets的心跳(兵乓球)
- 客户端—》服务端:ping
- 服务端—》客户端:pong
webSocket与http
区别
webSocket | http | |
---|---|---|
数据流向 | 双向通信 | 单向数据流 |
协议开头 | ws:// || wss:// | http:// || https:// |
同源限制 | 无同源限制,可跨域 | 有同源限制 |
传输开销 | 数据格式轻量、数据包头部较小 | 头较大 |
相同点
- 都需要建立TCP连接
-
缺点
前后端维持住ws连接,否则无法推送接收消息,长连接需要业务代码更稳定
协议内容
```javascript GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
**Connection** 决定当前的事务完成后,是否会关闭网络连接。如果该值是“keep-alive”,网络连接就是持久的,不会关闭,使得对同一个服务器的请求可以继续在该连接上完成。**为****Upgrade,意思是****请求升级**<br />**Upgrade**:表示协议升级为webSocket协议<br />**Sec-WebSocket-Extensions:** WebSocket的扩展<br />**Sec-WebSocket-Key:** 是 client 发送的一个 base64 编码的密文,要求 server 必须返回一个对应加密的 "Sec-WebSocket-Accept" 应答,否则 client 会抛出 "Error during WebSocket handshake" 错误,并关闭连接<br />**Sec-WebSocket-Version:** 版本号
响应头<br />**Sec-WebSocket-Accept:**服务端先获得请求头部的Sec-WebSocket-Key值,然后再其后面连接一个GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11),对连接后的字符串做SHA1,得到16进制表示的字符串,将每两位当作一个字节进行分隔,得到字节数组,对字节数组做Base64,即得到Sec-WebSocket-Accept的值
<a name="izlV5"></a>
# socket.io 库
Socket.io不是Websocket而是一个封装了 Websocket、基于 Node 的 JavaScript 框架,它只是将Websocket和轮询 (Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。也就是说,Websocket仅仅是 Socket.io实现实时通信的一个子集,**屏蔽了所有底层细节,让顶层调用非常简单**
<a name="mRVCS"></a>
## 特点
- 易用性
- 跨平台
- 自适应
<br />
<a name="yW69r"></a>
## 应用场景
- 实时的聊天
- 数据实时分析,数据传输
- 文件协同合作<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/271643/1597305736647-ac8195f8-673d-4ba1-ad22-2a1ef2b09c77.png#align=left&display=inline&height=305&margin=%5Bobject%20Object%5D&name=image.png&originHeight=610&originWidth=1138&size=657999&status=done&style=none&width=569)
数据掩码的作用是增强协议的安全性
<a name="DLHww"></a>
## 客户端
地址:[https://socket.io/docs/client-api/](https://socket.io/docs/client-api/)<br />socket.io-client 源码分析博客:[https://blog.csdn.net/weixin_41855143/article/details/103334904?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-4-103334904.nonecase](https://blog.csdn.net/weixin_41855143/article/details/103334904?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-4-103334904.nonecase)<br />[https://github.com/MetinSeylan/Vue-Socket.io](https://github.com/MetinSeylan/Vue-Socket.io)
默认情况下,首先建立长轮询连接,然后将其升级为“更好的”传输(例如WebSocket),如下配置<br />io(url, { transports:[ 'websocket' ] })
<a name="psMHs"></a>
### API
- io(url)创建一个链接
- socket.on() 监听事件
- socket.emit() 触发事件
- socket.open() 手动打开连接
- socket.close() 手动断开连接
```javascript
// vue中使用 socket.io-client
import io from 'socket.io-client';
export default {
data () {
return {
socket: io('10.0.25.29:3001')}
},
mounted () {
this.socket.on('MESSAGE', (data) => {
this.messages = [...this.messages, data];
});
},
methods: {
// 发送消息
sendMessage () {
this.socket.emit('SEND_MESSAGE', {
user: this.user,
message: this.message
})
},
// 关闭连接
handleClose () {
// 服务器端关闭
// this.socket.emit('CLOSE')
// 客户端关闭
this.socket.close()
},
// 重新连接
handleAttemp () {
console.log('尝试')
this.socket.open()
}
}
}
Request
服务端
地址:https://socket.io/docs/server-api/
API
- io.on() 监听事件 connection 监听连接、 disconnect 监听断开
- io.emit() 触发事件
- io.close() 关闭事件
- io.of(nsp) 初始化并获取
Namespace
其路径名标识的给定nsp,NameSpace 命名空间,隔离作用域,或者划分业务模块,推荐使用 - socket.broadcast.emit() 该事件发出将仅向除发送方以外的所有套接字广播事件数据
- socket.id 会话的唯一标识符(可以进行私聊) join leave to() ```javascript const express = require(‘express’); const app = express();
const server = app.listen(3001, ‘10.0.25.29’, function () { console.log(‘server running on port 3001’); });
const io = require(‘socket.io’)(server);
io.on(‘connection’, function (socket) { console.log(“用户” + socket.id + “连接”) socket.on(‘SEND_MESSAGE’, function (data) { socket.emit(‘MESSAGE’, data) }); // 关闭服务器 socket.on(‘CLOSE’, function () { socket.close() }) // 监听断开事件 socket.on(‘disconnect’, function () { console.log(“用户” + socket.id + “断开连接”) }) }); ```
Response
- pingInterval 发送新的ping数据包之前多少毫秒
- pingTimeout 没有pong数据包需要多少毫秒才能考虑连接已关闭
- sid 本次 EIO Socket 的会话 ID
- 绿色是发送,白色是接收,左侧前面数字是数据包类型,ping—>2, pong—>3, message—>4
介绍stomp
stomp+socket 开发过酒店管理系统的前台推送消息部分
地址:http://jmesnil.net/stomp-websocket/doc/
一个简单的面向文本/流的消息协议Stomp.over(ws)
方法来使用其他类型的WebSocket
流程图
wireShark
Frame: 物理层的数据帧概况
Ethernet II: 数据链路层以太网帧头部信息
Internet Protocol Version 4: 互联网层IP包头部信息
Transmission Control Protocol: 传输层T的数据段头部信息,此处是TCP
Hypertext Transfer Protocol: 应用层的信息,此处是HTTP协议
Line-based text data: 是服务器所响应而返回的页面内容
tcp retransmission原因
TCP协议是一个可靠的协议。它通过重新发送(retransmission)来实现TCP片段传输的可靠性。简单的说,TCP会不断重复发送TCP片段,直到片段被正确接收
进程突然杀死、持续刷新生成几个socket