C++ 网络编程学习七

C++ 网络编程学习七

asio实现http服务器

客户端实现

  1. 发送:io_context上下文
  2. server:服务器地址
  3. path:请求路径
  4. resolver_:tcp resolver,输入的不是一个域名的时候,解析出来。
  5. socket_:一个client就有一个socket_。
  6. handle_resolve:回调函数,异步处理连接。解析成功后,进行连接,连接也是异步函数handle_connect。
  7. handle_connect:处理连接,连接成功后,发送处理好的消息。发送也是异步发送,回调handle_write_request
  8. handle_write_request:等待服务器给我们发送的消息,监听对端发送的数据。当收到对方数据时,先解析响应的头部信息,接受到头部消息后,异步回调handle_read_status_line。
  9. handle_read_status_line:先读出HTTP版本,以及返回的状态码,如果状态码不是200,则返回,是200说明响应成功。接下来把所有的头部信息都读出来。回调handle_read_headers。
  10. handle_read_headers:逐行读出头部信息,然后读出响应的内容,继续监听读事件读取相应的内容,直到接收到EOF信息,也就是对方关闭,继续监听读事件是因为有可能是长连接的方式,当然如果是短链接,则服务器关闭连接后,客户端也是通过异步函数读取EOF进而结束请求。读服务器发送的数据,回调handle_read_content。
  11. handle_read_content:读取消息。
  12. main函数中:调用客户端请求服务器信息, 请求服务器的路由地址。
cpp 复制代码
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;
}

服务器实现

  1. 创建服务器类对象,绑定ip和端口,以及文件路径,run起来。
  2. Sever类中,生成一个acceptor_来接收对端的连接signals_是优雅退出时绑定的一些信号所有的读写,都交给connection_manager_()来处理socket_是服务器给客户端连接分配的socketrequest_handler_处理请求
  3. resolver:解析地址和端口,返回一个端点。端点传给acceptor,acceptor绑定,监听,异步的do_accept();
  4. do_accept:异步监听连接,收到成功的连接会把请求加到connection_manager_中,request_handler_也会传入,进行连接处理。
  5. do_read:处理读取信息过程
  6. handle_request:解析资源请求的逻辑。decode路径,获取资源,检测资源是否存在。
cpp 复制代码
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);
    }
相关推荐
小叶学C++3 分钟前
【C++】类与对象(下)
java·开发语言·c++
NuyoahC21 分钟前
算法笔记(十一)——优先级队列(堆)
c++·笔记·算法·优先级队列
FL16238631291 小时前
[C++]使用纯opencv部署yolov11-pose姿态估计onnx模型
c++·opencv·yolo
sukalot1 小时前
windows C++-使用任务和 XML HTTP 请求进行连接(一)
c++·windows
ぃ扶摇ぅ2 小时前
Windows系统编程(三)进程与线程二
c++·windows
Mr.Z.4112 小时前
【历年CSP-S复赛第一题】暴力解法与正解合集(2019-2022)
c++
Death2002 小时前
使用Qt进行TCP和UDP网络编程
网络·c++·qt·tcp/ip
郭二哈3 小时前
C++——list
开发语言·c++·list
黑不溜秋的3 小时前
C++ 语言特性29 - 协程介绍
开发语言·c++
一丝晨光3 小时前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby