【网络进阶】WebSocket协议

1. Web实时技术的应用

实时Web技术在许多应用场景中具有重要意义,它们使得用户可以立即获得最新的数据和信息,从而提高了用户体验。以下是一些实时Web技术的典型应用:

  1. 聊天和通讯:在线聊天应用、企业通讯软件以及社交网络平台都需要实时技术来实现用户间的即时通信。一些知名的聊天应用,如WhatsApp、微信、Telegram和Slack等,都采用了实时技术。

  2. 在线协作:在线文档编辑、实时画板、项目管理工具等需要多个用户同时对文档或项目进行协作。谷歌文档(Google Docs)和微软的Office 365就是在线协作的典型例子。

  3. 实时消息推送:新闻网站、博客、股票行情、天气预报等应用需要实时向用户推送最新的数据和信息。通过实时技术,用户无需手动刷新页面,就可以接收到最新的内容。

  4. 在线游戏:多人在线游戏、实时策略游戏以及虚拟现实应用都需要实时技术来保证玩家之间的交互。实时技术可以降低延迟,提高游戏的流畅性和可玩性。

  5. 物联网(IoT):实时Web技术可以用于监控和控制物联网设备。例如,智能家居系统可以实时反馈家庭设备的状态,用户可以随时控制家电设备。

  6. 实时分析:实时数据分析、数据可视化和大数据处理需要实时技术来支持。例如,网站访问统计、销售数据分析、服务器性能监控等应用需要实时显示数据。

  7. 实时音视频通信:语音通话、视频会议和直播应用需要实时技术来传输音视频数据。这些应用通常使用WebRTC这样的实时通信技术。

  8. 实时定位和导航:基于位置的服务、地图应用和交通信息系统需要实时技术来获取和更新位置数据。例如,Uber和高德地图等应用需要实时显示用户和车辆的位置。

实时Web技术在许多领域都有广泛应用,它们为用户提供了更加丰富、更加实时的网络体验。在未来,随着5G、边缘计算等技术的普及,实时Web技术将会更加普遍地应用于各种场景。

2. WebSocket协议介绍

WebSocket协议是一种基于TCP的通信协议,它提供了一种全双工、低延迟的通信方式,使得客户端和服务器之间可以进行双向实时数据交换。WebSocket协议的设计目的是为了在Web浏览器和Web服务器之间提供更高效、更实时的双向通信。

WebSocket协议与HTTP协议不同,它使用了自己独立的协议标识符“ws”(不加密)或“wss”(加密),例如:ws://example.com 或 wss://example.com。WebSocket协议最初是作为HTML5标准的一部分提出的,现在已经被广泛应用于Web应用程序、在线游戏、实时通讯等领域。

2.1 WebSocket的工作原理

  1. 建立连接:首先,客户端会向服务器发送一个HTTP请求,请求中包含“Upgrade”和“Connection”头字段,表示希望将连接升级为WebSocket。如果服务器同意升级,它会返回一个状态码为101的HTTP响应,表示连接已经升级。

  2. 数据帧:在WebSocket连接建立后,客户端和服务器可以开始发送和接收数据。数据在WebSocket中以“帧”为单位传输,每个帧都包含一个标识符、负载长度和负载数据。标识符用于指示帧的类型(例如文本、二进制数据或控制帧)。

  3. 控制帧:WebSocket协议中有一些特殊的控制帧,用于管理连接的状态。例如,“关闭”帧用于通知对方关闭连接,“Ping”帧用于检测连接是否仍然活跃,“Pong”帧用作对“Ping”帧的响应。

  4. 关闭连接:当客户端或服务器希望关闭连接时,它们会发送一个“关闭”帧。收到“关闭”帧的一方应当回应一个“关闭”帧,然后双方都可以关闭TCP连接。在某些情况下,WebSocket连接可能因为网络原因或其他问题意外断开,这种情况下并不会发送“关闭”帧。

2.2 优点

  1. 双向实时通信:WebSocket提供了全双工通信,允许客户端和服务器同时发送和接收数据,从而实现实时交互。
  2. 低延迟:与基于HTTP的轮询或长轮询等技术相比,WebSocket能够大大降低数据交换的延迟。
  3. 减少网络开销:由于WebSocket在建立连接后可以保持长连接,因此可以减少频繁建立和关闭连接导致的额外网络开销。
  4. 易于集成:WebSocket与现有的Web技术(如HTML、CSS和JavaScript)兼容,因此开发者可以在不改变现有Web应用架构的前提下轻松地将WebSocket集成进来。

