为什么需要 WebSocket?
HTTP 协议做不到服务器主动向客户端推送信息,只能由客户端向服务端主动发起请求来拉取信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询",即每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。但是,轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法,WebSocket因此而被发明。
Websocket的特性
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是
ws
(如果加密,则为wss
),服务器网址就是 URL。 - 浏览器兼容性从IE10开始支持。
服务端实现
WebSocket 服务器的实现,可以查看维基百科的列表。
常用的 Node 实现有以下三种。
Socket.IO 实现
Socket.IO 是一个基于 Node.js 的实时应用程序框架,在即时通讯、通知与消息推送,实时分析等场景中有较为广泛的应用。
安装命令:
npm install --save express socket.io
我们按照官网教程,简单实现一个聊天室。
服务端index.js
逻辑代码
const express = require('express');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
io.on('connection', (socket) => {
console.log('a user connected');
// 当有chat message来的时候处理
socket.on('chat message', (msg) => {
console.log('message: ' + msg);
// 接下来需要把这个消息广播给所有连接着的用户
io.emit('chat message', msg);
});
});
server.listen(3000, () => {
console.log('listening on *:3000');
});
客户端index.html
代码
<!DOCTYPE html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
body {
margin: 0;
padding-bottom: 3rem;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
#form {
background: rgba(0, 0, 0, 0.15);
padding: 0.25rem;
position: fixed;
bottom: 0;
left: 0;
right: 0;
display: flex;
height: 3rem;
box-sizing: border-box;
backdrop-filter: blur(10px);
}
#input {
border: none;
padding: 0 1rem;
flex-grow: 1;
border-radius: 2rem;
margin: 0.25rem;
}
#input:focus {
outline: none;
}
#form>button {
background: #333;
border: none;
padding: 0 1rem;
margin: 0.25rem;
border-radius: 3px;
outline: none;
color: #fff;
}
#messages {
list-style-type: none;
margin: 0;
padding: 0;
}
#messages>li {
padding: 0.5rem 1rem;
}
#messages>li:nth-child(odd) {
background: #efefef;
}
</style>
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="input" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var form = document.getElementById('form');
var input = document.getElementById('input');
// 当用户点击提交按钮的时候
form.addEventListener('submit', function(e) {
e.preventDefault(); // 阻止页面默认刷新
if (input.value) {
// 发送内容
socket.emit('chat message', input.value);
input.value = '';
}
});
// 当收到了广播消息时
socket.on('chat message', (msg) => {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>
WS实现
socket.io是一套完整的前后端socket封装逻辑,如果前端是小程序,那么socket.io就无能为力了。这里我们可以试一试WS这个库。
安装 WS库
npm install --save ws
服务端
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
// ws没有广播函数,所以需要存储所有人的ws对象,然后遍历发送
let ws_arr = [];
wss.on('connection', function connection(ws) {
console.log("Some one connect...");
//新来一个连接,存入连接数组中
ws_arr.push(ws);
// 来消息了
ws.on('message', (message) => {
console.log('received: %s', message);
// 遍历所有人,然后广播
ws_arr.forEach(ws => {
ws.send(message);
})
});
});
微信小程序端
// pages/websocket/websocket.js
Page({
data: {
},
onLoad: function (options) {
// 先连接接口
wx.connectSocket({
url: 'ws://localhost:8080',
})
// 当来消息了,用提示框弹出消息
wx.onSocketMessage((result) => {
wx.showToast({
title: result.data,
})
})
},
// 发送消息函数
sendMsg() {
wx.sendSocketMessage({
data: "hahaha",
})
}
})