asio的socket创建与连接的基础实现和与C风格的socket网络通信的对比

asio socket创建与连接的基础实现和与C风格的socket网络通信的对比

一、整体功能概述

基于Boost.Asio库实现了 TCP 网络编程的核心基础操作,涵盖了端点(endpoint)创建TCP 套接字(socket)创建服务端监听器(acceptor)创建与绑定客户端连接(直连 IP / 域名解析)服务端接受连接等关键步骤。

Boost.Asio 是 C++ 的异步 I/O 库,封装了操作系统的网络 API,提供了更优雅的面向对象接口和跨平台能力。


二、代码模块解析

1. 头文件与命名空间
#include<boost/asio.hpp>  // Boost.Asio核心头文件
#include<iostream>
using namespace boost;  // 简化boost命名空间使用(实际项目中不建议全局using,易冲突)
  • Boost.Asio 的核心组件都在boost::asio命名空间下,比如io_contextip::tcpsocketacceptor等。
2. 端点(Endpoint)创建:client_end_point & server_end_point

端点是网络通信的地址 + 端口抽象,对应 TCP/IP 的套接字地址结构(如 C 语言的sockaddr_in)。

  • C 语言的sockaddr_in
struct sockaddr_in {
    unsigned short sin_family; // 协议族,与 socket()函数的第一个参数相同。
    unsigned short sin_port; // 16 位端口号,大端序。用 htons(整数的端口)转换。
    struct in_addr sin_addr; // IP 地址的结构体。192.168.101.138
    unsigned char sin_zero[8]; // 未使用,为了保持与 struct sockaddr 一样的长度而添加。
};
  • Boost.Asio创建 TCP 客户端和服务端端点(endpoint
// 客户端端点(指定具体IP和端口)
int client_end_point() {
    std::string raw_ip_address = "127.4.8.1";
    unsigned short port_num = 3333;
    boost::system::error_code ec;  // Boost的错误码(替代异常,更灵活)
    // 解析IP地址(字符串→asio::ip::address对象)
    asio::ip::address ip_address = asio::ip::make_address(raw_ip_address, ec);
    if (ec.value() != 0) {  // 错误处理
        std::cout << "Failed to parse the IP address Error code = " << ec.value() << ".Message is " << ec.message();
        return ec.value();
    }
    // 创建TCP端点(IP+端口)
    asio::ip::tcp::endpoint ep(ip_address, port_num);
    return 0;
}

// 服务端端点(绑定所有IPv6地址+端口,也可改用v4::any())
int server_end_point() {
    unsigned short port_num = 3333;
    asio::ip::address ip_address = asio::ip::address_v6::any();  // 通配地址:监听所有网卡
    asio::ip::tcp::endpoint ep(ip_address, port_num);
    return 0;
}
  • 核心逻辑:将字符串 IP 解析为 Asio 的ip::address对象,再结合端口创建ip::tcp::endpoint(封装了地址和端口的面向对象结构)。
  • 错误处理:使用boost::system::error_code替代异常(也可使用异常,Asio 支持两种方式)。
3. TCP 套接字创建:create_tcp_socket

套接字是网络通信的文件描述符抽象,Asio 用ip::tcp::socket类封装。

int create_tcp_socket() {
    asio::io_context ioc;  // Asio的核心:I/O上下文(管理所有I/O操作的事件循环)
    asio::ip::tcp protocol = asio::ip::tcp::v4();  // 指定TCPv4协议
    asio::ip::tcp::socket sock(ioc);  // 创建套接字(关联I/O上下文)
    
    boost::system::error_code ec;
    sock.open(protocol, ec);  // 打开套接字(对应C语言的socket()函数)
    if (ec.value() != 0) {
        std::cout << "Failed to open the socket! Error code = " << ec.value() << ".Message is " << ec.message();
        return ec.value();
    }
    return 0;
}
  • 关键组件io_context是 Asio 的核心,所有 I/O 操作(如读写、连接、接受)都需要关联到它,它负责调度事件。
  • sock.open():对应 C 语言的socket(AF_INET, SOCK_STREAM, 0)(创建 TCP 套接字)。
4. 服务端监听器(Acceptor)创建:create_acceptor_socket & bind_acceptor_socket

服务端需要用acceptor(监听器)绑定端口并监听连接,Asio 的ip::tcp::acceptor封装了这一逻辑。

// 新版便捷写法:一行完成创建、打开、绑定
int create_acceptor_socket(){
    asio::io_context ios;
    asio::ip::tcp::acceptor a(ios, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 3333));
    return 0;
}