2.3. 使用场景

  1. 实时消息推送:例如聊天应用、新闻推送、股票行情等。
  2. 在线游戏:例如多人在线游戏、实时策略游戏等。
  3. 即时协作:例如在线文档编辑、实时画板等。
  4. 物联网(IoT):WebSocket可以用于实时监控和控制物联网设备。
  5. 其他实时应用:任何需要实时数据交换的场景都可以考虑使用WebSocket。

2.4 实现细节

  1. 安全性:为了提高安全性,可以使用加密的WebSocket连接(wss://)。此外,建议在服务器端实施适当的认证和授权策略。
  2. 跨域问题:WebSocket允许跨域连接,但需要注意的是,服务器端应检查请求头中的“Origin”字段以防止恶意连接。
  3. 心跳检测:为了确保连接的可靠性,可以定期发送“Ping”帧进行心跳检测,以便在连接中断时及时处理。
  4. 浏览器兼容性:虽然大多数现代浏览器都支持WebSocket,但仍需注意一些较旧版本的浏览器可能不支持。在这种情况下,可以考虑使用一些库(如Socket.IO)来提供自动降级到其他传输方式的功能。

3. WebSocket服务器实现

3.1 客户端代码(HTML & JavaScript)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Client</title>
    <style>
        #output {
      
      
            border: 1px solid #ccc;
            padding: 10px;
            width: 400px;
            height: 200px;
            overflow-y: scroll;
        }
    </style>
</head>
<body>
    <h1>WebSocket Client</h1>
    <label for="serverAddress">Server address:</label>
    <input type="text" id="serverAddress" value="ws://localhost">
    <label for="serverPort">Port:</label>
    <input type="number" id="serverPort" value="3000">
    <button id="connectBtn">Connect</button>
    <div id="output"></div>
    <button id="clearBtn">Clear</button><br>
    <input type="text" id="message" placeholder="Type your message...">
    <button id="sendBtn">Send</button>

    <script>
        function getBrowserInfo() {
      
      
            return {
      
      
                userAgent: navigator.userAgent,
                language: navigator.language,
                platform: navigator.platform,
                vendor: navigator.vendor,
            };
        }

        let ws;
        const serverAddress = document.getElementById('serverAddress');
        const serverPort = document.getElementById('serverPort');
        const connectBtn = document.getElementById('connectBtn');
        const output = document.getElementById('output');
        const message = document.getElementById('message');
        const sendBtn = document.getElementById('sendBtn');
        const clearBtn = document.getElementById('clearBtn');

        clearBtn.addEventListener('click', () => {
      
      
            output.innerHTML = '';
        });

        function appendOutput(text) {
      
      
            output.innerHTML += `${ 
        text}<br>`;
            output.scrollTop = output.scrollHeight;
        }

        connectBtn.addEventListener('click', () => {
      
      
            const address = `${ 
        serverAddress.value}:${ 
        serverPort.value}`;
            ws = new WebSocket(address);
            connectBtn.disabled = true;

            ws.addEventListener('open', () => {
      
      
                appendOutput(`Connected to server at ${ 
        address}`);
                ws.send(JSON.stringify(getBrowserInfo()));
            });

            ws.addEventListener('message', (event) => {
      
      
                appendOutput(`Server: ${ 
        event.data}`);
            });

            ws.addEventListener('close', () => {
      
      
                appendOutput('Disconnected from server');
                connectBtn.disabled = false;
            });

            ws.addEventListener('error', (event) => {
      
      
                appendOutput('Error: ' + event.message);
            });
        });

        sendBtn.addEventListener('click', () => {
      
      
            if (ws && ws.readyState === WebSocket.OPEN) {
      
      
                ws.send(message.value);
                appendOutput(`You: ${ 
        message.value}`);
                message.value = '';
            } else {
      
      
                appendOutput('Please connect to the server first.');
            }
        });

        message.addEventListener('keyup', (event) => {
      
      
            if (event.key === 'Enter') {
      
      
                sendBtn.click();
            }
        });
    </script>
