socket


  1. 客户端连接服务器(TCP/IP),三次握手,建立链接通道
  2. 客户端和服务器通过socket接口发送和接收消息,任何一端在任何时候,都可以向另一端发送消息
  3. 有一端断开了通道销毁

http


  1. 客户端连接服务器(TCP/IP),三次握手,建立链接通道
  2. 客户端发送一个http格式的消息(消息头,消息体),服务器响应http格式的消息(消息头,消息体)
    1. 长连接模式,会在请求头里面添加connection:keep-alive的请求头
    2. 以后请求还使用此链接通道
  3. 客户端或服务器断开链接,通道销毁(每次请求或响应后就断开链接,除非是长链接)

问题:

  1. 消息的实时性问题
    1. 轮询: 每个一段时间,请求一次接口,导致很多无用的链接,浪费服务器性能
    2. 长链接: 求头里面添加connection:keep-alive的请求头,如果服务器不返回消息,客户端会一直等待服务器相应,不会断开链接,需要服务器他有个独立线程单独处理场链接,程序设计比较复杂

    3. socket和http


  1. socket链接一直存在,http用完一次就销毁
  2. socket客户端和服务端都可以发送请求,http是客户端主动发送请求
  3. socket发送’响应消息无格式限制,http必须发送/响应http(请求头,请求体)格式的消息

websocket


专门用于解决实时的传输问题,最好的案例就是聊天

  1. 客户端连接服务器(TCP/IP),三次握手,建立链接通道
  2. 客户端发送一个http格式的消息(特殊格式,只有消息头部,没有消息体的请求询问服务器是否支持websocket),服务器也响应一个特殊格式的消息,称为http握手
  3. 双方自由通信,通信格式按照websocket的要求进行
  4. 客户端/服务器一方断开,通道销毁

特殊请求消息格式
image.png

  • Connection:Upgrade 链接方式升级了
  • Upgrade: websocket 升级为websocket
  • Sec-Websocket-Key:+A…== 本次请求的key,服务器会根据此key生成相应的key返回,每次请求的key都是不一样的

特殊相应消息
image.png

  • Sec-Websocket-Accept: doc….= 相应key根据请求key加密后的base64编码

在websocket的http握手阶段,服务器响应头中需要包含如下内容:

  1. Upgrade: websocket
  2. Connection: Upgrade
  3. Sec-WebSocket-Accept: [key]

其中,Sec-WebSocket-Accept的值来自于以下算法:

  1. base64(sha1(Sec-WebSocket-Key) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")

node中可以使用以下代码获得:

  1. const crypto = require("crypto");
  2. const hash = crypto.createHash("sha1");
  3. hash.update(requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
  4. const key = hash.digest("base64");

其中,requestKey来自于请求头中的Sec-WebSocket-Key

demo

客户端

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <title>Document</title>
  7. </head>
  8. <body>
  9. <button>发送数据到服务器</button>
  10. <script>
  11. // 客户端(浏览器)websocket
  12. const ws = new WebSocket("ws://localhost:12306"); // 创建一个websocket,同时,发送连接到服务器
  13. ws.onopen = function () {
  14. // http握手完成
  15. console.log("连接已建立");
  16. };
  17. ws.onmessage = function (e) {
  18. console.log("来自服务器的数据", e.data);
  19. };
  20. ws.onclose = function () {
  21. console.log("通道关闭");
  22. };
  23. document.querySelector("button").onclick = function () {
  24. ws.send("123");
  25. };
  26. // ws.close(); //客户端主动断开连接
  27. </script>
  28. </body>
  29. </html>

服务端

  1. const net = require("net");
  2. const crypto = require("crypto");
  3. const server = net.createServer((socket) => {
  4. console.log("收到客户端的连接");
  5. socket.once("data", (chunk) => {
  6. const httpContent = chunk.toString("utf-8");
  7. let parts = httpContent.split("\r\n");
  8. parts.shift();
  9. parts = parts
  10. .filter((s) => s)
  11. .map((s) => {
  12. const i = s.indexOf(":");
  13. return [s.substr(0, i), s.substr(i + 1).trim()];
  14. });
  15. const headers = Object.fromEntries(parts);
  16. const hash = crypto.createHash("sha1");
  17. hash.update(
  18. headers["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
  19. );
  20. const key = hash.digest("base64");
  21. // 响应
  22. socket.write(`HTTP/1.1 101 Switching Protocols
  23. Upgrade: websocket
  24. Connection: Upgrade
  25. Sec-WebSocket-Accept: ${key}
  26. `);
  27. socket.on("data", (chunk) => {
  28. console.log(chunk);
  29. });
  30. });
  31. });
  32. server.listen(5008);
  • 根据客户端的sec-webscoket-key,添加一个以自定义的字符串,生成hash值
  • 把hash值base64转码以后,添加到相应头中

    1. const hash = crypto.createHash("sha1");
    2. hash.update(
    3. headers["Sec-WebSocket-Key"] + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
    4. );
    5. const key = hash.digest("base64");
  • websocket中特殊相应 ``json // 响应 socket.write(HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: ${key}

`); ```

  • 只有响应头没有响应体,需要把key传递过去
  • 由于相应的头部这样的写法比较复杂,一般我们会使用第三方库来实现