HTML5的新特性(三)——Websocket (附一个ws聊天室demo)

引言

在互联网技术迅猛发展的今天,HTML5标准的推出无疑是一大里程碑,它不仅极大地丰富了网页的表现力,还提供了一系列先进的技术特性,使得网页应用能够更加动态和富有交互性。其中,WebSockets作为HTML5引入的新特性之一,开启了实时通信的新篇章,为现代网络应用,尤其是需要快速、实时交互的应用提供了强大的支持。

本文将深入探索WebSockets技术,从其基本概念、与传统HTTP连接的对比,到如何在实际项目中应用,以构建一个简单的聊天室为例,全面展示WebSockets的实现过程和应用场景。无论你是前端新手还是资深开发者,本文都将为你提供有价值的参考和启发,帮助你更好地理解和运用这项强大的技术,开发出更加丰富和高效的网络应用。

7. WebSockets

WebSockets如何实现实时通信

WebSockets 提供了一种在单个持久连接上进行全双工通信的方式。它允许客户端和服务器之间建立一个长期的关系,使得数据可以快速且实时地双向传输。

当使用 WebSockets 时,客户端和服务器之间的交互流程通常如下:

  1. 客户端通过发送一个特殊的 HTTP 请求来请求建立 WebSocket 连接。这个请求被称为"握手"。
  2. 服务器理解这个请求,并响应一个升级(Upgrade)头信息,确认连接的建立。
  3. 一旦握手成功,客户端和服务器之间的连接就从 HTTP 协议升级为 WebSocket 协议。
  4. 在 WebSocket 连接上,客户端和服务器可以互相发送消息,直到其中一方关闭连接。

这种机制非常适合需要快速、实时通信的应用程序,如在线游戏、实时交易平台和聊天室。

对比传统的HTTP连接

与传统的HTTP连接相比,WebSockets 提供了以下优势:

  • 全双工通信:客户端和服务器可以同时发送和接收信息,而HTTP通常是请求/响应模式。
  • 减少开销:在建立连接后,WebSocket不需要每次通信都发送HTTP头,这减少了不必要的网络流量和延迟。
  • 实时性:WebSocket提供了接近实时的通信能力,这对于需要快速响应的应用至关重要。
  • 持久连接:一旦WebSocket连接建立,它将保持开放状态,直到客户端或服务器决定关闭它。

示例:建立一个简单的聊天室

以下是使用 WebSockets 建立一个简单聊天室的基本示例。我们将使用 JavaScript 的 WebSocket API 在客户端创建 WebSocket 连接,并假设服务器端已经设置了 WebSocket 服务来接收和广播消息。

服务端代码(使用ws库)

index.js,如未安装请先npm i ws 或者npm i -g ws

javascript 复制代码
// 引入WebSocket模块
const WebSocket = require('ws');

// 创建WebSocket服务器监听在3000端口
const wss = new WebSocket.Server({ port: 4090 });

// 当有客户端连接时
wss.on('connection', function connection(ws) {
  console.log('A new client connected');

  // 当收到客户端消息时
  ws.on('message', function incoming(message) {
    console.log('received: %s', message);

    // 发送消息到客户端
    ws.send('Hello, you sent -> ' + message);
  });

  // 当连接关闭时
  ws.on('close', function close() {
    console.log('Client disconnected');
  });

  // 发送欢迎消息到客户端
  ws.send('Welcome to the WebSocket server!');
});

console.log('ws server started');

客户端代码 (HTML/JavaScript):

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>简单聊天室</title>
</head>
<body>
    <input type="text" id="messageInput" placeholder="输入消息">
    <button onclick="sendMessage()">发送</button>
    <ul id="chatMessages"></ul>

    <script>
        // 假设 WebSocket 服务运行在此端口和路径上
        var ws = new WebSocket('ws://localhost:3000/chat');

        ws.onopen = function() {
            console.log('连接到聊天室');
        };

        ws.onmessage = function(event) {
            var message = event.data;
            document.getElementById('chatMessages').innerHTML += '<li>' + message + '</li>';
        };

        ws.onerror = function(error) {
            console.error('WebSocket 错误 ' + error);
        };

        ws.onclose = function() {
            console.log('聊天室连接关闭');
        };

        function sendMessage() {
            var message = document.getElementById('messageInput').value;
            ws.send(message);
            document.getElementById('messageInput').value = ''; // 清空输入框
        }
    </script>
</body>
</html>

在这个示例中,客户端使用 new WebSocket(url) 创建了一个到服务器的 WebSocket 连接。我们为 onopen, onmessage, onerror, 和 onclose 事件设置了事件处理程序,以处理连接打开、接收消息、错误和连接关闭的情况。

用户通过输入框输入消息,并点击发送按钮,sendMessage 函数会被调用,该函数将消息通过 WebSocket 连接发送到服务器。

