什么是 WebSocket
WebSocket 诞生背景
早期网站为了实现实时推送,采用轮询。轮询是指浏览器客户端每隔一段时间向服务器发送HTTP请求,服务器返回最新数据给客户端。轮询方式分为轮询和长轮询:
Polling轮询:
function polling(url){
var xhr= new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function(){...}
xhr.send();
}
setInterval(
function(){
polling('./poll')
}
,1000)
Long-Polling轮询:
function longPolling(url){
var xhr= new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function(){
update('/longPoll')
}
xhr.send();
}
update('/longPoll')
传统模式带来的缺点,浏览器需要不断向服务器发送请求,然而HTTP请求与响应可能会包含较长的头部信息,但是真正的有效数据很少,造成带宽资源浪费。
比较新的轮询技术是 Comet)。这种技术虽然可以实现双向通信,但仍然需要反复发出请求。而且在 Comet 中普遍采用的 HTTP 长连接也会消耗服务器资源。
HTML5定义WebSocket协议,能很好节省服务器资源和带宽,实现实时通信。WebSocket使用ws或wss的统一资源标志符(URI),wss表示使用TLS的WebSocket。如下:
ws://echo.websocket.org
wss://echo.websocket.org
WebSocket 与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制。默认情况下,WebSocket 协议使用 80 端口;若运行在 TLS 之上时,默认使用 443 端口。
WebSocket API
手写WebSocket 服务器
WebSocket实现聊天应用
使用ws中间件建立websoket服务
服务端使用ws搭建建立服务器wsServer.js
const WebSocket = require('ws')
var WebSocketServer = WebSocket.Server,
wss = new WebSocketServer({ port: 8899 });//服务端口8899
wss.on('connection', function (ws) {
console.log('服务端:客户端已连接', ws);
ws.on('message', function (message) {
//打印客户端监听的消息
wss.clients.forEach(link => {
if(link != ws && link.readyState === WebSocket.OPEN){
console.log("不是自己发送的消息");
link.send(message)
}
})
});
});
使用vue创建前端聊天对话框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webSocket</title>
<style>
#app{
width: 400px;
}
#ul{
width: 100%;
padding:0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
#ul li{
width: 75%;
list-style: none;
border-radius: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
padding: 5px 12px;
margin: 6px;
}
#ul li p{
margin: 0;
}
.self{
align-self: flex-end;
color:coral;
text-align: right;
}
.other{
color:darkslategray;
}
#input{
border-radius: 10px;
width: 200px;
font-size: 20px;
height: 32px;
line-height: 32px;
}
#btn{
width: 100px;
height: 32px;
border: none;
font-size: 20px;
}
</style>
</head>
<body>
<div id="app">
<div v-if="isLink">
<ul id="ul">
<li :class="[li.from=='self'? 'self' : 'other']" v-for="li in list">
<p>
{{li.value}}
</p>
</li>
</ul>
<input id="input" type="text" v-model="inputText" v-on:keyup.enter="send"></textarea>
<input id="btn" type="button" value="send" @click.enter="send">
</div>
<div v-else>
网络没有连接
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
let ws = new WebSocket('ws://localhost:8899')
var app = new Vue({
el: '#app',
data: {
inputText: '',
list:[],
isLink:false
},
methods: {
send(){
if(!this.inputText) return;
// 使用send给服务端发送数据
ws.send(this.inputText);
this.list.push({
value:this.inputText,
from: 'self'
})
this.inputText = '';
}
}
})
ws.onopen = ()=>{
app.isLink = true;
console.log("onopen");
// ws.emit("msg", 12,21)
}
ws.onmessage = (data)=>{
// 从服务端传回的数据
console.log("onmessage receive", data.data);
app.list.push({
from:"other",
value: data.data
})
}
ws.onclose = ()=>{
app.isLink = false;
console.log("onclose");
}
// 自己封装emit
ws.emit = (name, ...args)=>{
ws.send(JSON.stringify({name, data: [...args]}))
}
</script>
</body>
</html>
可以打开多个窗口对话框,最终效果:自己发送的消息靠右侧,颜色为橙色。接收到别人消息为黑色。
使用socket.io搭建服务端
创建socketServer.js
const http = require('http');
const io =require('socket.io')
let httpServer = http.createServer()
httpServer.listen(8899)
let wsServer = io(httpServer)
let socketLink = []
wsServer.on('connection',socket=>{
// socket.emit
// socket.on
socketLink.push(socket)
socket.on('send', str=>{
socketLink.forEach(so=>{
if(so != socket){
so.emit('send', str)
}
})
})
// 断开连接
socket.on('disconnect', ()=>{
let idx = socketLink.indexOf(socket)
if(idx!=-1){
socketLink.splice(idx, 1);
}
})
})
客户端client创建连接,需要引入socket.io.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>socket.io</title>
<style>
#app{
width: 400px;
}
#ul{
width: 100%;
padding:0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
#ul li{
width: 75%;
list-style: none;
border-radius: 10px;
border: 1px solid #ccc;
background-color: #f9f9f9;
padding: 5px 12px;
margin: 6px;
}
#ul li p{
margin: 0;
}
.self{
align-self: flex-end;
color:coral;
text-align: right;
}
.other{
color:darkslategray;
}
#input{
border-radius: 10px;
width: 200px;
font-size: 20px;
height: 32px;
line-height: 32px;
}
#btn{
width: 100px;
height: 32px;
border: none;
font-size: 20px;
}
</style>
</head>
<body>
<div id="app">
<div v-if="isLink">
<ul id="ul">
<li :class="[li.from=='self'? 'self' : 'other']" v-for="li in list">
<p>
{{li.value}}
</p>
</li>
</ul>
<input id="input" type="text" v-model="inputText"></textarea>
<input id="btn" type="button" value="send" @click="send">
</div>
<div v-else>
网络没有连接
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script type="text/javascript" src="http://localhost:8899/socket.io/socket.io.js"></script>
<script type="text/javascript">
let socket = io('ws://localhost:8899')
var app = new Vue({
el: '#app',
data: {
inputText: '',
list:[],
isLink:false
},
methods: {
send(){
if(!this.inputText) return;
socket.emit('send', this.inputText);
this.list.push({
value:this.inputText,
from: 'self'
})
this.inputText = '';
}
}
})
socket.on('send', str=>{
app.list.push({
value:str,
from:'other'
})
})
socket.on('connect', ()=>{
console.log("已连接");
app.isLink = true;
})
socket.on('disconnect', ()=>{
console.log('断开连接');
app.isLink = false;
})
</script>
</body>
</html>
最后呈现效果和ws创建的功能一样。