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_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和端口)
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_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 语言需手动关闭文件描述符)。
1413

被折叠的 条评论
为什么被折叠?