注意 :在实际应用中,服务器端也需要相应的 WebSocket 服务来处理客户端的连接请求,接收消息,并将消息广播给所有连接的客户端。服务器端的实现取决于所使用的语言和框架,例如 Node.js 的 ws 库或 Python 的 websockets 库。

聊天室综合示例

服务端index.js
  1. 服务器启动:在4090端口上启动一个WebSocket服务器,等待客户端连接。
  2. 管理房间 :通过一个rooms对象管理多个聊天室。每个房间由一个唯一的标识符(房间号)表示,存储在该房间的客户端连接。
  3. 客户端连接处理
    • 当新客户端连接时,服务器会打印一条消息表示有新连接。
    • 客户端可以发送两种类型的消息:加入房间(join)和发送消息到房间(message)。
    • 当收到join类型的消息时,服务器会将该客户端添加到指定的房间。如果房间不存在,会先创建房间。然后向房间内所有客户端发送一条欢迎消息。
    • 当收到message类型的消息时,服务器会将该消息广播到指定房间内的所有客户端,包括消息的发送者。
  4. 客户端断开连接处理:当客户端断开连接时,服务器会从客户端所在的房间中移除该客户端。如果房间因此变为空,则删除该房间。
js 复制代码
// 引入WebSocket模块
const WebSocket = require("ws");

// 创建WebSocket服务器监听在4090端口
const wss = new WebSocket.Server({ port: 4090 });

// 用于存储房间信息的对象,键为房间号,值为连接集合
const rooms = {};

// 当有客户端连接时
wss.on("connection", function connection(ws) {
  console.log("一个新的客户端已连接");

  // 当收到客户端消息时
  ws.on("message", function incoming(data) {
    const message = JSON.parse(data); // 解析收到的消息
    console.log("接收到消息:", message);

    // 根据消息类型处理
    switch (message.type) {
      case "join":
        // 用户加入房间
        if (!rooms[message.room]) {
          rooms[message.room] = new Set(); // 如果房间不存在,则创建
        }
        rooms[message.room].add(ws); // 将当前连接加入房间
        ws.room = message.room; // 在WebSocket连接上记录房间号
        ws.userId = message.userId; // 在WebSocket连接上记录用户ID
        // 向加入房间的用户发送确认消息
        ws.send(`你好,你已经加入房间 ${message.room}`);
        rooms[message.room].forEach((client) => {
          if (client.readyState === WebSocket.OPEN) {
            client.send(`欢迎 用户 ${message.userId} 加入房间 ${message.room}`);
          }
        });
        break;
      case "message":
        // 向房间内的所有客户端(包括发送者)广播消息
        if (rooms[message.room]) {
          rooms[message.room].forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
              client.send(`${message.userId} [${new Date()}] : ${message.content}`);
            }
          });
        }
        break;
    }
  });

  // 当连接关闭时
  ws.on("close", function close() {
    console.log("客户端已断开连接");
    // 从房间中移除连接
    if (ws.room && rooms[ws.room]) {
      rooms[ws.room].delete(ws);
      // 如果房间为空,则删除房间
      if (rooms[ws.room].size === 0) {
        delete rooms[ws.room];
      }
    }
  });
});

console.log("WebSocket服务器已启动");
客户端ws.html
加入房间

当用户想要加入一个房间时,他们需要进行以下操作:

  1. 填写房间号:在提供的输入框中输入房间号。如果用户已经有一个用户ID,他们可以使用它;如果没有,系统会为他们生成一个。
  2. 点击加入房间按钮:用户点击"加入房间"按钮后,触发一个事件监听器。
  3. 事件处理 :在点击事件的处理函数中,首先获取房间号和用户ID的值。如果用户ID为空,则调用generateUserId()函数生成一个新的用户ID,并将其显示在用户ID输入框中。
  4. 发送加入请求 :最后,使用socket.send()方法向服务器发送一个JSON字符串,该字符串表示一个对象,包含type字段(值为join),表示这是一个加入房间的请求,以及房间号和用户ID。

代码片段如下:

javascript 复制代码
document.getElementById("joinRoom").addEventListener("click", () => {
  let room = document.getElementById("room").value; // 获取房间号
  let userId = document.getElementById("userId").value; // 获取用户ID
  // 如果用户ID未填写,则生成随机用户ID
  if (!userId) {
    userId = generateUserId();
    document.getElementById("userId").value = userId; // 显示生成的用户ID
  }
  // 发送加入房间的消息到服务器
  socket.send(JSON.stringify({ type: "join", room, userId }));
});
发送消息

