《http篇》boost.asio实现http服务
模块详解及TCP实例
下载和编译参考之前文章
参考链接:https://zhuanlan.zhihu.com/p/634878993
层次关系
首先,boost这个库有很多模块,asio是其中的一个网络模块,所有的模块都是在boost的命名空间下
using namespace boost;
然后我们这里是使用asio这个网络模块里面的各个类,所以就是:
using namespace boost::asio;
而asio空间中,我们首先不可避免的就是类io_service或io_context。
注意,io_context这个类是用来替代io_service的,所以建议以后都直接使用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地址转化为网络字节序。
最后总体总结一下常用的类所在位置:
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));
而这里的endpoint第一个参数为tcp::v4(),这个函数返回一个tcp协议类型,由于没有找到说明,我理解的就是监听ipv4类型本机所有地址,其第二个参数就是要监听的端口号。
然后就是调用accept函数,用于接受客户端连接,其参数就是一个申请好的socket,用于保存连接上来的客户端信息。
cout << "client:" << sock.remote_endpoint().address() << endl;
同时这里我还通过调用remote_endpoint函数,可以获取客户端连接上来的终端,再调用address函数,就可以得到地址信息,并打印出来。
然后通过一个try和catch结构来捕获错误信息,比如客户端中断连接之类的。
在while循环里面,我们就可以通过receive函数与send函数,进行同步接受与发送消息。
注意缓存区都需要通过buffer函数来构造函数可接受的缓存区结构体,否则会报错误。
客户端
#include<iostream>
#include"boost/asio.hpp"
using namespace std;
using namespace boost::asio;
int main() {
io_context io;
ip::tcp::socket sock(io);
sock.connect(ip::tcp::endpoint(ip::address::from_string("127.0.0.1"),6688));
char buf[0xFF];
while (true) {
cin >> buf;
sock.send(buffer(buf));
memset(buf, 0, 0xFF);
sock.receive(buffer(buf));
cout << buf<<endl;
}
sock.close();
::system("pause");
}
客户端就更加简单了,直接构造一个socket ,然后调用connect函数连接即可,其唯一的参数就是一个终端类endpoint。
这里使用address类的静态函数from_string将十进制的地址转化,得到第一个参数,第二个参数为要连接的端口。
紧接着就是进入while循环,先发送,后接收信息即可。
实现HTTP服务端
下载和编译参考之前文章
参考链接:https://blog.csdn.net/luchengtao11/article/details/97814106
代码
main函数很简单,就是启动了一个server实例、并运行。
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
//if (argc != 4)
//{
// std::cerr << "Usage: http_server <address> <port> <doc_root>\n";
// std::cerr << " For IPv4, try:\n";
// std::cerr << " receiver 0.0.0.0 80 .\n";
// std::cerr << " For IPv6, try:\n";
// std::cerr << " receiver 0::0 80 .\n";
// return 1;
//}
//官网的例子是通过命令行传参的,这里为方便,直接在代码中写死
// Initialise the server.
std::string ip_address = "0.0.0.0";
std::string port = "8080";
std::string doc_root = "../../../docs";
http::server::server s(ip_address, port, doc_root);
// Run the server until stopped.
s.run();
}
catch (std::exception& e)
{
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}
下面还没看