C++ 网络编程学习七
- asio实现http服务器
asio实现http服务器
客户端实现:
- 发送:io_context上下文
- server:服务器地址
- path:请求路径
- resolver_:tcp resolver,输入的不是一个域名的时候,解析出来。
- socket_:一个client就有一个socket_。
- handle_resolve:回调函数,异步处理连接。解析成功后,进行连接,连接也是异步函数handle_connect。
- handle_connect:处理连接,连接成功后,发送处理好的消息。发送也是异步发送,回调handle_write_request
- handle_write_request:等待服务器给我们发送的消息,监听对端发送的数据。当收到对方数据时,先解析响应的头部信息,接受到头部消息后,异步回调handle_read_status_line。
- handle_read_status_line:先读出HTTP版本,以及返回的状态码,如果状态码不是200,则返回,是200说明响应成功。接下来把所有的头部信息都读出来。回调handle_read_headers。
- handle_read_headers:
逐行读出头部信息,然后读出响应的内容,继续监听读事件读取相应的内容,直到接收到EOF信息
,也就是对方关闭,继续监听读事件是因为有可能是长连接的方式,当然如果是短链接,则服务器关闭连接后,客户端也是通过异步函数读取EOF进而结束请求。读服务器发送的数据,回调handle_read_content。 - handle_read_content:读取消息。
- main函数中:调用客户端请求服务器信息, 请求服务器的路由地址。
client(boost::asio::io_context& io_context,const std::string& server, const std::string& path): resolver_(io_context),socket_(io_context){std::ostream request_stream(&request_);request_stream << "GET " << path << " HTTP/1.0\r\n"; // 中间是有空格的request_stream << "Host: " << server << "\r\n";request_stream << "Accept: */*\r\n";request_stream << "Connection: close\r\n\r\n";size_t pos = server.find(":");std::string ip = server.substr(0, pos);std::string port = server.substr(pos + 1);// 异步解析ip和port,resolver_.async_resolve(ip, port,boost::bind(&client::handle_resolve, this,boost::asio::placeholders::error,boost::asio::placeholders::results));}void handle_resolve(const boost::system::error_code& err,const tcp::resolver::results_type& endpoints){if (!err){// 解析成功后,进行连接,连接也是boost::asio::async_connect(socket_, endpoints,boost::bind(&client::handle_connect, this,boost::asio::placeholders::error));}else{std::cout << "Error: " << err.message() << "\n";}}void handle_connect(const boost::system::error_code& err)
{if (!err){// 连接成功后,发送消息到服务器端口。boost::asio::async_write(socket_, request_,boost::bind(&client::handle_write_request, this,boost::asio::placeholders::error));}else{std::cout << "Error: " << err.message() << "\n";}
}void handle_write_request(const boost::system::error_code& err){if (!err){// boost::asio::async_read_until(socket_, response_, "\r\n",boost::bind(&client::handle_read_status_line, this,boost::asio::placeholders::error));}else{std::cout << "Error: " << err.message() << "\n";}}void handle_read_status_line(const boost::system::error_code& err){if (!err){// Check that response is OK.std::istream response_stream(&response_);std::string http_version;response_stream >> http_version;unsigned int status_code;response_stream >> status_code;std::string status_message;std::getline(response_stream, status_message);if (!response_stream || http_version.substr(0, 5) != "HTTP/"){std::cout << "Invalid response\n";return;}if (status_code != 200){std::cout << "Response returned with status code ";std::cout << status_code << "\n";return;}// Read the response headers, which are terminated by a blank line.boost::asio::async_read_until(socket_, response_, "\r\n\r\n",boost::bind(&client::handle_read_headers, this,boost::asio::placeholders::error));}else{std::cout << "Error: " << err << "\n";}}void handle_read_headers(const boost::system::error_code& err){if (!err){// Process the response headers.std::istream response_stream(&response_);std::string header;while (std::getline(response_stream, header) && header != "\r")std::cout << header << "\n";std::cout << "\n";// Write whatever content we already have to output.if (response_.size() > 0)std::cout << &response_;// Start reading remaining data until EOF.boost::asio::async_read(socket_, response_,boost::asio::transfer_at_least(1),boost::bind(&client::handle_read_content, this,boost::asio::placeholders::error));}else{std::cout << "Error: " << err << "\n";}}void handle_read_content(const boost::system::error_code& err){if (!err){// Write all of the data that has been read so far.std::cout << &response_;// Continue reading remaining data until EOF.boost::asio::async_read(socket_, response_,boost::asio::transfer_at_least(1),boost::bind(&client::handle_read_content, this,boost::asio::placeholders::error));}else if (err != boost::asio::error::eof){std::cout << "Error: " << err << "\n";}}int main(int argc, char* argv[])
{try{boost::asio::io_context io_context;client c(io_context, "127.0.0.1:8080", "/");io_context.run();getchar();}catch (std::exception& e){std::cout << "Exception: " << e.what() << "\n";}return 0;
}
服务器实现:
- 创建服务器类对象,绑定ip和端口,以及文件路径,run起来。
- Sever类中,
生成一个acceptor_来接收对端的连接
。signals_是优雅退出时绑定的一些信号
。所有的读写,都交给connection_manager_()来处理
。socket_是服务器给客户端连接分配的socket
。request_handler_处理请求
。 - resolver:解析地址和端口,返回一个端点。端点传给acceptor,acceptor绑定,监听,异步的do_accept();
- do_accept:异步监听连接,收到成功的连接会把请求加到connection_manager_中,request_handler_也会传入,进行连接处理。
- do_read:处理读取信息过程
- handle_request:解析资源请求的逻辑。decode路径,获取资源,检测资源是否存在。
int main(int argc, char* argv[])
{try{std::filesystem::path path = std::filesystem::current_path() / "res";// 使用 std::cout 输出拼接后的路径std::cout << "Path: " << path.string() << '\n';std::cout << "Usage: http_server <127.0.0.1> <8080> "<< path.string() <<"\n";// Initialise the server.http::server::server s("127.0.0.1", "8080", path.string());// Run the server until stopped.s.run();}catch (std::exception& e){std::cerr << "exception: " << e.what() << "\n";}return 0;
}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){signals_.add(SIGINT);//绑定几个信号signals_.add(SIGTERM);
#if defined(SIGQUIT)signals_.add(SIGQUIT);
#endif do_await_stop(); //异步等待的方式,监听这些信号,收到信号后,连接移除,优雅关闭服务器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();
}// 异步接收。
void server::do_accept()
{acceptor_.async_accept(socket_,[this](boost::system::error_code ec){if (!acceptor_.is_open()){return;}if (!ec){// connection_manager_是连接管理类,用智能指针的方式,创建新的连接,// socket_ 给move到connection的构造函数里。// connection_manager_.start(std::make_shared<connection>(std::move(socket_), connection_manager_, request_handler_));}do_accept();});}void connection::do_read()
{auto self(shared_from_this());// 构造了一个buffer去读,bytes_transferred的意思是读到了多少数据。socket_.async_read_some(boost::asio::buffer(buffer_),[this, self](boost::system::error_code ec, std::size_t bytes_transferred){if (!ec){request_parser::result_type result;// 把返回值绑定成了一个元组。std::tie(result, std::ignore) = request_parser_.parse(request_, buffer_.data(), buffer_.data() + bytes_transferred);// 如果返回值是good,就继续处理请求if (result == request_parser::good){request_handler_.handle_request(request_, reply_);do_write();}// 如果是bad,请求直接返回掉,返回值写到回调里。else if (result == request_parser::bad){reply_ = reply::stock_reply(reply::bad_request);do_write();}else{ //还没解析完do_read();}}else if (ec != boost::asio::error::operation_aborted){connection_manager_.stop(shared_from_this());}});
}void request_handler::handle_request(const request& req, reply& rep)
{// 解码,如果是get请求,后面可能会带一些参数// decode路径std::string request_path;if (!url_decode(req.uri, request_path)){rep = reply::stock_reply(reply::bad_request);return;}// 如果是空,返回一个bad_requestif (request_path.empty() || request_path[0] != '/'|| request_path.find("..") != std::string::npos){rep = reply::stock_reply(reply::bad_request);return;}// 只发送一个斜杠,返回index.htmlif (request_path[request_path.size() - 1] == '/'){request_path += "index.html";}// 区分目录还是文件std::size_t last_slash_pos = request_path.find_last_of("/");std::size_t last_dot_pos = request_path.find_last_of(".");std::string extension;if (last_dot_pos != std::string::npos && last_dot_pos > last_slash_pos){extension = request_path.substr(last_dot_pos + 1);}// 打开文件std::string full_path = doc_root_ + request_path;std::ifstream is(full_path.c_str(), std::ios::in | std::ios::binary);if (!is){rep = reply::stock_reply(reply::not_found);return;}// Fill out the reply to be sent to the client.rep.status = reply::ok;char buf[512];// 先把头部添上,再返回给对端while (is.read(buf, sizeof(buf)).gcount() > 0)rep.content.append(buf, is.gcount());rep.headers.resize(2);rep.headers[0].name = "Content-Length";rep.headers[0].value = std::to_string(rep.content.size());rep.headers[1].name = "Content-Type";rep.headers[1].value = mime_types::extension_to_type(extension);}