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

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

参考文献

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

一、引言

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

二、客户端设计

  1. 设计思路
    • 首要任务是依据服务器的IP地址与端口号构建一个 endpoint,这是后续连接的目标定位信息。
    • 借助 socket 对象,向已确定的 endpoint 发起连接请求,尝试与服务器建立通信链路。
    • 成功连接后,运用同步读写机制,实现向服务器发送数据以及接收服务器响应数据的功能。
  2. 代码剖析
cpp 复制代码
#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"功能。
    • 代码详情
cpp 复制代码
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 函数处理与该客户端的交互,同时将线程分离,使其能够独立运行,避免主线程阻塞。
    • 代码详情
cpp 复制代码
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. 粘包处理机制完善:可以通过在数据传输中添加特定的消息分隔符,或者在数据头部嵌入长度标识符等方式,让接收端能够精准地识别每条消息的边界,确保数据的完整性与准确性,提升系统的可靠性。
相关推荐
AIHE-TECH1 小时前
PLC实现HTTP协议JSON格式数据上报对接的参数配置说明
网络协议·http·json·url·网页·西门子plc·mes
LLLuckyGirl~1 小时前
计算机网络之---MAC协议
网络·计算机网络·macos
WoTrusSSL3 小时前
SSL 证书格式和证书文件扩展名:完整指南
网络·网络协议·ssl
wang09073 小时前
SSL,TLS协议分析
网络·网络协议·ssl
17´3 小时前
使用QT+OpenCV+C++完成一个简单的图像处理工具
c++·图像处理·qt·opencv
苹果3 小时前
C++二十三种设计模式之迭代器模式
c++·设计模式·迭代器模式
飞yu流星4 小时前
C++ 函数 模板
开发语言·c++·算法
秋说4 小时前
【网络协议】静态路由详解
网络协议·静态路由
Goldinger5 小时前
vscode 配置c/c++环境 中文乱码
c语言·c++·vscode
高 朗5 小时前
【GO基础学习】项目日志zap Logger使用
服务器·学习·golang·日志·zap