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>
相关推荐
GIS程序媛—椰子31 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
DogEgg_00137 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端40 分钟前
Content Security Policy (CSP)
前端·javascript·面试
木舟100944 分钟前
ffmpeg重复回听音频流,时长叠加问题
前端
王大锤43911 小时前
golang通用后台管理系统07(后台与若依前端对接)
开发语言·前端·golang
半桶水专家1 小时前
用go实现创建WebSocket服务器
服务器·websocket·golang
我血条子呢1 小时前
[Vue]防止路由重复跳转
前端·javascript·vue.js
黎金安1 小时前
前端第二次作业
前端·css·css3
啦啦右一1 小时前
前端 | MYTED单篇TED词汇学习功能优化
前端·学习