gpt4 book ai didi

c++ - C++中的HTTP代理示例

转载 作者:行者123 更新时间:2023-12-02 10:12:00 26 4
gpt4 key购买 nike

因此,我一直在尝试使用boost.asio在C++中编写代理。我的最初项目包括将字符串消息写入套接字的客户端,接收此消息并将字符串消息写入套接字的服务器以及与上述两个套接字一起工作的代理。
代理代码如下所示(将来的目的是处理多个连接并以某种方式使用传输的数据,并且回调将执行除日志记录之外的一些实际工作):

#include "commondata.h"
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>

using namespace boost::asio;
using ip::tcp;
using std::cout;
using std::endl;


class con_handler : public boost::enable_shared_from_this<con_handler> {
private:
tcp::socket client_socket;
tcp::socket server_socket;
enum { max_length = 1024 };
char client_data[max_length];
char server_data[max_length];

public:
typedef boost::shared_ptr<con_handler> pointer;
con_handler(boost::asio::io_service& io_service):
server_socket(io_service),
client_socket(io_service) {
memset(client_data, 0, max_length);
memset(server_data, 0, max_length);
server_socket.connect( tcp::endpoint( boost::asio::ip::address::from_string(SERVERIP), SERVERPORT ));
}
// creating the pointer
static pointer create(boost::asio::io_service& io_service) {
return pointer(new con_handler(io_service));
}
//socket creation
tcp::socket& socket() {
return client_socket;
}

void start() {
//read the data into the input buffer
client_socket.async_read_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
server_socket.async_write_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_write,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
server_socket.async_read_some(
boost::asio::buffer(server_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
server_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
client_socket.async_write_some(
boost::asio::buffer(server_data, max_length),
boost::bind(&con_handler::handle_write,
shared_from_this(),
server_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}

void handle_read(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
cout << "proxy handle_read" << endl;
cout << data << endl;
} else {
std::cerr << "error: " << err.message() << std::endl;
client_socket.close();
}
}

void handle_write(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
cout << "proxy handle_write" << endl;
cout << data << endl;
} else {
std::cerr << "error: " << err.message() << endl;
client_socket.close();
}
}
};


class Server {
private:
boost::asio::io_service io_service;
tcp::acceptor acceptor_;
void start_accept() {
// socket
con_handler::pointer connection = con_handler::create(io_service);

// asynchronous accept operation and wait for a new connection.
acceptor_.async_accept(connection->socket(),
boost::bind(&Server::handle_accept, this, connection,
boost::asio::placeholders::error));
}

public:
//constructor for accepting connection from client
Server()
: acceptor_(io_service, tcp::endpoint(tcp::v4(), PROXYPORT)) {
start_accept();
}

void handle_accept(const con_handler::pointer& connection, const boost::system::error_code& err) {
if (!err) {
connection->start();
}
start_accept();
}
boost::asio::io_service& get_io_service() {
return io_service;
}
};


int main(int argc, char *argv[]) {
try {
Server server;
server.get_io_service().run();
} catch(std::exception& e) {
std::cerr << e.what() << endl;
}
return 0;
}

如果发送的消息是字符串(最初用于测试我的代码是否可以正常工作),那么所有的回调都以我希望它们被调用的方式调用,并且事情似乎正在起作用。
这是该情况下代理的标准输出:
user@laptop:$ ./proxy 
proxy handle_read
message from the client
proxy handle_write
message from the client
proxy handle_read
message from server
proxy handle_write
message from server
因此,客户端发送“来自客户端的消息”字符串,该字符串由代理接收并保存,相同的字符串发送至服务器,然后服务器发回“来自服务器的消息”字符串,该字符串也被接收并保存然后由代理发送给客户端。
当我尝试使用实际的Web服务器(Apache)和JMeter之类的应用程序互相通信时,会出现问题。这是这种情况的标准输出:
user@laptop:$ ./proxy 
proxy handle_write

proxy handle_write

proxy handle_read
GET / HTTP/1.1
Connection: keep-alive
Host: 127.0.0.1:1337
User-Agent: Apache-HttpClient/4.5.5 (Java/11.0.8)


error: End of file
然后,JMeter测试失败并出现超时(即代理收到EOF错误),并且似乎没有数据发送到apache网络服务器。我现在要问的问题是,为什么与发送字符串消息时的情况相比,以另一种顺序调用回调函数,为什么不将数据传输到服务器套接字。在此先感谢您的帮助!

最佳答案

缩写为start():

    client_socket.async_read_some  (buffer(client_data), ...);
server_socket.async_write_some (buffer(client_data), ...);
server_socket.async_read_some (buffer(server_data), ...);
client_socket.async_write_some (buffer(server_data), ...);
//read the data into the input
client_socket.async_read_some (buffer(client_data), ...);
server_socket.async_write_some (buffer(client_data), ...);
server_socket.async_read_some (buffer(server_data), ...);
client_socket.async_write_some (buffer(server_data), ...);
那不是异步操作的工作原理。它们异步运行,这意味着它们都将立即返回。
您可以同时从某些缓冲区读取和写入数据,而不必等待有效的数据。另外,无论接收了多少缓冲区,您总是在写完整的缓冲区。
所有这些拼写为 Undefined Behaviour
从简单开始
从概念上讲,您 只是要阅读:
void start() {
//read the data into the input buffer
client_socket.async_read_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
现在,一旦收到数据,您可能想要中继:
void handle_read(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
std::cout << "proxy handle_read" << std::endl;
server_socket.async_write_some(
boost::asio::buffer(client_data, bytes_transferred),
boost::bind(&con_handler::handle_write,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
std::cerr << "error: " << err.message() << std::endl;
client_socket.close();
}
}
请注意,仅在出现错误时关闭连接的一侧似乎有些武断。您可能至少要对两者的任何异步操作 cancel()(可选地为 shutdown()),然后让 shared_ptr破坏您的 con_handler
全双工
现在,对于全双工操作, 可以确实同时启动反向中继。在单独的方法中维护调用链有点不方便(毕竟,您不仅要切换缓冲区,还要切换套接字对)。
认识到您在做两次相同的事情可能会很有启发性:
client -> [...buffer...] -> server

server -> [...buffer...] -> client
您可以将每一侧封装在一个类中,并避免重复所有代码:
struct relay {
tcp::socket &from, &to;
std::array<char, max_length> buf{};

void run_relay(pointer self) {
from.async_read_some(asio::buffer(buf),
[this, self](error_code ec, size_t n) {
if (ec) return handle(from, ec);

/*
*std::cout
* << "From " << from.remote_endpoint()
* << ": " << std::quoted(std::string_view(buf.data(), n))
* << std::endl;
*/
async_write(to, asio::buffer(buf, n), [this, self](error_code ec, size_t) {
if (ec) return handle(to, ec);
run_relay(self);
});
});
}

void handle(tcp::socket& which, error_code ec = {}) {
if (ec == asio::error::eof) {
// soft "error" - allow write to complete
std::cout << "EOF on " << which.remote_endpoint() << std::endl;
which.shutdown(tcp::socket::shutdown_receive, ec);
}

if (ec) {
from.cancel();
to.cancel();

std::string reason = ec.message();
auto fep = from.remote_endpoint(ec),
tep = to.remote_endpoint(ec);
std::cout << "Stopped relay " << fep << " -> " << tep << " due to " << reason << std::endl;
}
}
} c_to_s {client_socket, server_socket, {0}},
s_to_c {server_socket, client_socket, {0}};
笔记
  • 我们通过使用lambdas
  • 避开了 bind困惑
  • 我们在错误
  • 上取消中继的两端
  • 我们使用std::array缓冲区-更安全,更易于使用
  • ,无论缓冲区
  • 的大小如何,我们只写所接收的字节数
  • 为了避免破坏buf
  • 中的数据,我们不会安排其他读取,直到写入完成

    让我们实现 con_handler重新开始
    从上面使用 relay:
    void start() {
    c_to_s.run_relay(shared_from_this());
    s_to_c.run_relay(shared_from_this());
    }
    就这样。我们传递自己,以便 con_handler保持 Activity 状态,直到完成所有操作。
    DEMO Live On Coliru
    #define PROXYPORT 8899
    #define SERVERIP "173.203.57.63" // coliru IP at the time
    #define SERVERPORT 80
    #include <boost/enable_shared_from_this.hpp>
    #include <boost/asio.hpp>
    #include <iostream>
    #include <iomanip>

    namespace asio = boost::asio;
    using boost::asio::ip::tcp;
    using boost::system::error_code;
    using namespace std::chrono_literals;

    class con_handler : public boost::enable_shared_from_this<con_handler> {
    public:
    con_handler(asio::io_service& io_service):
    server_socket(io_service),
    client_socket(io_service)
    {
    server_socket.connect({ asio::ip::address::from_string(SERVERIP), SERVERPORT });
    }
    // creating the pointer
    using pointer = boost::shared_ptr<con_handler>;
    static pointer create(asio::io_service& io_service) {
    return pointer(new con_handler(io_service));
    }

    //socket creation
    tcp::socket& socket() {
    return client_socket;
    }

    void start() {
    c_to_s.run_relay(shared_from_this());
    s_to_c.run_relay(shared_from_this());
    }

    private:
    tcp::socket server_socket;
    tcp::socket client_socket;
    enum { max_length = 1024 };

    struct relay {
    tcp::socket &from, &to;
    std::array<char, max_length> buf{};

    void run_relay(pointer self) {
    from.async_read_some(asio::buffer(buf),
    [this, self](error_code ec, size_t n) {
    if (ec) return handle(from, ec);

    /*
    *std::cout
    * << "From " << from.remote_endpoint()
    * << ": " << std::quoted(std::string_view(buf.data(), n))
    * << std::endl;
    */
    async_write(to, asio::buffer(buf, n), [this, self](error_code ec, size_t) {
    if (ec) return handle(to, ec);
    run_relay(self);
    });
    });
    }

    void handle(tcp::socket& which, error_code ec = {}) {
    if (ec == asio::error::eof) {
    // soft "error" - allow write to complete
    std::cout << "EOF on " << which.remote_endpoint() << std::endl;
    which.shutdown(tcp::socket::shutdown_receive, ec);
    }

    if (ec) {
    from.cancel();
    to.cancel();

    std::string reason = ec.message();
    auto fep = from.remote_endpoint(ec),
    tep = to.remote_endpoint(ec);
    std::cout << "Stopped relay " << fep << " -> " << tep << " due to " << reason << std::endl;
    }
    }
    } c_to_s {client_socket, server_socket, {0}},
    s_to_c {server_socket, client_socket, {0}};
    };

    class Server {
    asio::io_service io_service;
    tcp::acceptor acceptor_;

    void start_accept() {
    // socket
    auto connection = con_handler::create(io_service);

    // asynchronous accept operation and wait for a new connection.
    acceptor_.async_accept(
    connection->socket(),
    [connection, this](error_code ec) {
    if (!ec) connection->start();
    start_accept();
    });
    }

    public:
    Server() : acceptor_(io_service, {{}, PROXYPORT}) {
    start_accept();
    }

    void run() {
    io_service.run_for(5s); // .run();
    }
    };

    int main() {
    Server().run();
    }
    当与
    printf "GET / HTTP/1.1\r\nHost: coliru.stacked-crooked.com\r\n\r\n" | nc 127.0.0.1 8899
    服务器打印:
    EOF on 127.0.0.1:36452
    然后netcat收到回复:
    HTTP/1.1 200 OK 
    Content-Type: text/html;charset=utf-8
    Content-Length: 8616
    Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2g
    Date: Sat, 01 Aug 2020 00:25:10 GMT
    Connection: Keep-Alive

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
    <html>
    ....
    </html>
    概括
    清楚地思考您要实现的目标,可以避免意外的复杂性。它使我们能够提出一个好的构建基块( relay),从而消除了复杂性。

    关于c++ - C++中的HTTP代理示例,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63193076/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com