原理
-
WebSocket 是 HTML5 的新协议,允许服务器端向客户端发送数据,是一种全双工、双向的通信方式,并且它可以保持长时间通信;
-
通过 JavaScript 向服务端发出建立 WebSocket 连接的请求,在 WebSocket 连接建立成功后,客户端和服务端可以实现一个长连接的网络管道;因为 WebSocket 本质上是 TCP 连接,它是一个长连接,除非断开连接否则无需重新创建连接,所以其开销相对 HTTP 节省了很多;
基本使用
创建连接
if (typeof window.WebSocket == 'function') { // 检测一下浏览器是否支持 Websocket
var ws = new WebSocket('http://127.0.0.1:8003'); // 创建基于本地的 8003 端口的 websocket 连接
} else {
alert("您的浏览器不支持 websocket");
}
创建成功
由于 JavaScript 的各种 IO 操作是基于事件回调的,所以 Websocket 也不例外,需要创建一个连接成功的回调函数来处理连接创建成功之后的业务处理
ws.onopen = function(){ // 通过监听 open 时间来做创建成功的回调处理
console.log('websocket 连接创建成功');
// 进行业务处理
}
接收消息
需要在回调函数里处理(一不小心就陷入了回调地狱了);
Websocket 是基于二进制流格式的,传输过来的消息可能不一定是基于 utf8 的字符串格式,因此需要对格式进行判断;
ws.onmessage = function (event) {
// 接收到消息之后的业务处理
var d = event.data;
// 判断数据的类型格式
switch (typeof d) {
case "String":
break;
case "blob":
break;
case "ArrayBuffer":
break;
default:
return;
}
}
发送消息
客户端通过使用 send 函数向服务端发送数据;
可以发送文本格式,也可以发送二进制格式;
var input = document.getElementById("file");
input.onchange = function(){
var file = this.files[0];
if(!!file){
// 读取本地文件,以 gbk 编码方式输出
var reader = new FileReader();
reader.readAsBinaryString(file);
reader.onload = function(){
// 读取完毕后发送消息
ws.send(this.result);
}
}
}
监听错误信息
ws.onerror = function(event){
// 这里处理错误信息
}
关闭连接
当服务端或者客户端关闭 websocket 连接时,系统会触发一个关闭事件
ws.onclose = function (event){
// 这里处理关闭之后的业务
}
连接的状态
通过 websocket 对象的 readyState 属性可以获取到当前连接的状态,其中常用的有 4 种,通过 websocket 对象的几种定义常量对比判断
switch (ws.readyState){
case WebSocket.CONNECTING:
break; // 处于正在连接中的状态
case WebSocket.OPEN:
break; // 表示已经连接成功
case WebSocket.CLOSING:
break; // 表示连接正在关闭
case WebSocket.CLOSE:
break; // 表示连接已经关闭,或者创建连接失败
default:break;
}
Web Socket 特点
与 HTTP 协议兼容;
建立在 TCP 协议上,利用了握手,与 HTTP 协议同属应用层;
服务器和客户端可以发送少量数据,性能开销小,通信高效;
没有同源策略限制;
协议标志符是 ws 和 wss ,前者为不安全协议,后者为安全协议;
可以发送文本,也可以发送二进制数据;
注意事项
websocket 创建之前需要使用 HTTP 协议进行一次握手请求,服务端正确回复相应的请求之后才能创建 websocket 连接;
创建 websocket 时需要进行一些类似 token 之类的登录认证,不然任何客户端都可以向服务器进行 websocket 连接;
websocket 是明文传输,敏感的数据需要进行加密处理;
由于 websocket 是长连接,当出现异常时连接会断开,服务端的进程也会丢失,所以服务端最好有守护进程进行监控重启;
服务器监听的端口最好使用非系统性且不常使用的端口,不然可能会导致端口冲突;
案例
<!-- 前端代码 -->
<!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>
<!-- socket.io(对原生的 WebSocket 进行了封装,使用起来更方便,客户端/服务端要同时使用) -->
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
var messages = document.getElementById('messages');
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', function(msg) {
var item = document.createElement('li');
item.textContent = msg;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
</script>
</body>
</html>
// 服务端代码
const app = require('express')();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html'); // http://localhost:3000
});
io.on('connection', (socket) => {
socket.on('chat message', msg => {
io.emit('chat message', msg);
});
});
http.listen(port, () => {
console.log(`Socket.IO server running at http://localhost:${port}/`);
});
API 篇:Fetch Api
上一篇