一文读懂 WebSocket
Websocket 是一个长久化的网络通信协定,能够在单个 TCP 连贯上进行全双工通信,没有了 Request 和 Response 的概念,两者位置齐全平等,容许服务端被动向客户端发送数据,一旦建设连贯,客户端和服务端之间即可实时进行双向数据传输。
它的最大特点就是,服务器能够被动向客户端推送信息,客户端也能够被动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
(1)建设在 TCP 协定之上,服务器端的实现比拟容易。
(2)没有同源限度,客户端能够与任意服务器通信。
(3)协定标识符是 ws(如果加密,则为 wss),服务器网址就是 URL。
客户端
因为 websocket 是 HTML5 开始提供的,因而客户端能够新建 ws 对象用于建设通信。
let ws = new WebSocket('ws://localhost:3000');
ws.onopen = () => {console.log('open connection');
}
ws.onmessage = (event) => {
// 客户端接管到服务端数据的回调
console.log(event,'onmessage event');
}
ws.onclose = () => {console.log('close connection')
}
以上是 websocket 的三个事件,还有一个是 error 事件,显著是在通信产生谬误时触发的事件。
另外 websocket 还有两个罕用办法,在后续的例子中将会应用它们:
ws.send(); // 应用连贯发送数据
ws.close(); // 敞开连贯
服务端
node 中,应用最宽泛的是通过 ws 模块来创立 websocket 服务,应用前须要先装置这个模块:
const express = require('express');
const SocketServer = require('ws').Server;
const port = 3000;
const server = express().listen(port, () => {console.log(`listening to ${port}`);
})
const wss = new SocketServer({server});
wss.on('connection', (ws) => {console.log('Client connected.');
ws.on('message', (data) => {console.log(data);
// 服务端发送数据到客户端
ws.send(data);
})
ws.on('close', () => {console.log('Close connected.')
})
})
启动服务,运行:
node server.js
能看到服务启动:
listening to 3000
此时拜访客户端页面,在控制台能看到:
open connection
输出 ws,而后回车显示 ws 对象信息:
WebSocket{
binaryType: "blob"
bufferedAmount: 0
extensions: ""onclose: () => { console.log('close connection') }
onerror: null
onmessage: (event) => {console.log(event,'onmessage event'); }
onopen: () => { console.log('open connection'); }
protocol: ""
readyState: 1
url: "ws://localhost:3000/"
}
这里提一下 readyState,他是返回实例对象的以后状态,共有四种:
状态 | 代码 | 含意 |
---|---|---|
CONNECTING | 0 | 示意正在连接 |
OPEN | 1 | 示意连贯胜利,能够通信 |
CLOSING | 2 | 示意连贯正在敞开 |
CLOSED | 3 | 示意连贯曾经敞开或关上连贯失败 |
这时在控制台手动调用 send 发送音讯:
ws.send('hello')
实例对象的 send()办法用于向服务端发送数据,触发客户端 ws.onmessage 事件,同时服务端的 ws.on(‘message’)事件也被触发,服务端接管客户端发送过去的音讯。
客户端打印 event 回传信息:
在 data 属性中能够看到”hello“信息。
同时,服务端能够被动间断向客户端发送音讯:
wss.on('connection', (ws) => {console.log('Client connected.');
const sendNowTime = setInterval(() => {ws.send(String(new Date()))
},1000)
});
// 服务端每秒向客户端发送实时音讯
客户端 连贯后 就会开始定时接管服务端发来的数据,直至手动敞开连贯或服务。
ws.close()
模仿多人聊天
上面提供一个残缺的例子用于模拟两人通过 websocket 进行实时聊天:
客户端代码
<div id="app">
<div class="box">
<ul>
<li v-for="(item,index) in news" :class="item.nickname == nickname ?'me':'other'":key="index">
![](item.nickname == nickname ?)
<p>{{item.message}}</p>
</li>
</ul>
<div>
<input type="text" v-model="send">
<button @click="sendMsg"> 发送 </button>
</div>
</div>
</div>
*{
margin: 0 ;
padding: 0;
}
ul,li{list-style: none;}
.box{
width: 500px;
margin: 0 auto;
}
.box ul li{
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
.box ul li img{
width: 40px;
height: 40px;
margin-right: 8px;
}
.box ul li p{
font-size: 14px;
line-height: 20px;
max-width: 300px;
background: #f1f1f1;
border-radius: 4px;
padding: 10px 20px;
}
.me{
justify-content: flex-start;
flex-direction: row;
}
.other{
flex-direction: row-reverse;
order:1
}
let app = new Vue({
el: '#app',
data(){
return {
socketUrl: 'ws://localhost:8000?userName=',
nickname: '',
news: [],
send: '',
userList: []}
},
mounted(){this.initChatting();
let url = window.location.href;
this.nickname = url.split('=')[1]
},
methods: {initChatting(){if(window.WebSocket){this.client = new WebSocket(this.socketUrl + this.nickname);
this.client.onopen = (e) => {if(e.type == 'open'){console.log('客户端连贯')
}
}
// 客户端接管到服务端数据的回调
this.client.onmessage = (e) => {let data = JSON.parse(e.data);
if(data instanceof Array == true){this.userList = data; // 在线用户数变动}else{
// 聊天信息
this.news.push(data);
}
console.log(this.news)
}
this.client.onclose = (e) => {this.client = null;}
this.client.onerror = () => {if(!this.client){console.log("服务连贯失败")
}
}
}else{alert("浏览器版本过低,不反对 websocket.")
}
},
sendMsg(){
let data = {
message: this.send,
uid: new Date().getTime(),
nickname: this.nickname,
date: new Date()}
this.client.send(JSON.stringify(data))
this.send = "";
}
}
})
服务端代码
const WebSocket = require('ws').Server;
const moment = require('moment');
const url = require('url');
const querystring = require('querystring');
let wss = new WebSocket({
url: 'localhost',
port: 8000
})
let id = 0;
let onlineMemberList = [];
let defaultUser = 'user';
wss.on('connection', (ws,req) => {console.log('connected.')
id++;
ws.id = id;
let arg = url.parse(req.url).query;
let nameObj = querystring.parse(arg);
let userName;
if(nameObj.username){userName = decodeURIComponent(username);
}else{userName = defaultUser + id}
let userInfo = {
userName,
socketId: id,
date: moment().format('MMMM Do YYYY, hh:mm:ss a')
}
for(let i=0;i<onlineMemberList.length;i++){if(userInfo.userName === onlineMemberList[i].userName){onlineMemberList[i] = userInfo;
wss.clients.forEach(item => {item.send(JSON.stringify(onlineMemberList))
})
return;
}
}
onlineMemberList.push(userInfo);
wss.clients.forEach(item => {item.send(JSON.stringify(onlineMemberList));
})
ws.on('message',(data) => {let newData = JSON.parse(data);
console.log(data,'data')
newData.serveDate = moment().format('MMMM Do YYYY, h:mm:ss a');
wss.clients.forEach(item => {
// 监听客户端发来的数据,间接将信息一成不变,全副返回回去
item.send(JSON.stringify(newData));
})
})
ws.on('close',(e) => {
onlineMemberList = onlineMemberList.filter(item => {return item.socketId != ws.id;})
wss.clients.forEach(item => {item.send(JSON.stringify(onlineMemberList));
})
})
ws.on('error',(e) => {console.log('客户端异样',e)
})
})
实现成果
跨域发送接管信息
因为 websocket 能够建设客户端和服务端点对点的链接,因而不受跨域问题影响,也可用于解决客户端跨域问题:
let ws = new WebSocket("ws://localhost:8200"); // 建设连贯
ws.onopen = function () { // 关上协定
console.log("连贯胜利");
}
ws.onmessage = function (mes) { // 发送数据到服务端
console.log(mes);
}
// ws.addEventListener("message", function (e) {});
document.querySelector(".btn").onclick = function () {let input = document.querySelector(".input").value;
console.log("客户端发送给服务端的信息:" + input);
ws.send(input); // 如果服务端敞开协定后,即执行 ws.close()后, 此时会报错:WebSocket is already in CLOSING or CLOSED state.};
看完这篇文章,心愿读者能对 websocket 能有一个初步的理解,在工作、练习中能够更快地上手利用。