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>usingnamespaceboost;// 简化boost命名空间使用(实际项目中不建议全局using,易冲突)- Boost.Asio 的核心组件都在
boost::asio命名空间下,比如io_context、ip::tcp、socket、acceptor等。
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和端口)intclient_end_point(){std::string raw_ip_address="127.4.8.1";unsignedshortport_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();returnec.value();}// 创建TCP端点(IP+端口)asio::ip::tcp::endpointep(ip_address,port_num);return0;}// 服务端端点(绑定所有IPv6地址+端口,也可改用v4::any())intserver_end_point(){unsignedshortport_num=3333;asio::ip::address ip_address=asio::ip::address_v6::any();// 通配地址:监听所有网卡asio::ip::tcp::endpointep(ip_address,port_num);return0;}- 核心逻辑:将字符串 IP 解析为 Asio 的
ip::address对象,再结合端口创建ip::tcp::endpoint(封装了地址和端口的面向对象结构)。 - 错误处理:使用
boost::system::error_code替代异常(也可使用异常,Asio 支持两种方式)。
3. TCP 套接字创建:create_tcp_socket
套接字是网络通信的文件描述符抽象,Asio 用ip::tcp::socket类封装。
intcreate_tcp_socket(){asio::io_context ioc;// Asio的核心:I/O上下文(管理所有I/O操作的事件循环)asio::ip::tcp protocol=asio::ip::tcp::v4();// 指定TCPv4协议asio::ip::tcp::socketsock(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();returnec.value();}return0;}- 关键组件:
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封装了这一逻辑。
// 新版便捷写法:一行完成创建、打开、绑定intcreate_acceptor_socket(){asio::io_context ios;asio::ip::tcp::acceptora(ios,asio::ip::tcp::endpoint(asio::ip::tcp::v4(),3333));return0;}// 老版本:分步实现(打开→绑定)intbind_acceptor_socket(){unsignedshortport_num=3333;asio::ip::tcp::endpointep(asio::ip::address_v4::any(),port_num);asio::io_context ios;asio::ip::tcp::acceptoracceptor(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();returnec.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:同步连接intconnect_to_end(){std::string raw_ip_address="192.168.1.124";unsignedshortport_num=3333;try{asio::ip::tcp::endpointep(asio::ip::make_address(raw_ip_address),port_num);asio::io_context ios;asio::ip::tcp::socketsock(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();returne.code().value();}}// 域名解析连接:通过DNS获取IP后连接intdns_connect_to_end(){std::string host="llfc.club";std::string port_num="3333";asio::io_context ios;try{asio::ip::tcp::resolverresolver(ios);// DNS解析器// 解析域名→多个端点(可能是IPv4/IPv6)asio::ip::tcp::resolver::results_type endpoints=resolver.resolve(host,port_num);asio::ip::tcp::socketsock(ios);// 遍历端点并连接(Asio自动处理,直到成功或失败)asio::connect(sock,endpoints);}catch(system::system_error&e){std::cout<<"Error occured! Error code = "<<e.code()<<".Message: "<<e.what();returne.code().value();}}resolver(解析器):对应 C 语言的getaddrinfo()(DNS 解析),Asio 封装了解析过程,返回results_type(多个端点的集合)。asio::connect():自动遍历解析后的端点,尝试连接,直到成功或全部失败(简化了 C 语言中手动遍历addrinfo链表的逻辑)。
6. 服务端接受连接:accept_new_connection
服务端通过acceptor接受客户端的连接请求,得到一个新的套接字用于通信。
intaccept_new_connection(){constintBACKLOG_SIZE=30;// 监听队列大小(对应listen()的backlog参数)unsignedshortport_num=3333;asio::ip::tcp::endpointep(asio::ip::address_v4::any(),port_num);asio::io_context ios;try{asio::ip::tcp::acceptoracceptor(ios,ep.protocol());acceptor.bind(ep);// 绑定端口acceptor.listen(BACKLOG_SIZE);// 开始监听(对应C语言的listen())asio::ip::tcp::socketsock(ios);acceptor.accept(sock);// 阻塞等待连接(对应C语言的accept())// 此时sock是与客户端通信的套接字}catch(system::system_error&e){std::cout<<"Error occured! Error code = "<<e.code()<<".Message: "<<e.what();returne.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())数据类型差异(如 SOCKETvsint)函数名差异(如 closesocket()vsclose()) |
| 11. 异步操作 | 原生支持异步 I/O(如async_connect、async_accept、async_read/write),通过回调或协程处理 | 需手动实现(如使用 select/poll/epoll/kqueue,或线程),开发复杂度高 |
四、核心差异总结
编程范式:
- C 语言:过程式,基于文件描述符(整数)和结构体,手动管理内存和资源,代码冗长且易出错(如强制类型转换、结构体填充、内存释放)。
- Boost.Asio:面向对象,用
socket、acceptor、endpoint等类封装底层操作,隐藏了繁琐的细节(如字节序转换、地址结构体填充)。
错误处理:
- C 语言:通过返回值(如
-1)和errno判断错误,需手动调用perror()或strerror()解析错误信息。 - Boost.Asio:支持错误码(
error_code)和异常两种方式,错误信息更丰富(包含错误码和描述),且类型安全。
- C 语言:通过返回值(如
DNS 解析:
- C 语言:需手动调用
getaddrinfo(),遍历返回的addrinfo链表,处理每个端点的连接,最后释放资源。 - Boost.Asio:用
resolver自动解析域名,返回results_type,asio::connect()自动遍历端点并连接,简化了代码。
- C 语言:需手动调用
异步 I/O:
- C 语言:异步操作需手动使用
select/epoll/kqueue等 I/O 多路复用技术,或线程池,开发难度大。 - Boost.Asio:原生支持异步操作(
async_*系列函数),通过回调函数、协程(C++20)或future处理结果,是 Asio 的核心优势之一。
- C 语言:异步操作需手动使用
跨平台性:
- C 语言:需处理不同系统的差异(如 Windows 的 WSA 初始化、
closesocket()vsclose())。 - Boost.Asio:封装了系统差异,代码无需修改即可在不同平台运行。
- C 语言:需处理不同系统的差异(如 Windows 的 WSA 初始化、
五、注意事项
io_context的作用:Asio 的所有 I/O 操作都依赖io_context,即使是同步操作,也需要创建它(异步操作还需要调用io_context.run()启动事件循环)。- 异常与错误码:代码中同时使用了异常和
error_code,Asio 的所有操作都提供了两种重载(带error_code参数的版本不抛异常,不带的版本抛异常),可根据场景选择。 - 命名空间:代码中使用
using namespace boost;简化代码,但实际项目中建议使用namespace ba = boost::asio;这样的别名,避免命名冲突。 - 资源管理:Asio 的类(如
socket、acceptor)都实现了 RAII(资源获取即初始化),析构时会自动释放资源(如关闭套接字),无需手动调用close()(C 语言需手动关闭文件描述符)。