// 老版本:分步实现(打开→绑定)
int bind_acceptor_socket() {
    unsigned short port_num = 3333;
    asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num);
    asio::io_context ios;
    asio::ip::tcp::acceptor acceptor(ios, ep.protocol());  // 打开协议(TCPv4)
    boost::system::error_code ec;
    acceptor.bind(ep, ec);  // 绑定端点(对应C语言的bind())
    if (ec.value() != 0) {
        std::cout << "Failed to bind the acceptor socket." << "Error code = " << ec.value() << ".Message: " << ec.message();
        return ec.value();
    }
}
  • acceptor的作用:对应 C 语言的socket() + bind() + listen()组合,Asio 提供了便捷构造函数(直接传入 endpoint,一步完成所有操作)和分步操作(更灵活)两种方式。
  • ip::address_v4::any():对应 C 语言的INADDR_ANY(绑定所有网卡的 IP)。
5. 客户端连接:connect_to_end(直连 IP)& dns_connect_to_end(域名解析)

客户端通过套接字连接服务端端点,Asio 封装了连接和 DNS 解析逻辑。

// 直连IP:同步连接
int connect_to_end(){
    std::string raw_ip_address = "192.168.1.124";
    unsigned short port_num = 3333;
    try {
        asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
        asio::io_context ios;
        asio::ip::tcp::socket sock(ios, ep.protocol());  // 创建套接字
        sock.connect(ep);  // 连接端点(对应C语言的connect())
    }catch (system::system_error& e) {  // 异常处理(替代error_code)
        std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what();
        return e.code().value();
    }
}

// 域名解析连接:通过DNS获取IP后连接
int dns_connect_to_end() {
    std::string host = "llfc.club";
    std::string port_num = "3333";
    asio::io_context ios;
    try {
        asio::ip::tcp::resolver resolver(ios);  // DNS解析器
        // 解析域名→多个端点(可能是IPv4/IPv6)
        asio::ip::tcp::resolver::results_type endpoints = resolver.resolve(host, port_num);
        asio::ip::tcp::socket sock(ios);
        // 遍历端点并连接(Asio自动处理,直到成功或失败)
        asio::connect(sock, endpoints);
    }
    catch (system::system_error& e) {
        std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what();
        return e.code().value();
    }
}
  • resolver(解析器):对应 C 语言的getaddrinfo()(DNS 解析),Asio 封装了解析过程,返回results_type(多个端点的集合)。
  • asio::connect():自动遍历解析后的端点,尝试连接,直到成功或全部失败(简化了 C 语言中手动遍历addrinfo链表的逻辑)。
6. 服务端接受连接:accept_new_connection

服务端通过acceptor接受客户端的连接请求,得到一个新的套接字用于通信。

int accept_new_connection() {
    const int BACKLOG_SIZE = 30;  // 监听队列大小(对应listen()的backlog参数)
    unsigned short port_num = 3333;
    asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num);
    asio::io_context ios;
    try {
        asio::ip::tcp::acceptor acceptor(ios, ep.protocol());
        acceptor.bind(ep);  // 绑定端口
        acceptor.listen(BACKLOG_SIZE);  // 开始监听(对应C语言的listen())
        asio::ip::tcp::socket sock(ios);
        acceptor.accept(sock);  // 阻塞等待连接(对应C语言的accept())
        // 此时sock是与客户端通信的套接字
    }
    catch (system::system_error& e) {
        std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what();
        return e.code().value();
    }
}
  • acceptor.listen(BACKLOG_SIZE):设置监听队列大小,对应 C 语言的listen(sockfd, BACKLOG_SIZE)
  • acceptor.accept(sock):阻塞等待客户端连接,成功后将新的套接字写入sock(对应 C 语言的accept()返回新的文件描述符)。

三、与 C 语言 BSD Socket 的对比

C 语言的 BSD Socket 是过程式的底层 API,直接调用操作系统的系统调用;而 Boost.Asio 是面向对象的封装,基于 C++ 的特性(如类、异常、模板)简化了开发,同时保留了底层功能的灵活性。以下是关键操作的对比:

