什么是 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.orgwss://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 });//服务端口8899wss.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");}// 自己封装emitws.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.onsocketLink.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创建的功能一样。
