当前位置: 首页 > article >正文

Boost.Asio 同步读写及客户端 - 服务器实现详解

Boost.Asio 同步读写及客户端 - 服务器实现详解

参考文献

  1. Boost.Asio 官方文档
  2. 学习资料来源: 参考网址

一、引言

Boost.Asio作为一个强大的跨平台网络编程库,为开发者提供了丰富的网络操作接口。在之前的学习中,我们已接触到其同步读写的API函数,本文将在此基础上,深入探讨如何运用这些API构建一个完整且能实际运行的客户端与服务器示例,且双方均采用阻塞式的同步读写方式来实现通信。

二、客户端设计

  1. 设计思路
    • 首要任务是依据服务器的IP地址与端口号构建一个 endpoint,这是后续连接的目标定位信息。
    • 借助 socket 对象,向已确定的 endpoint 发起连接请求,尝试与服务器建立通信链路。
    • 成功连接后,运用同步读写机制,实现向服务器发送数据以及接收服务器响应数据的功能。
  2. 代码剖析
#include <iostream>
#include <boost/asio.hpp>
using namespace std;
using namespace boost::asio::ip;
const int MAX_LENGTH = 1024;

int main() {
    try {
        // 1. 初始化Boost.Asio的核心对象io_context,它为后续的网络操作提供运行环境
        boost::asio::io_context ioc;

        // 2. 构建远程服务器的endpoint,明确通信目标的IP与端口,这里以本地回环地址127.0.0.1及端口10086为例
        tcp::endpoint remote_ep(address::from_string("127.0.0.1"), 10086);

        // 3. 创建用于网络通信的socket对象,绑定到之前创建的io_context
        tcp::socket sock(ioc);

        // 4. 向服务器发起连接请求,并通过error_code检查连接结果
        boost::system::error_code error = boost::asio::error::host_not_found;
        sock.connect(remote_ep, error);

        // 若连接失败,输出错误详情并终止程序
        if (error) {
            cout << "连接失败,错误代码: " << error.value() << " 错误信息: " << error.message() << endl;
            return 0;
        }

        // 5. 提示用户输入要发送给服务器的消息
        std::cout << "请输入消息: ";
        char request[MAX_LENGTH];
        std::cin.getline(request, MAX_LENGTH);

        // 6. 计算输入消息的长度,并通过Boost.Asio的write函数将消息发送至服务器
        size_t request_length = strlen(request);
        boost::asio::write(sock, boost::asio::buffer(request, request_length));

        // 7. 准备接收服务器的回复,利用read函数从socket读取数据至reply缓冲区
        char reply[MAX_LENGTH];
        size_t reply_length = boost::asio::read(sock,
            boost::asio::buffer(reply, request_length));

        // 8. 输出服务器的回复内容
        std::cout << "服务器回复: ";
        std::cout.write(reply, reply_length);
        std::cout << "\n";
    }
    catch (std::exception& e) {
        // 捕获并处理任何可能出现的异常,输出错误信息
        std::cerr << "发生异常: " << e.what() << endl;
    }

    return 0;
}

三、服务器设计

服务器端的设计相对复杂,主要由两个关键函数协同完成工作。

  1. session 函数
    • 功能定位:此函数专门负责处理单个客户端的连接请求。每当服务器监听到有新客户端接入时,便会立即调用该函数,对客户端的数据交互进行管理。
    • 实现逻辑:采用循环结构,持续监听客户端发送的数据。一旦接收到数据,首先检查错误状态,若客户端正常关闭连接,函数能及时察觉并结束循环;若出现其他错误,则抛出异常。对于接收到的有效数据,不仅会在服务器端打印出来源客户端的IP地址以及消息内容,还会将数据原封不动地回传给客户端,实现简单的“echo”功能。
    • 代码详情
void session(std::shared_ptr<tcp::socket> sock) {
    try {
        for (;;) {
            char data[MAX_LENGTH];
            memset(data, '\0', MAX_LENGTH); // 初始化缓冲区,确保数据干净

            // 从客户端socket读取数据,同时关注可能出现的错误
            boost::system::error_code error;
            size_t length = sock->read_some(boost::asio::buffer(data, MAX_LENGTH), error);

            // 根据不同的错误情况进行相应处理
            if (error == boost::asio::error::eof) {
                std::cout << "连接被客户端关闭" << endl;
                break;
            } else if (error) {
                throw boost::system::system_error(error);
            }

            // 打印客户端连接信息及发送的消息
            std::cout << "收到来自 " << sock->remote_endpoint().address().to_string() << " 的消息" << endl;
            std::cout << "消息内容: " << data << endl;

            // 将接收到的消息原样回传给客户端
            boost::asio::write(*sock, boost::asio::buffer(data, length));
        }
    }
    catch (std::exception& e) {
        // 捕获并处理线程内出现的异常,输出错误详情
        std::cerr << "线程中发生异常: " << e.what() << "\n" << std::endl;
    }
}
  1. server 函数
    • 功能定位:作为服务器的启动与管理核心,负责创建服务器的监听机制,并协调客户端连接的处理流程。
    • 实现逻辑:首先创建一个 acceptor 对象,绑定到指定的IP地址与端口,开始监听客户端的连接请求。进入无限循环后,每当有新客户端连接到来,便创建一个新的 socket 对象用于与该客户端通信,并立即启动一个独立的线程,在线程中调用 session 函数处理与该客户端的交互,同时将线程分离,使其能够独立运行,避免主线程阻塞。
    • 代码详情
