socket
- 客户端连接服务器(TCP/IP),三次握手,建立链接通道
- 客户端和服务器通过socket接口发送和接收消息,任何一端在任何时候,都可以向另一端发送消息
- 有一端断开了通道销毁
http
- 客户端连接服务器(TCP/IP),三次握手,建立链接通道
- 客户端发送一个http格式的消息(消息头,消息体),服务器响应http格式的消息(消息头,消息体)
- 长连接模式,会在请求头里面添加connection:keep-alive的请求头
- 以后请求还使用此链接通道
- 客户端或服务器断开链接,通道销毁(每次请求或响应后就断开链接,除非是长链接)
问题:
- 消息的实时性问题
- socket链接一直存在,http用完一次就销毁
- socket客户端和服务端都可以发送请求,http是客户端主动发送请求
- socket发送’响应消息无格式限制,http必须发送/响应http(请求头,请求体)格式的消息
websocket
专门用于解决实时的传输问题,最好的案例就是聊天
- 客户端连接服务器(TCP/IP),三次握手,建立链接通道
- 客户端发送一个http格式的消息(特殊格式,只有消息头部,没有消息体的请求询问服务器是否支持websocket),服务器也响应一个特殊格式的消息,称为http握手
- 双方自由通信,通信格式按照websocket的要求进行
- 客户端/服务器一方断开,通道销毁
特殊请求消息格式
- Connection:Upgrade 链接方式升级了
- Upgrade: websocket 升级为websocket
- Sec-Websocket-Key:+A…== 本次请求的key,服务器会根据此key生成相应的key返回,每次请求的key都是不一样的
特殊相应消息
- Sec-Websocket-Accept: doc….= 相应key根据请求key加密后的base64编码
在websocket的http握手阶段,服务器响应头中需要包含如下内容:
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: [key]
其中,Sec-WebSocket-Accept
的值来自于以下算法:
base64(sha1(Sec-WebSocket-Key) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
在node
中可以使用以下代码获得:
const crypto = require("crypto");
const hash = crypto.createHash("sha1");
hash.update(requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
const key = hash.digest("base64");
其中,requestKey
来自于请求头中的Sec-WebSocket-Key
demo
客户端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button>发送数据到服务器</button>
<script>
// 客户端(浏览器)websocket
const ws = new WebSocket("ws://localhost:12306"); // 创建一个websocket,同时,发送连接到服务器
ws.onopen = function () {
// http握手完成
console.log("连接已建立");
};
ws.onmessage = function (e) {
console.log("来自服务器的数据", e.data);
};
ws.onclose = function () {
console.log("通道关闭");
};
document.querySelector("button").onclick = function () {
ws.send("123");
};
// ws.close(); //客户端主动断开连接
</script>
</body>
</html>
服务端
const net = require("net");
const crypto = require("crypto");
const server = net.createServer((socket) => {
console.log("收到客户端的连接");
socket.once("data", (chunk) => {
const httpContent = chunk.toString("utf-8");
let parts = httpContent.split("\r\n");
parts.shift();
parts = parts
.filter((s) => s)
.map((s) => {
const i = s.indexOf(":");
return [s.substr(0, i), s.substr(i + 1).trim()];
});
const headers = Object.fromEntries(parts);
const hash = crypto.createHash("sha1");
hash.update(
headers["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
);
const key = hash.digest("base64");
// 响应
socket.write(`HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: ${key}
`);
socket.on("data", (chunk) => {
console.log(chunk);
});
});
});
server.listen(5008);
- 根据客户端的sec-webscoket-key,添加一个以自定义的字符串,生成hash值
把hash值base64转码以后,添加到相应头中
const hash = crypto.createHash("sha1");
hash.update(
headers["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
);
const key = hash.digest("base64");
websocket中特殊相应
``json // 响应 socket.write(
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: ${key}
`); ```
- 只有响应头没有响应体,需要把key传递过去
- 由于相应的头部这样的写法比较复杂,一般我们会使用第三方库来实现