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: websocketConnection: UpgradeSec-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>// 客户端(浏览器)websocketconst 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 ProtocolsUpgrade: websocketConnection: UpgradeSec-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传递过去
 - 由于相应的头部这样的写法比较复杂,一般我们会使用第三方库来实现
 
