实时通信技术中WebSocket的应用

作者:禅与计算机程序设计艺术

1.简介

WebSocket(Web Socket)是一种在单个TCP连接上进行全双工通讯的网络通信协议。它提供了一种双向通信的方式,允许服务端主动推送信息给客户端。随着HTML5出现,WebSocket已经成为现代浏览器和服务器之间通信的重要方式之一。WebSocket可以将多种多样的应用场景引入到互联网应用中。本文主要讨论WebSocket在实际应用中的一些典型应用场景、基本概念、算法原理及具体的代码实例。最后对未来的发展方向和存在的问题进行展望。希望通过阅读本文,读者能够掌握WebSocket的相关知识并在实际工作中运用得当。

2. WebSocket概念

WebSocket定义于RFC 6455标准文档,其目标是在单个TCP连接上提供全双工通信通道,允许服务端主动向客户端推送数据,而且WebSocket协议使得客户端和服务器之间的数据交换变得更加简单、高效。WebSocket协议属于可选层协议,它依赖HTTP协议,如果用户请求使用WebSocket协议访问页面,那么浏览器会自动使用WebSocket协议与服务器建立连接,实现数据交换。

2.1 基本概念

  • WebSocket是基于HTTP协议的一种通信协议,是一个独立的协议,它可以运行在TCP/IP四层以上传输层之上,它是由两端点——客户端和服务端组成。WebSocket协议是建立在TCP/IP之上的,需要建立两个TCP连接,一个用于客户端到服务端的连接,另一个用于服务端到客户端的连接。
  • WebSocket有两种工作模式:WebSocket客户端和WebSocket服务器模式。
  • WebSocket客户端工作流程如下:
    • 用户首先向服务器发送HTTP请求,要求建立WebSocket连接,若服务器接受该请求则返回101状态码表示同意建立WebSocket连接;
    • 如果建立成功,客户端和服务端之间就可以进行数据传输,采用的是TCP长连接机制。
  • WebSocket服务器工作流程如下:
    • 服务端接收到HTTP请求后,创建套接字,监听客户端连接请求;
    • 当客户端发起连接请求时,服务端接收到请求并分配一个唯一的ID作为WebSocket连接标识符;
    • 服务端发送101响应,表明已同意建立WebSocket连接,然后等待客户端发送数据;
    • 一旦客户端发送数据,服务端立即处理,并把数据发送给其他所有在线客户端。
  • WebSocket协议是一个双向通信协议,客户端和服务端都可以向对方发送消息,所以需要客户端和服务端都实现相应的协议。比如WebSocket客户端需要实现WebSocket API接口,而服务端需要部署相应的WebSocket服务器程序。
  • WebSocket协议中有很多不同的应用场景,包括:聊天系统、实时数据推送、游戏开发等。其中最常用的就是实时数据推送,即服务端向客户端推送最新的数据。

    2.2 数据帧

    WebSocket数据帧分为文本数据帧、二进制数据帧两种类型。文本数据帧一般用于发送文本数据,而二进制数据帧用于发送如图片、视频、音频等二进制文件。WebSocket协议定义了以下规则来区分数据帧:
  • 每个数据帧都是以固定长度的消息头开头,消息头中指定了数据帧的类型、长度、扩展字段等。
  • 在数据帧头部还有一个FIN位(Final Frame),用来标志数据帧是否是最后一个数据帧,即在一次完整的消息中,若有多个数据帧,则第一个数据帧的FIN为0,中间的数据帧的FIN均为1,最后一个数据帧的FIN为0。
  • 在消息头之后,紧跟着的是数据体。对于文本类型的数据帧来说,数据体就是文本字符串;对于二进制类型的数据帧来说,数据体就是字节数组或文件流。

    2.3 消息类型

    WebSocket协议定义了4种不同类型的消息:
  • 文本类型(TEXT):用于发送UTF-8编码的文本数据。
  • 二进制类型(BINARY):用于发送任意二进制数据,如图像、视频、音频等。
  • Ping消息:用于检查客户端与服务器之间的连接是否正常。
  • Pong消息:服务器在收到Ping消息后,应当返回Pong消息。

    2.4 握手协商过程

    WebSocket协议规定了客户端和服务端的握手协商过程。具体来说,握手协商过程分为三个步骤:
  1. 请求连接:首先,客户端发送一个WebSocket连接请求,包含请求资源的URI,以及一些请求首部字段(如Cookie)。
  2. 服务器回应连接:接着,服务端接收到客户端的连接请求,确认并回复101状态码,表明已同意建立WebSocket连接。同时,服务端可能会发送一些与WebSocket连接相关的首部字段。
  3. WebSocket连接建立:第三步,客户端和服务端完成握手协商,升级为WebSocket协议。至此,WebSocket连接就建立好了。

    2.5 URI scheme

    WebSocket协议的URI scheme为ws://或wss://,分别表示WebSocket的非加密连接和加密连接,采用http协议之上。

示例:

ws://example.com/websocket
wss://secure.example.com:8080/websocket

3. WebSocket基本算法原理

