socket

socket 工作流程

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

http

http 工作流程

  1. 客户端连接服务器(TCP/IP),三次握手,建立了连接通道
  2. 客户端发送一个 http 格式的消息(消息头 消息体),服务器响应 http 格式的消息(消息头 消息体)
  3. 客户端或服务器断开,通道销毁

实时性的问题

  1. 轮询

  2. 长连接

WebSocket

HTML5 新增 专门用于解决实时传输的问题

WebSocket 工作流程

  1. 客户端连接服务器(TCP/IP),三次握手,建立了连接通道

  2. 客户端发送一个 http 格式的消息(特殊格式),服务器也响应一个 http 格式的消息(特殊格式),称之为 http 握手

  3. 双发自由通信,通信格式按照 WebSocket 的要求进行

  4. 客户端或服务器断开,通道销毁

服务端的握手响应

  1. WebSockethttp 握手阶段,服务器响应头中需要包含如下内容:

    # 升级成 websocket 协议
    Upgrade: websocket
    
    # 连接方式:升级的方式
    Connection: Upgrade
    
    # 从客户端发来的 Sec-WebSocket-Accept,服务端对它加密后再返还给客户端(用于通信)
    Sec-WebSocket-Accept: [key]
    
  2. 其中 Sec-WebSocket-Accept 的值来自于以下算法:

    // 对客户端传来的 Sec-WebSocket-Key 进行 sha1 加密,再对加密后的结果拼接固定字符串,最后进行 base64 编码
    base64(sha1(Sec-WebSocket-Key) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") 
    
  3. node 中可以使用以下代码获得:

    const crypto = require("crypto");
    const hash = crypto.createHash("sha1");
    // requestKey 来自于请求头中的 Sec-WebSocket-Key
    hash.update(requestKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    const key = hash.digest("base64");
    

WebSocket 原理

  1. index.html 客户端

    <!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:5008"); // 创建一个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>
    
  2. index.js 服务端

    const net = require("net");
    
    const server = net.createServer((socket) => {
      console.log("收到客户端的连接");
      socket.once("data", (chunk) => {
        // 解析 http 协议的请求头信息
        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()];
          });
        
        // 加密 Sec-WebSocket-Key
        const headers = Object.fromEntries(parts);
        const crypto = require("crypto");
        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);
    

socket.io

案例:在线聊天室

消息规格

# --- 客户端发送 ---
## 获取当前所有在线用户
消息名称:users
消息内容:无

## 登录
消息名称:login
消息内容:用户名

## 消息
消息名称:msg
消息内容:`{to:"目标用户名,null表示所有人", content:"消息内容"}`


# --- 服务器发送 ---
## 获取当前所有在线用户
消息名称:users
消息内容:用户数组

## 登录
消息名称:login
消息内容:true 或 false,true表示登录成功,false表示登录失败(昵称已存在)

## 新用户进入
消息名称:userin
消息内容:用户名

## 用户离开
消息名称:userout
消息内容:用户名

## 新消息来了
消息名称:new msg
消息内容:`{from:"用户名", content:"消息内容", to:"接收消息的人,如果是null,表示所有人"}`

客户端实现

  1. index.html

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>在线聊天室</title>
      <link rel="stylesheet" href="./css/global.css" />
      <link rel="stylesheet" href="./css/index.css" />
    </head>
    
    <body>
      <div class="login">
        <div class="form">
          <h1>请输入你的昵称,回车后进入聊天室</h1>
          <input type="text" />
        </div>
      </div>
      <div class="chat" style="display: none;">
        <div class="user-list">
          <div class="title">在线人数:<span>0</span></div>
          <ul class="users">
            <li class="all">所有人</li>
          </ul>
        </div>
    
        <div class="main">
          <ul class="chat-list">
            <li class="log">欢迎来到聊天室</li>
          </ul>
          <div class="sendmsg">
            <span class="gray">对</span>
            <span class="user">所有人</span>
            <span class="gray">说:</span>
            <input type="text" />
          </div>
        </div>
      </div>
      <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.0/jquery.min.js"></script>
      <script src="js/ui.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
      <script src="./js/chat.js"></script>
    </body>
    
    </html>
    
  2. chat.js

    const socket = io.connect();
    
    // 客户端发送消息给服务器
    page.onLogin = function (username) {
      socket.emit("login", username);
    };
    
    page.onSendMsg = function (me, msg, to) {
      socket.emit("msg", {
        to,
        content: msg,
      });
      page.addMsg(me, msg, to);
      page.clearInput();
    };
    
    // 客户端监听服务器消息
    socket.on("login", (result) => {
      if (result) {
        page.intoChatRoom();
        socket.emit("users", "");
      } else {
        alert("昵称不可用,请更换昵称");
      }
    });
    
    socket.on("users", (users) => {
      page.initChatRoom();
      for (const u of users) {
        page.addUser(u);
      }
    });
    
    socket.on("userin", (username) => {
      page.addUser(username);
    });
    
    socket.on("userout", (username) => {
      page.removeUser(username);
    });
    
    socket.on("new msg", (result) => {
      page.addMsg(result.from, result.content, result.to);
    });
    

服务端实现

  1. index.js

    const express = require("express");
    const http = require("http");
    const path = require("path");
    
    // express
    const app = express();
    const server = http.createServer(app);
    app.use(express.static(path.resolve(__dirname, "public")));
    
    // websocket
    require("./chatServer")(server);
    
    // 监听端口
    server.listen(5008, () => {
      console.log("server listening on 5008");
    });
    
  2. chatServer.js

    const socketIO = require("socket.io");
    let users = [];
    
    module.exports = function (server) {
      const io = socketIO(server);
      io.on("connection", (socket) => {
        let curUser = ""; //当前用户名
        // 监听客户端消息
        socket.on("login", (data) => {
          if (
            data === "所有人" ||
            users.filter((u) => u.username === data).length > 0
          ) {
            //昵称不可用
            socket.emit("login", false);
          } else {
            // 昵称可用
            users.push({
              username: data,
              socket,
            });
            curUser = data;
            socket.emit("login", true);
            // 新用户进入了
            socket.broadcast.emit("userin", data);
          }
        });
    
        socket.on("users", () => {
          const arr = users.map((u) => u.username);
          socket.emit("users", arr);
        });
    
        socket.on("msg", (data) => {
          if (data.to) {
            // 发送给指定的用户
            const us = users.filter((u) => u.username === data.to);
            const u = us[0];
            u.socket.emit("new msg", {
              from: curUser,
              content: data.content,
              to: data.to,
            });
          } else {
            // 发送给所有人
            socket.broadcast.emit("new msg", {
              from: curUser,
              content: data.content,
              to: data.to,
            });
          }
        });
    
        socket.on("disconnect", () => {
          socket.broadcast.emit("userout", curUser);
          users = users.filter((u) => u.username !== curUser);
        });
      });
    };
    
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. socket
  2. 2. http
  3. 3. WebSocket
    1. 3.1. 服务端的握手响应
    2. 3.2. WebSocket 原理
  4. 4. socket.io
  5. 5. 案例:在线聊天室
    1. 5.1. 消息规格
    2. 5.2. 客户端实现
    3. 5.3. 服务端实现