功能步骤Boost.Asio(C++)实现C 语言 BSD Socket 实现
1. 初始化需创建io_context(I/O 上下文,管理所有 I/O 操作)无需初始化,直接调用系统调用
2. 创建 TCP 套接字asio::ip::tcp::socket sock(ioc); sock.open(tcp::v4());int sockfd = socket(AF_INET, SOCK_STREAM, 0);(返回文件描述符
3. 定义端点 / 地址asio::ip::tcp::endpoint ep(ip::make_address("127.0.0.1"), 3333);(面向对象)手动填充sockaddr_in结构体:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(3333);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
4. 服务端绑定端口acceptor.bind(ep);(acceptor 封装了套接字)bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));(需强制类型转换)
5. 服务端监听acceptor.listen(BACKLOG_SIZE);listen(sockfd, BACKLOG_SIZE);
6. 服务端接受连接acceptor.accept(sock);(新连接写入 sock 对象)int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);(返回新文件描述符)
7. 客户端连接sock.connect(ep);(直接传入 endpoint 对象)connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));(需强制类型转换)
8. DNS 域名解析resolver.resolve(host, port); + asio::connect(sock, endpoints);(自动遍历端点)调用getaddrinfo()获取addrinfo链表,手动遍历链表并调用connect(),最后释放freeaddrinfo()
9. 错误处理支持两种方式:
1. boost::system::error_code(返回错误码)
2. 异常捕获(system_error
检查函数返回值(如-1),通过errno获取错误码,调用perror()strerror()查看信息
10. 跨平台自动适配 Windows/Linux/macOS(Asio 封装了不同系统的差异)需处理系统差异:
Windows 需初始化 WSA(WSAStartup()
数据类型差异(如SOCKET vs int
函数名差异(如closesocket() vs close()
11. 异步操作原生支持异步 I/O(如async_connectasync_acceptasync_read/write),通过回调或协程处理需手动实现(如使用 select/poll/epoll/kqueue,或线程),开发复杂度高

四、核心差异总结

  1. 编程范式

    • C 语言过程式,基于文件描述符(整数)和结构体,手动管理内存和资源,代码冗长且易出错(如强制类型转换、结构体填充、内存释放)。
    • Boost.Asio面向对象,用socketacceptorendpoint等类封装底层操作,隐藏了繁琐的细节(如字节序转换、地址结构体填充)。
  2. 错误处理

    • C 语言:通过返回值(如-1)和errno判断错误,需手动调用perror()strerror()解析错误信息。
    • Boost.Asio:支持错误码error_code)和异常两种方式,错误信息更丰富(包含错误码和描述),且类型安全。
  3. DNS 解析

    • C 语言:需手动调用getaddrinfo(),遍历返回的addrinfo链表,处理每个端点的连接,最后释放资源。
    • Boost.Asio:用resolver自动解析域名,返回results_typeasio::connect()自动遍历端点并连接,简化了代码。
  4. 异步 I/O

    • C 语言:异步操作需手动使用select/epoll/kqueue等 I/O 多路复用技术,或线程池,开发难度大。
    • Boost.Asio:原生支持异步操作(async_*系列函数),通过回调函数、协程(C++20)或future处理结果,是 Asio 的核心优势之一。
  5. 跨平台性

    • C 语言:需处理不同系统的差异(如 Windows 的 WSA 初始化、closesocket() vs close())。
    • Boost.Asio:封装了系统差异,代码无需修改即可在不同平台运行。

五、注意事项

  1. io_context的作用:Asio 的所有 I/O 操作都依赖io_context,即使是同步操作,也需要创建它(异步操作还需要调用io_context.run()启动事件循环)。
  2. 异常与错误码:代码中同时使用了异常和error_code,Asio 的所有操作都提供了两种重载(带error_code参数的版本不抛异常,不带的版本抛异常),可根据场景选择。
  3. 命名空间:代码中使用using namespace boost;简化代码,但实际项目中建议使用namespace ba = boost::asio;这样的别名,避免命名冲突。
  4. 资源管理:Asio 的类(如socketacceptor)都实现了 RAII(资源获取即初始化),析构时会自动释放资源(如关闭套接字),无需手动调用close()(C 语言需手动关闭文件描述符)。
Asio(Boost.Asio)库中,当不再需要异步接收数据时,需要正确地终止异步操作并释放相关的资源。以下是一些步骤来合理地终止一个异步接收线程: 1. **标记停止标志**:首先,创建一个bool变量作为停止标志,比如`stop_receiving`。当需要关闭连接时,将其设置为true。 ```cpp std::atomic<bool> stop_receiving(false); ``` 2. **检查停止标志**:在处理接收到的数据之前或之后,检查这个标志。如果为true,则中断接收过程。 ```cpp void on_receive(const boost::system::error_code& ec, std::size_t bytes_transferred) { if (!ec && !stop_receiving) { // ...继续接收数据... } else { // 数据接收结束或有错误,后续处理 handle_error(ec); } } void shutdown() { stop_receiving = true; } ``` 3. **取消异步操作**:如果你正在等待一个正在进行的异步操作(如read),可以调用`asio::post()`函数取消它,并确保所有依赖于该操作的等待都会被适当地中断。 ```cpp if (!stop_receiving) { socket.async_read_some(buffer, read_handler); // ...其他异步操作... auto cancel_handler = [&, ec = asio::error_code{}] (const error_code&) mutable { if (!ec) // 取消操作成功 shutdown(); }; io_context.post(cancel_handler); } ``` 4. **等待完成**:如果你希望等待所有正在进行的操作完成后才真正退出,可以在`shutdown()`函数中加入一个循环,直到所有的异步任务都完成。 ```cpp void shutdown() { stop_receiving = true; while (async_operations_in_progress) { io_context.run_one(); // 运行一个事件循环轮询,直到没有活动操作 } } ``` 5. **清理资源**:最后,在异步操作完全结束后,关闭socket,释放缓冲区等资源。 ```cpp void handle_error(const boost::system::error_code& ec) { if (!ec) return; // 错误处理,例如关闭socket close_socket(socket); // 如果没有其他未处理的错误,关闭io_context io_context.stop(); } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值