WebSocket协议在协议栈中处于第7层,因此可以参与到互联网协议栈中各种通信进程中,例如HTTP请求、HTTPS请求、FTP请求等。WebSocket采用客户端/服务器模型,包含客户端和服务器两个部分,并且每个部分都可以主动发起连接请求,也可以被动接受连接请求。为了建立WebSocket连接,客户端和服务器都会在HTTP协议之上进行一次握手,连接建立完成后,双方可以直接进行消息的传递。WebSocket协议中定义了一系列功能,比如WebSocket允许服务端主动推送信息给客户端,并支持服务器向客户端推送数据,WebSocket服务器可以支持推送消息、支持多路复用、支持压缩等。下面,我们结合WebSocket协议基本算法原理,分析一下具体的应用场景、基本概念、算法原理及具体的代码实例。

4. WebSocket实战案例——发布订阅系统

发布订阅(Publish/Subscribe)模式是面向消息的分布式计算模型,它是一种在消息通道上进行异步通信的模式,允许一组终端(称为发布者)发送消息,而不必知道谁对这些消息感兴趣。消息通常是有限的,因此发布者和订阅者的数量往往是多对多的关系,每个终端都可以作为发布者,也可以作为订阅者,因此订阅者必须保持健康状态,以便在需要时接收发布者的消息。

通过WebSocket协议,我们可以很方便地构建发布订阅系统,下面用发布订阅系统来实现简单的聊天室功能。首先,我们编写WebSocket服务端代码:

const express = require('express');
const app = express();

// 保存订阅的客户端对象列表
let subscribers = [];

app.use(express.json());

// 启动WebSocket服务
const server = require('http').createServer(app);
const wss = new (require('ws')).Server({server});

// 监听新连接事件
wss.on('connection', ws => {
  // 将新的客户端添加到订阅者列表中
  console.log(`[Server] New client connected`);
  subscribers.push(ws);

  // 向所有订阅者发送消息
  const message = JSON.stringify({
    type:'message',
    data: `A new subscriber has joined`
  });
  broadcast(message);

  // 监听客户端关闭事件
  ws.on('close', () => {
    // 从订阅者列表中移除该客户端
    subscribers.splice(subscribers.indexOf(ws), 1);

    // 向所有订阅者发送消息
    const leftMessage = JSON.stringify({
      type: 'left',
      data: `${ws._socket.remoteAddress} disconnected`
    });
    broadcast(leftMessage);
    console.log(`[Server] ${ws._socket.remoteAddress} disconnected`);
  });
});

function broadcast(data) {
  for (let i = 0; i < subscribers.length; i++) {
    try {
      // 向订阅者发送消息
      subscribers[i].send(data);
    } catch (err) {}
  }
}

// 启动服务端口
server.listen(3000, function() {
  console.log('[Server] Listening on port %d', server.address().port);
});

上面代码主要负责启动WebSocket服务,保存客户端订阅者列表,并管理订阅者间的消息广播。其中,broadcast函数用于将消息广播给所有订阅者,包括新增订阅者加入后的欢迎消息和断开连接后的通知消息。

接下来,我们编写WebSocket客户端代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebSocket Chat</title>
</head>
<body>
  <!-- 欢迎信息 -->
  <h1 id="welcome"></h1>

  <!-- 消息列表 -->
  <ul id="messages"></ul>

  <!-- 输入框 -->
  <input type="text" id="messageBox" placeholder="Say something..." />

  <!-- 脚本 -->
  <script src="/socket.io/socket.io.js"></script>
  <script>
    // 创建Socket连接
    let socket = io();

    // 获取欢迎信息
    socket.emit('join', {username: prompt("Please enter your name:")});

    // 绑定收到的消息
    socket.on('message', msg => {
      const li = document.createElement('li');
      li.textContent = `${msg.username}: ${msg.content}`;
      document.getElementById('messages').appendChild(li);
    });

    // 绑定用户加入信息
    socket.on('joined', username => {
      document.getElementById('welcome').innerHTML = `<p>Welcome to the chat room, ${username}!</p>`;
    });

    // 绑定用户离开信息
    socket.on('left', username => {
      const li = document.createTextNode(`${username} left.`);
      document.getElementById('messages').appendChild(li);
    });

    // 绑定提交按钮点击事件
    document.getElementById('messageBox').addEventListener('keypress', e => {
      if (e.keyCode === 13 &&!e.shiftKey) {
        e.preventDefault();

        // 获取输入的内容
        const content = document.getElementById('messageBox').value;

        // 清空输入框
        document.getElementById('messageBox').value = '';

        // 发送消息
        socket.emit('message', {
          username: prompt("Enter your name:"),
          content: content
        });
      }
    });
  </script>
</body>
</html>

上面代码主要负责初始化WebSocket连接,监听消息、欢迎消息和离开消息的接收,以及用户输入消息发送。由于WebSocket协议没有严格的客户端-服务器模式的实现,因此这里的WebSocket客户端也具有“模拟”客户端的效果,所以只需准备好一个普通的HTML页面即可,不需要特别复杂的库或者插件。

猜你喜欢

转载自blog.csdn.net/universsky2015/article/details/133446696