void server(boost::asio::io_context& io_context, unsigned short port) {
    // 创建acceptor,用于监听指定端口的客户端连接
    tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), port));

    for (;;) {
        // 为新连接创建socket对象
        std::shared_ptr<tcp::socket> socket(new tcp::socket(io_context));
        acceptor.accept(*socket); // 接受客户端连接

        // 为每个连接创建独立线程处理通信,分离线程以独立运行
        auto t = std::make_shared<std::thread>(session, socket);
        t->detach(); 
    }
}
  1. 多线程处理优势:通过为每个客户端连接分配独立的线程,并在其中进行读写操作,确保了服务器的 acceptor 能够持续监听新连接,不会因某个连接的阻塞读写操作而陷入停滞,有效提升了服务器的并发处理能力。

四、同步读写的特性剖析

  1. 优点
    • 编程简易性:同步读写模式下,代码逻辑呈现顺序执行的特点,开发者无需处理复杂的异步回调嵌套,编写过程清晰明了,对于初学者或是处理简单网络场景的项目而言,上手难度较低,易于快速实现功能。
    • 维护便利性:由于代码按照线性顺序执行,没有异步操作带来的回调函数分散在各处的问题,程序的逻辑流易于追踪与理解,后续的代码维护与调试工作相对轻松。
  2. 缺点
    • 阻塞隐患:在服务器端,若客户端未能及时发送数据, read 操作将会使线程陷入阻塞状态,白白消耗系统资源,直到有数据可读为止,这在高并发场景下极易引发性能瓶颈。
    • 线程资源受限:鉴于每个连接都需要独立的线程来维持同步读写操作,而操作系统对线程数量存在上限限制(例如Linux系统默认2048个线程),当连接数大量增加时,系统可能无法创建足够的线程,导致新连接无法及时处理。
    • 线程切换成本:大量线程的频繁切换会消耗大量的CPU时间用于上下文保存与恢复,使得系统用于实际业务处理的资源被削减,整体性能受到负面影响。
    • 粘包问题未解决:无论是客户端还是服务器端,当前的同步读写实现都没有考虑到TCP协议在数据传输过程中可能出现的粘包现象,这可能导致接收端无法准确识别消息边界,造成数据解析错误。

五、改进方向展望

为克服同步读写的固有缺陷,引入异步读写模式成为必然选择:

  1. 并发性能飞跃:异步操作允许在等待数据读写完成的过程中,CPU能够转而处理其他任务,避免线程阻塞,从而能够在相同硬件资源下承载更多的客户端连接,大幅提升服务器的并发处理能力。
  2. 全双工通信优势:发送与接收数据的过程相互独立,不再受限于同步模式下的先后顺序,能够更好地适应复杂多变的通信需求,例如实时双向数据交互的场景。
  3. 粘包处理机制完善:可以通过在数据传输中添加特定的消息分隔符,或者在数据头部嵌入长度标识符等方式,让接收端能够精准地识别每条消息的边界,确保数据的完整性与准确性,提升系统的可靠性。

http://www.kler.cn/a/500731.html

相关文章:

  • 【数据库系统概论】数据库恢复技术
  • antd-design-vue1.7.8浏览器中使用
  • 时序数据库的订阅对比:TDengine vs InfluxDB 谁更强?
  • Java Spring Boot实现基于URL + IP访问频率限制
  • 人机交互 | 期末复习(上)| 补档
  • 前端学习-事件流,事件捕获,事件冒泡以及阻止冒泡以及相应案例(二十八)
  • YOLOv10-1.1部分代码阅读笔记-downloads.py
  • Docker基础篇(一)
  • 如何在C#中使用COM接口
  • Linux web资产收集
  • 计算机视觉算法实战——打电话行为检测
  • Windows下Dll在Unity中使用的一般方式
  • 运维高级课作业一
  • MeCo——给预训练数据增加源信息,就能减少33%的训练量并且提升效果
  • MYSQL-创建数据库 CREATE DATABASE (十一)
  • 蠕虫病毒会给服务器造成哪些危害?
  • vue3后台系统动态路由实现
  • centos 搭建nginx+配置域名+windows访问
  • Vue 开发者的 React 实战指南:性能优化篇
  • 【Ubuntu与Linux操作系统:九、Shell编程】
  • Perl语言的编程范式
  • 简历整理YH
  • Django 社团管理系统的设计与实现
  • SpringBoot项目实战(39)--Beetl网页HTML文件中静态图片及CSS、JS文件的引用和展示
  • 如何在Go语言开发中实现高性能的分布式日志收集