当用户想要发送消息时,他们需要进行以下操作:

  1. 输入消息:在提供的文本输入框中输入消息内容。
  2. 点击发送消息按钮:用户点击"发送消息"按钮后,触发一个事件监听器。
  3. 事件处理:在点击事件的处理函数中,首先获取消息内容、房间号和用户ID的值。如果用户ID为空,同样会生成一个新的用户ID,并将其显示在用户ID输入框中。
  4. 发送消息请求 :使用socket.send()方法向服务器发送一个JSON字符串,该字符串表示一个对象,包含type字段(值为message),表示这是一个发送消息的请求,以及房间号、用户ID和消息内容。
  5. 清空消息输入框:发送消息后,清空消息输入框,以便用户输入新的消息。

代码片段如下:

javascript 复制代码
document.getElementById("sendMessage").addEventListener("click", () => {
  const message = document.getElementById("message").value; // 获取消息内容
  const room = document.getElementById("room").value; // 获取房间号
  let userId = document.getElementById("userId").value; // 获取用户ID
  // 如果用户ID未填写,则生成随机用户ID
  if (!userId) {
    userId = generateUserId();
    document.getElementById("userId").value = userId; // 显示生成的用户ID
  }
  // 发送消息到服务器
  socket.send(
    JSON.stringify({ type: "message", room, userId, content: message })
  );
  // 清空消息输入框
  document.getElementById("message").value = "";
});

这两个逻辑的核心都在于使用WebSocket的send()方法向服务器发送JSON格式的消息。服务器根据消息类型(type字段)来判断是处理加入房间的请求还是广播消息的请求。

完整代码如下

html 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>WebSocket客户端</title>
    <script>
      document.addEventListener("DOMContentLoaded", () => {
        // 创建WebSocket连接到本地服务器
        const socket = new WebSocket("ws://localhost:4090");

        // 设置默认房间号为1001
        document.getElementById("room").value = "1001";

        // 生成随机用户ID
        function generateUserId() {
          return "user_" + Math.random().toString(36).substr(2, 9);
        }

        // 加入房间按钮点击事件
        document.getElementById("joinRoom").addEventListener("click", () => {
          let room = document.getElementById("room").value; // 获取房间号
          let userId = document.getElementById("userId").value; // 获取用户ID
          // 如果用户ID未填写,则生成随机用户ID
          if (!userId) {
            userId = generateUserId();
            document.getElementById("userId").value = userId; // 显示生成的用户ID
          }
          // 发送加入房间的消息到服务器
          socket.send(JSON.stringify({ type: "join", room, userId }));
        });

        // 发送消息按钮点击事件
        document.getElementById("sendMessage").addEventListener("click", () => {
          const message = document.getElementById("message").value; // 获取消息内容
          const room = document.getElementById("room").value; // 获取房间号
          let userId = document.getElementById("userId").value; // 获取用户ID
          // 如果用户ID未填写,则生成随机用户ID
          if (!userId) {
            userId = generateUserId();
            document.getElementById("userId").value = userId; // 显示生成的用户ID
          }
          // 发送消息到服务器
          socket.send(
            JSON.stringify({ type: "message", room, userId, content: message })
          );
          // 清空消息输入框
          document.getElementById("message").value = "";
        });

        // 接收到服务器消息时的处理
        socket.onmessage = function (event) {
          // 将接收到的消息显示在页面上
          document.getElementById("serverMessages").textContent +=
            event.data + "\n";
        };
      });
    </script>
    <style type="text/css">
      main {
        display: flex;
        justify-content: space-between;
        width: 100vw;
        height: 100vh;
      }
      .message {
        flex: 0 0 75vw;
        height: 100vh;
        position: relative;
      }
      .message-main {
        flex: 0 0 75vw;
        height: 70vh;
        min-height: 100px;
      }
      .send-message {
        flex: 0 0 75vw;
        height: 300px;
        position: absolute;
        bottom: 0;
      }
      .send-message input {
        width: calc(75vw - 100px);
      }
      .login {
        flex: 0 0 20vw;
        height: 100vh;
      }
    </style>
  </head>
  <body>
    <h1>WebSocket客户端</h1>
    <main>
      <div class="message">
        <div
          class="message-main"
          id="serverMessages"
          style="white-space: pre"
        ></div>
        <div class="send-message">
          <input type="text" id="message" />
          <button id="sendMessage">发送消息</button>
        </div>
      </div>
      <div class="login">
        <p>
          <label for="userId">用户ID:</label>
          <input type="text" id="userId" placeholder="留空将自动生成" />
        </p>
        <p>
          <label for="room">房间号:</label>
          <input type="text" id="room" />
        </p>
        <p><button id="joinRoom">加入房间</button></p>
      </div>
    </main>
  </body>
</html>
相关推荐
诗书画唱3 分钟前
【前端面试题】JavaScript 核心知识点解析(第二十二题到第六十一题)
开发语言·前端·javascript
excel10 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子16 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构23 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep25 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss28 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风29 分钟前
html二次作业
前端·html
江城开朗的豌豆33 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵33 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae
画月的亮36 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf