前言
最近学习Boost.aiso库,把官网的HTTP 服务端的例子给实现了一下。
官网链接:https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/examples/cpp11_examples.html
笔者建了一个VS工程,并把相关的头文件和依赖库给整合了进去,下载下来之后可以直接在Release X64下编译执行。
下载链接: Boost.Aiso实现Http服务端,VS2015工程可直接编译运行
执行结果如图所示:
PS: 上面的文件下载会需要C币,如果你没有C币的话可以留言,我发你邮箱
讲解
下面将从整个程序一步一步解析HTTP服务端是如何接受并处理请求的。
1、main函数
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;
}
2、server构造函数
server构造函数中主要就是构造:
boost::asio::io_service
对象,用于执行异步操作boost::asio::signal_set
对象,用于接收外部信号,比如ctrl+c
、kill
,收到这些信号就会清理资源并退出程序boost::asio::ip::tcp::acceptor
对象,用于监听连接connection_manager
对象,用于管理连接boost::asio::ip::tcp::socket
对象,是一个流,接收到的socket报文都会放进这个对象里request_handler
,用于处理请求
此外,在构造函数体内,还解析了监听地址和端口,并开始监听。最后,调用do_accept();
来处理接收到的请求
server::server(const std::string& address, const std::string& port,
const std::string& doc_root)
: io_service_(),
signals_(io_service_),
acceptor_(io_service_),
connection_manager_(),
socket_(io_service_),
request_handler_(doc_root)
{
// Register to handle the signals that indicate when the server should exit.
// It is safe to register for the same signal multiple times in a program,
// provided all registration for the specified signal is made through Asio.
signals_.add(SIGINT);
signals_.add(SIGTERM);
#if defined(SIGQUIT)
signals_.add(SIGQUIT);
#endif // defined(SIGQUIT)
do_await_stop();
// Open the acceptor with the option to reuse the address (i.e. SO_REUSEADDR).
boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve({ address, port });
acceptor_.open(endpoint.protocol());
acceptor_.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
acceptor_.bind(endpoint);
acceptor_.listen();
do_accept();
}
3、server::do_accept
这步,是将收到的socket流通过异步的方式传给connection_manager_.start
。异步就意味着,这一步是不阻塞的,会立马返回。当有新的socket报文来的时候,才会调用后面的操作。
void server::do_accept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
// Check whether the server was stopped by a signal before this
// completion handler had a chance to run.
if (!acceptor_.is_open())
{
return;
}
if (!ec)
{
connection_manager_.start(std::make_shared<connection>(
std::move(socket_), connection_manager_, request_handler_));
}
do_accept();
});
}
4、connection_manager::start
上一部中构造了一个connection
对象,并将只能指针传进来。这一步中将只能connection
对象的智能指针放进set里,用于连接管理。然后调用connection::start
进行do_read
void connection_manager::start(connection_ptr c)
{
connections_.insert(c);
c->start();
}
void connection::start()
{
do_read();
}
5、connection::do_read
将socket中的字节流读入buffer中,然后调用request_parser_.parse
一个字节一个字节地解析buffer中的数据,parse的过程比较麻烦但是比较容易,就是普通的字符处理,最终构造出buffer中的数据解析成一个request
对象。
然后,调用request_handler_.handle_request
处理request
,并返回reply_
,其过程就是读取request中要请求的文件,然后读取文件并将文件内容存在reply中。
最后,调用do_write()
。
6、connection::do_write
这一步就是将上一步得到的reply_
写进socket流中。最终socket流会返回给客户端。
至此,这个简单的http服务端接收请求->解析请求->处理请求->构造响应->发送响应的流程就走完了。但是这个Demo中的很多思想还是很值以后继续慢慢学习的。