首页 > 代码库 > Boost.Asio c++ 网络编程翻译(25)

Boost.Asio c++ 网络编程翻译(25)

代理实现
代理一般位于客户端和服务端之间。它接受客户端的请求,可能对请求进行修改,然后接着把请求发送到服务端。然后从服务端取回结果,也可能对结果进行修改,然后接着把结果发送到客户端。
page119image9728
代理有什么特别的,我们讲述它的目的在于:对每个连接,你都需要两个sokect,一个给客户端,另外一个给服务端。这些都给实现一个代理增加了不小的难度。
实现一个同步的代理应用比异步的方式更加复杂;数据可能同时从两个端过来(客户端和服务端),也可能同时发往两个端。这也就意味着如果我们选择同步,我们就可能在一端向另一端read()或者write(),同时另一端向这一端read()或者write()时阻塞,这也就意味着最终我们会变的无响应。
把下面几条当作一个异步代理的简单例子:
  • 在我们的情形下,我们在构造函数中能拿到两个连接。但是不是所有的都这样,比如对于一个web代理来说,客户端只告诉我们服务端的地址。
  • 因为比较简单,所以不是线程安全的。参考如下的代码:
class proxy  : public boost::enable_shared_from_this<proxy> {
    proxy(ip::tcp::endpoint ep_client, ip::tcp::endpoint ep_
server) : ... {}
public:
    static ptr start(ip::tcp::endpoint ep_client,
ip::tcp::endpoint ep_svr) {
        ptr new_(new proxy(ep_client, ep_svr));
        // … 连接到两个端
        return new_;
    }
    void stop() {
        // ... 关闭两个连接
    }
    bool started() { return started_ == 2; }
private:
    void on_connect(const error_code & err) {
        if ( !err)      {
            if ( ++started_ == 2) on_start();
        } else stop();
    }
    void on_start() {
        do_read(client_, buff_client_);
        do_read(server_, buff_server_);
    }

... private:

ip::tcp::socket client_, server_;

    enum { max_msg = 1024 };
    char buff_client_[max_msg], buff_server_[max_msg]; 

    int started_; 

};

这是个非常简单的代理。当我们两个端都连接时,它开始从两个端读取(on_start()方法):
   class proxy  : public boost::enable_shared_from_this<proxy> {
       ...
       void on_read(ip::tcp::socket & sock, const error_code& err, size_t
   bytes) {
           char * buff = &sock == &client_ ? buff_client_ : buff_server_;
           do_write(&sock == &client_ ? server_ : client_, buff, bytes);
       }
       void on_write(ip::tcp::socket & sock, const error_code &err,
   size_t bytes){
           if ( &sock == &client_) do_read(server_, buff_server_);
           else                    do_read(client_, buff_client_);
       }
       void do_read(ip::tcp::socket & sock, char* buff) {
           async_read(sock, buffer(buff, max_msg),
                      MEM_FN3(read_complete,ref(sock),_1,_2),
                      MEM_FN3(on_read,ref(sock),_1,_2));
       }
       void do_write(ip::tcp::socket & sock, char * buff, size_t size) {
           sock.async_write_some(buffer(buff,size),
                                 MEM_FN3(on_write,ref(sock),_1,_2));
       }
       size_t read_complete(ip::tcp::socket & sock,
                            const error_code & err, size_t bytes) {
           if ( sock.available() > 0) return sock.available();
           return bytes > 0 ? 0 : 1;
       }

}; 

对每一个成功的读取操作(on_read),它把消息发到另外一个部分。只要消息一发送成功(on_write),我们就从来源那部分再次读取。
使用下面的代码片段让这个流程运转起来:
int main(int argc, char* argv[]) {
       ip::tcp::endpoint ep_c( ip::address::from_string("127.0.0.1"),
   8001);
       ip::tcp::endpoint ep_s( ip::address::from_string("127.0.0.1"),
   8002);
       proxy::start(ep_c, ep_s);
       service.run();

你会注意到我在读和写中我重用了buffer。这个重用是ok的,因为从客户端读取的消息是在一个新消息被读取之前写到服务端的,反之亦然。这也意味着这种实现方式会碰到无响应的问题。当我们正在处理到B部分的写入时,我们不会从A读取(我们会在写入到B部分完成时重新从A部分读取)。你可以通过下面的方式重写实现来克服这个问题:
你需要使用多个读取buffer
对每个成功的read操作,除了异步写回到另外一个部分,还需要做额外的一个read(写入到一个新的buffer)
对每个成功的write操作,销毁(或者重用)这个buffer
我会把这个当作联系留给你们。

小结
当在选择同步或者异步时需要考虑很多事情。最先需要考虑的就是避免混淆它们。
在这一章中,我们已经学到:
  • 实现,测试,调试每个类型的应用是多么简单
  • 线程是如何影响你的应用的
  • 应用的行为是怎么影响它的实现的(拉取或者推送类型)
  • 当你选择异步时你怎样去嵌入你自己的异步操作
接下来,我们会了解一些Boost.Asio不那么为人知晓的特性,而且我最喜欢的Boost.Asio的特性-协程,它可以使你享受异步带来的好处而几乎不会碰到它任何的坏处。

Boost.Asio c++ 网络编程翻译(25)