websocket1
WebSocket
最近项目中使用到了WebSocket,在这里总结一下使用的方法和遇到的问题
首先介绍下什么是WebSocket
WebSocket是一种网络传输层协议,可在单个TCP链接上进行全双工通信,
位于OSI模型的应用层,WebSocket允许服务端向客户端主动推送数据。浏览器和我武器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
为什么要使用WebSocket(WebSocket与传统HTTP有什么优势)
- 客户端与服务端只建立一个TCP连接,可以使用更少的连接
- 服务器可以推送数据到客户端,比HTTP请求响应模式更灵活,更高效
- 更轻量级的协议头,减少数据传送量
WebSocket 握手
客户端建立连接时,通过HTTP发起请求报文
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
与HTTP请求协议有区别的部分:
Upgrade: websocket
Connection: Upgrade
这两个字段表示请求服务器端升级为WebSocket。
Sec-WebSocket-Key
用安全校验:
Sec-WebSocket-Key的值是随机生成的Base64编码的字符串。服务器端接收之后将其与字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11相连,行成dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11,然后通过sha1安全散列算法计算出结果,在进行Base64编码,最后返回给客户端。
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
上面两个字段指定子协议和版本号
服务端处理完请求后,响应报文如下:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
状态码101表示切换协议,更新应用层协议为WebSocket协议,并在当前的套接字上应用新的协议
Sec-WebSocket-Accep:
表示服务端基于Sec-WebSocket-Key生成的字符串
Sec-WebSocket-Protocol:
表示最终使用的协议
客户端代码
let ws = new WebSocket('ws://192.168.10.40:3000/')
ws.onopen = mes=>{
console.log(mes,'aaa')
}
服务端代码
const net = require('net');
const crypto = require('crypto');
const wsGUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
net.createServer(function(socket) {
socket.on('data', function(buffer) {
// data 是buffer需要转化
const data = buffer.toString(),
key = getWebSocketKey(data),
acceptKey = crypto.createHash('sha1').update(key + wsGUID).digest('base64'),
headers = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Accept: ' + acceptKey
];
socket.write(headers.concat('','').join('\r\n'));
})
}).listen(3000)
function getWebSocketKey(dataStr) {
var match = dataStr.match(/Sec\-WebSocket\-Key:\s(.+)\r\n/);
if (match) {
return match[1];
}
}
客户端的API
1.websocket 构造函数
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var ws = new WebSocket('ws://localhost:8080');
执行上面语句之后,客户端就会与服务器进行连接。下面这张图是实例对象的所有属性和方法清单
2.webSocket.readyState
readyState属性返回实例对象的当前状态,共有四种。
console.log(ws.readyState) // 我们可以输入看当前是什么状态
- CONNECTING:值为0,表示正在连接。
- OPEN:值为1,表示连接成功,可以通信了。
- CLOSING:值为2,表示连接正在关闭。
- CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
3.webSocket.onopen
实例对象的onopen属性,用于指定连接成功后的回调函数。
ws.onopen = function () {
ws.send('Hello Server!');
}
如果要指定多个回调函数,可以使用
addEventListener
方法。
ws.addEventListener('open', function (event) {
ws.send('Hello Server!');
});
4.webSocket.onclose
实例对象的onclose属性,用于指定连接关闭后的回调函数。
ws.onclose = function(event) {
var code = event.code;
var reason = event.reason;
var wasClean = event.wasClean;
// handle close event
};
5.webSocket.onmessage
实例对象的onmessage属性,用于指定收到服务器数据后的回调函数
ws.onmessage = function(event) {
var data = event.data;
// 处理数据
};
ws.addEventListener("message", function(event) {
var data = event.data;
// 处理数据
});
我们可以使用这个回调函数处理返回的数据相当于我们平时使用axios返回数据成功的then方法
6.webSocket.send()
实例对象的send()方法用于向服务器发送数据
ws.send('your message');
7.webSocket.bufferedAmount
实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
var data = new ArrayBuffer(10000000);
socket.send(data);
if (socket.bufferedAmount === 0) {
// 发送完毕
} else {
// 发送还没结束
}
8.webSocket.onerror
实例对象的onerror属性,用于指定报错时的回调函数。
socket.onerror = function(event) {
// handle error event
};
socket.addEventListener("error", function(event) {
// handle error event
});
完整示例
服务端
import userInfoModel from '../model/userinfo';
import addendanceInfoModel from '../model/addentance';
import Websocket from 'ws';
import * as http from 'http';
const wss = new Websocket.Server({port: 3004});
wss.on('connection',function(ws:Websocket,req:http.IncomingMessage) {
ws.on('message',async function(){
try{
// 获取基本数据
const userData = await userInfoModel.find();
ws.send(JSON.stringify(userData));
// 获取出勤数据
const addendanceData = await addendanceInfoModel.find();
ws.send(JSON.stringify(addendanceData))
}catch(err){
ws.close()
}
})
})
前端websocket
// Ws 封装一系列websock方法
// getInstance 获取当前类的实例
// initConnect 初始化连接,返回promise,连接成功resolve,失败reject
// send 发送数据,包括断开重新连接
export default class Ws{
constructor(){
// 初始化连接
this.initConnect()
}
static getInstance() {
if(!this.instance){
this.instance = new Ws()
}
return this.instance;
}
initConnect(){
// 创建ws实例
return new Promise((resolve,reject)=>{
this.ws = new WebSocket('ws://192.168.10.40:3004/');
this.ws.onmessage = event => this.message(JSON.parse(event.data));
this.ws.onopen = event => resolve(event);
this.ws.onclose = event => reject(event);
})
}
async send(data){
// 如果为连接断开就重新连接
if(this.ws.readyState == 3 ){
// 初始化连接
await this.initConnect();
// 连接成功发送消息
data && this.ws.send(JSON.stringify(data))
}else{
// 发送消息
data && this.ws.send(JSON.stringify(data))
}
}
close() {
if(this.ws.readyState == 3){
return;
}
this.ws.close();
}
}
前端组件部分
<script>
import Ws from '@/assets/websocket.js';
export default {
name: 'HelloWorld',
mounted() {
this.ws = Ws.getInstance();
this.ws.message = this.onmessage;
},
methods:{
onmessage(data) {
console.log(data,'数据')
},
OnClick() {
this.ws.send({})
}
}
}
</script>
websocket 遇到的坑
如果前端发送数据到后端,后端响应时间过长,可能会超出服务器的读取时间,排查问题,最后发现nginx设置了超时代理
proxy_read_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s:
设置从后端读取超时,默认60s,他决定了nginx会等待多长时间获得请求的响应,超时nginx会关闭连接
proxy_send_timeout 60s;
设置发送请求给上游服务器的超时时间,如果60s内上游服务器没有收到任何响应,nginx关闭连接
我们只需找运维同学帮我们把代理时间延长,就不会出现超时时间了
我们在代码中也可以设置断开重连
initConnect(){
// 创建ws实例
return new Promise((resolve,reject)=>{
this.ws = new WebSocket('ws://192.168.10.40:3004/');
this.ws.onmessage = event => this.message(JSON.parse(event.data));
this.ws.onopen = event => resolve(event);
this.ws.onclose = event => reject(event);
})
}
async send(data){
// 如果为连接断开就重新连接
if(this.ws.readyState == 3 ){
// 初始化连接
await this.initConnect();
// 连接成功发送消息
data && this.ws.send(JSON.stringify(data))
}else{
// 发送消息
data && this.ws.send(JSON.stringify(data))
}
}
上面代码中,每次给后端发送数据,判断当前websocket连接状态,当状态是3(连接已断开)时,重新进行连接,连接成功给后端发送数据。