WebSocket
是双向的,在客户端-服务器通信的场景中使用的全双工协议,与HTTP
不同,它以ws://
或wss://
开头。它是一个有状态协议,这意味着客户端和服务器之间的连接将保持活动状态,直到被任何一方(客户端或服务器)终止。在通过客户端和服务器中的任何一方关闭连接之后,连接将从两端终止。如果其中任何一方(客户端服务器)宕掉或主动关闭连接,则双方均将关闭连接。套接字的工作方式与
HTTP
的工作方式略有不同,状态代码101
表示WebSocket
中的交换协议。
原理
当客户端要和服务端建立 WebSocket 连接时,在客户端和服务器的握手过程中,客户端首先会向服务端发送一个 HTTP 请求,包含一个 Upgrade 请求头来告知服务端客户端想要建立一个 WebSocket 连接。
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: ************==
Sec-WebSocket-Version: **
Server正确接收后,会返回一个响应头:
Upgrade:websocket
Connnection: Upgrade
Sec-WebSocket-Accept: ******
和TCP、HTTP协议的关系
WebSocket是基于TCP的独立的协议。
和HTTP的唯一关联就是HTTP服务器需要发送一个“Upgrade”请求,即101 Switching Protocol
到HTTP服务器,然后由服务器进行协议转换。
优点
- 较少的控制开销,在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于 HTTP 请求每次都要携带完整的头部,此项开销显著减少了。
- 更强的实时性,由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
- 长连接,保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。
- 双向通信、更好的二进制支持。与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易被屏蔽,能通过各种 HTTP 代理服务器。
示例
后端使用node.js来hostwebsocket服务,使用ws这个三方库:https://github.com/websockets/ws
当然java和.net也都有很好的库支持,这里不做介绍。
Server端代码如下,记得装依赖包:
- express
- ws
'use strict';
const express = require('express');
const path = require('path');
const { createServer } = require('http');
const WebSocket = require('ws');
const app = express();
app.use(express.static(path.join(__dirname, '/public')));
const server = createServer(app);
const wss = new WebSocket.Server({ server });
// http
app.get('/api/word', function (req, res) {
res.send({ message: 'hello - http' })
})
// ws
wss.on('connection', function (ws) {
ws.send(JSON.stringify({ message: 'hello - websocket' }), function () {});
ws.on('message', function (data, isBinary) {
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data, { binary: isBinary });
}
});
});
ws.on('open', function() {
console.log('connected');
ws.send({ message: 'hello - websocket - open' });
});
ws.on('close', function () {
console.log('closed');
});
});
server.listen(8080, function () {
console.log('Listening on http://localhost:8080');
});
Client端代码如下:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
</head>
<body>
<button id="connect">建立连接</button>
<input id="message-text" type="text">
<button id="send">发送</button>
<button id="close">关闭</button>
<div id="message-list"></div>
</body>
<script type="text/javascript">
let ws;
document.getElementById("connect").onclick = function () {
ws = new WebSocket('ws://localhost:8080');
ws.onopen = function(evt) {
console.log('建立连接,状态:' + ws.readyState);
};
ws.onmessage = function(evt) {
const data = JSON.parse(evt.data)
console.log("状态:" + ws.readyState + ";服务端返回数据:", data);
const list = document.getElementById("message-list");
list.innerHTML = `${list.innerHTML} <p>${data.message}</p>`;
};
ws.onerror = function(evt) {
console.log('发生错误,状态:' + ws.readyState);
};
ws.onclose = function(evt) {
console.log("连接关闭,状态:", ws.readyState);
};
}
document.getElementById("send").onclick = function () {
const val = document.getElementById("message-text").value
const data = {
message: val
}
const list = document.getElementById("message-list");
list.innerHTML = `${list.innerHTML} <p>${data.message}</p>`;
ws.send(JSON.stringify(data));
}
document.getElementById("close").onclick = function () {
ws.close();
}
</script>
</html>
效果如下:
打开两个浏览器,edge和chrome,两边都要先建立连接,然后就可以通信了。当一方关闭连接,另一方就收不到消息了。
发表评论
所有评论(0)