</body>
</html>

3.2 服务器端代码(C++)

#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>
#include <string>
#include <thread>
#include <nlohmann/json.hpp>

namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
using json = nlohmann::json;

constexpr auto DEFAULT_PORT = "3000";

void handleSession(websocket::stream<tcp::socket> ws) {
    
    
  try {
    
    
    ws.accept();
    auto remote_endpoint = ws.next_layer().socket().remote_endpoint();
    std::cout << "Connected to client: " << remote_endpoint << std::endl;

    for (;;) {
    
    
      beast::flat_buffer buffer;
      ws.read(buffer);

      auto message = beast::buffers_to_string(buffer.data());
      std::cout << "Received: " << message << std::endl;

      // Check if received message is browser information
      try {
    
    
        auto browser_info = json::parse(message);
        if (browser_info.contains("userAgent") && browser_info.contains("language") &&
            browser_info.contains("platform") && browser_info.contains("vendor")) {
    
    
          std::cout << "Browser Information:\n";
          std::cout << "  User Agent: " << browser_info["userAgent"].get<std::string>() << std::endl;
          std::cout << "  Language: " << browser_info["language"].get<std::string>() << std::endl;
          std::cout << "  Platform: " << browser_info["platform"].get<std::string>() << std::endl;
          std::cout << "  Vendor: " << browser_info["vendor"].get<std::string>() << std::endl;
          continue;
        }
      } catch (...) {
    
    
        // Not a valid JSON or not containing browser information
      }

      ws.text(ws.got_text());
      ws.write(buffer.data());
    }
  } catch (beast::system_error const &se) {
    
    
    if (se.code() != websocket::error::closed)
      std::cerr << "Error: " << se.code().message() << std::endl;
  } catch (std::exception const &e) {
    
    
    std::cerr << "Error: " << e.what() << std::endl;
  }
}

int main(int argc, char *argv[]) {
    
    
  try {
    
    
    auto const address = net::ip::make_address("0.0.0.0");
    auto const port = static_cast<unsigned short>(std::atoi(argc == 2 ? argv[1] : DEFAULT_PORT));

    net::io_context ioc{
    
    1};
    tcp::acceptor acceptor{
    
    ioc, {
    
    address, port}};
    std::cout << "WebSocket server is listening on " << address << ":" << port << std::endl;

    for (;;) {
    
    
      tcp::socket socket{
    
    ioc};
      acceptor.accept(socket);
      std::thread{
    
    handleSession, websocket::stream<tcp::socket>{
    
    std::move(socket)}}.detach();
    }
  } catch (std::exception const &e) {
    
    
    std::cerr << "Error: " << e.what() << std::endl;
    return EXIT_FAILURE;
  }
}

注意如上代码中使用了Boost.Beast库来实现WebSocket和nlohmann/json的JSON库来处理JSON数据,Boost库的安装之前已经讲解过,下面说明nlohmann/json库的安装:

  1. 使用以下命令安装 wget(如果尚未安装):
sudo yum install wget
  1. 使用 wget 下载 nlohmann/json 库的单文件版本:
wget https://github.com/nlohmann/json/releases/download/v3.10.4/json.hpp

注意:请确保下载与您的代码兼容的版本。在此示例中,我下载了 3.10.4 版本。您可以在 此处 查找其他版本。

  1. 创建一个包含 nlohmann/json 的头文件的目录:
sudo mkdir -p /usr/local/include/nlohmann
  1. 将下载的 json.hpp 文件移动到刚刚创建的目录中:
sudo mv json.hpp /usr/local/include/nlohmann/

现在,nlohmann/json库已经安装在您的CentOS 7.6系统上。在C++代码中,您可以通过包含以下头文件来使用它:

#include <nlohmann/json.hpp>

在编译时,确保链接器可以找到nlohmann/json库的头文件。这通常不需要额外的编译器标志,但如果需要,您可能需要添加-I参数以指定头文件的安装路径。例如:

g++ your_code.cpp -o your_program -I/usr/local/include

3.3 测试结果

客户端:
在这里插入图片描述
服务器端:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_52665939/article/details/130498867
今日推荐