前言
由于C/C++一直以来没有推出标准的网络库,导致每次我们在网络编程中,就不得直接调用系统API来进行编程,但很多步骤都是重复不变的的,却又不得不写,非常繁琐。
但其实作为C/C++准标准库的boost库,早就有了一个网络库,名为asio,也许在下一个C++标准,就加入了标准库也说不一定。
如果你没有安装boost库,那么可以直接去官方下载:Boost,官方已经推出了编译好的版本、下载相应的window安装程序即可:

除此之外,我还写了一篇基于asio库实现的聊天室:手写一个聊天室(单聊、群聊、文件传输)。
该项目的服务器因为使用的asio库,所以完全跨平台,可以直接在windows、linux平台进行编译。
注意,本文基于1.88版本,版本不同可能存在差异。
理清层次
既然是网络编程,那么就必然少不了TCP与UDP的身影,虽然这个网络模块还可以控制的更加深入,也支持其它协议,但这里就不更多讲解了。
不过这个库也有很不友好的地方,那就是层次太多了!所以我们首先来理一下各个类的层次关系。
比如首先,boost这个库有很多模块,asio就是其中的一个网络模块,这些所有模块都是在boost这个命名空间下:
using namespace boost;
然后我们这里是使用asio这个网络模块里面的各个类,所以就是:
using namespace boost::asio;
而asio空间中,我们首先不可避免的就是io_context。
这个类非常重要,它相当于我们程序与系统之间I/O操作的中介,我们所有的接受或发送数据操作,都是通过将需求提交给这个类,然后这个类再交给计算机来执行的。
基于这个理念,基本所有asio网络库中有读写I/O需求的类,其构造函数的第一个参数就是它,比如后面要讲的收发数据的socket类,以及tcp服务器用于接受用户连接的acceptor类等
而这个io_context就在asio里面,所以在using namespace boost::asio;之后,就可以直接用它实例化对象:
io_context io;
除了io_context 外,asio里面还有一个函数非常重要,那就是buffer函数,它的作用其实就是构造一个结构体,大致如下:
struct{
void* buf;
s_size len;
}
该网络模块中所有的收发数据操作,都不接受单独的字符串,而是这样一个结构体,分别为缓存区的首地址以及缓存区的大小。
总结一下就是,asio里面,直接用到的就是一个类:io_context 与一个函数:buffer。
然后继续深入,紧接着就是asio里面进一步的命名空间ip,我们的TCP和UDP相关类,就在这个ip里面。
比如我们想使用tcp,其socket类,就是:ip::tcp::socket,而udp的socket类就是:ip::udp::socket。
由于我们通常程序用中可能只使用其中某一个协议,比如只使用TCP,那就可以这样写:
using asio::ip::tcp;
作为TCP服务器,用于接受客户端连接的类acceptor也在其中。
这样就不用每次都加前面那一大长串了(如果tcp与udp都会使用,那就别这样写了,会混淆)。
除了socket类,我们在网络通信中还需要对方的ip与端口才行,这就用到了类endpoint,它同样在tcp与udp中都有。
还有就是地址处理类:address,直接就在ip里面,其最常用的就是它的静态函数from_string,将十进制的ip地址转化为网络字节序。
最后总体总结一下常用的类所在位置:
| 空间 | 类或函数 |
|---|---|
| boost::asio | io_context,buffer |
| boost::asio::ip | address |
| boost::asio::ip::tcp | socket,acceptor,endpoint |
| boost::asio::ip::udp | socket,endpoint |
TCP编程实例
这个网络库不仅支持同步,而且还支持异步,所以下面我们分别进行讲解。
同步实例
服务器:
#include<iostream>
#include"boost/asio.hpp"
using namespace std;
using namespace boost;
using asio::ip::tcp;
int main() {
cout << "server start ……" << endl;
asio::io_context io;
tcp::acceptor acptr(io, tcp::endpoint(tcp::v4(), 6688));
tcp::socket sock(io);
acptr.accept(sock);
cout << "client:" << sock.remote_endpoint().address() << endl;
try {
while (true) {
char buf[0xFF];
sock.receive(asio::buffer(buf));
sock.send(asio::buffer(buf));
}
}
catch (std::exception& e) {
cout << e.what();
}
sock.close();
::system("pause");
}
这里写的是只接受一个客户端连接的回声服务器,可以看到,这里用到的acceptor 与socket ,第一个参数都为这个io_context
注意一下逻辑的转化,比如以前我们使用纯系统网络api时,是必须要先有一个监听socket,但使用了asio就有点不一样了,acceptor 类就封装了一个监听socket,通过其构造参数的第二个,endpoint,来确定监听地址与端口。
tcp::acceptor acptr(io, tcp::endpoint(tcp::v4(), 6688));
请开通后继续阅读后续 70% 并运行完整示例~
DISCUSSION
评论 0
还没有评论,来留下第一条吧。