C++ 网络编程学习七
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_request
if (request_path.empty() || request_path[0] != '/'
|| request_path.find("..") != std::string::npos)
{
rep = reply::stock_reply(reply::bad_request);
return;
}
// 只发送一个斜杠,返回index.html
if (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);
}