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

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

协程
Boost.Asio的作者在2009-2010年间实现了非常酷的一个部分,协程,它能让你更简单地设计你的异步应用。
它们可以让你同时享受同步和异步两个世界中最好的部分,这就是:异步编程但是很简单就能遵循流程控制,就好像应用是按流程实现的。
page132image3216
正常的流程已经在情形1种展示了,如果使用协程,你会尽可能的接近情形2。
简单来说,就是协程允许在一个方法中的指定位置存在入口来暂停和恢复运行。
如果你要使用协程,你需要只能在boost/libs/asio/example/http/server4目录下找到的两个头文件:yield.hpp和coroutine.hpp。在这里,Boost.Asio定义了两个虚拟的关键词(宏)和一个类:
  • coroutine:这个类在实现协程时被你的连接类继承或者使用。
  • reenter(entry):这个是协程的主体。参数entry是一个指向coroutine实例的指针,它被当作一个代码块在整个方法中使用。
  • yield code:它把一个声明当作协程的一部分来运行。当下一次进入方法时,操作会在这段代码之后执行。
为了更好的理解,我们来看一个例子。我们会重新实现第四章,异步客户端中的应用,这是一个简单的可以登录,ping,然后能告诉你其他客户端登录的客户端应用。
核心代码和下面的代码片段类似:
  1.  class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr>
                         , public coroutine, boost::noncopyable {
    
       ...
       void step(const error_code & err = error_code(), size_t bytes = 0) {
    
           reenter(this) { for (;;) {
               yield async_write(sock_, write_buffer_, MEM_FN2(step,_1,_2) );
               yield async_read_until( sock_, read_buffer_,"\n", MEM_
    
       FN2(step,_1,_2));
               yield service.post( MEM_FN(on_answer_from_server));
    

    }} }

    }; 

首先改变的事就是,我们只有一个叫做step()的方法,而不是有大量类似connect(),on_connect(),on_read(),do_read(),on_write(),do_write()等等的成员方法。
方法的主体在reenter(this) { for (;;) { }} 内。你可以把reenter(this)当作我们上次运行的代码,所以这次我们就可以执行下一次的代码。
在reenter代码块中,你会发现几个yield声明。你第一次进入方法时,async_write方法被执行,第二次async_read_until方法被执行,第三次service.post方法被执行,然后第四次async_write方法被执行,然后继续。
你永远不能忘记for(;;){}实例。参考下面的代码片段:
void step(const error_code & err = error_code(), size_t bytes = 0) {
       reenter(this) {
           yield async_write(sock_, write_buffer_, MEM_FN2(step,_1,_2) );
           yield async_read_until( sock_, read_buffer_, "\n",MEM_
   FN2(step,_1,_2));
           yield service.post( MEM_FN(on_answer_from_server));
       }

如果我们第三次使用上述的代码片段,我们会进入方法然后执行service.post。当我们第四次进入方法时,我们跳过service.post,不执行任何东西。当执行第五次时就是一样的了,然后一直循环下去:
class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr>
                     , public coroutine, boost::noncopyable {
       talk_to_svr(const std::string & username) : ... {}
       void start(ip::tcp::endpoint ep) {
           sock_.async_connect(ep, MEM_FN2(step,_1,0) );
       }
       static ptr start(ip::tcp::endpoint ep, const std::string &
   username) {
           ptr new_(new talk_to_svr(username)); new_->start(ep); return
   new_;

}

       void step(const error_code & err = error_code(), size_t bytes = 0)
   {

);

reenter(this) { for (;;) {
    if ( !started_) {
        started_ = true; std::ostream out(&write_buf_);
        out << "login " << username_ << "\n";
    }
    yield async_write(sock_, write_buf_, MEM_FN2(step,_1,_2)
            yield async_read_until( sock_,read_buf_,"\n", MEM_
FN2(step,_1,_2));
            yield service.post( MEM_FN(on_answer_from_server));
        }}
    }
    void on_answer_from_server() {
        std::istream in(&read_buf_); std::string word; in >> word;
if ( word == "login") on_login();
           else if ( word == "ping") on_ping();
           else if ( word == "clients") on_clients();
           read_buf_.consume( read_buf_.size());
           if (write_buf_.size() > 0) service.post( MEM_FN2(step,error_
   code(),0));
       }

... private:

       ip::tcp::socket sock_; streambuf read_buf_, write_buf_;
       bool started_; std::string username_; deadline_timer timer_;
   };
当我们启动连接,start()被调用,然后异步地连接到服务端。当连接完成时,我们第一次进入step()。这就是我们发送我们登录信息的时候。
在那之后,我们使用async_write,然后async_read_until,然后处理消息(on_answer_from_server)。
在on_answer_from_server,我们处理进来的消息;我们读取第一个字符,然后把它分发到适合的方法。然后,忽略剩下的消息(如果还有一些消息没读完):
class talk_to_svr : ... {
       ...
       void on_login() { do_ask_clients(); }
       void on_ping() {
           std::istream in(&read_buf_);
           std::string answer; in >> answer;
           if ( answer == "client_list_changed") do_ask_clients();
           else postpone_ping();
       }
       void on_clients() {
           std::ostringstream clients; clients << &read_buf_;
           std::cout << username_ << ", new client list:" << clients.
   str();
           postpone_ping();
       }
       void do_ping() {
           std::ostream out(&write_buf_); out << "ping\n";
           service.post( MEM_FN2(step,error_code(),0));

void postpone_ping() {

           timer_.expires_from_now(boost::posix_time::millisec(rand() %
   7000));
           timer_.async_wait( MEM_FN(do_ping));
       }
       void do_ask_clients() {
           std::ostream out(&write_buf_); out << "ask_clients\n";
       }

}; 

例子还会更复杂一点,因为我们需要随机地ping服务端。实现这个我们需要在第一次请求完客户端列表之后做一个ping操作。然后,在每个从服务端返回的ping操作的结果中,我们做另外一个ping操作。
使用下面的代码片段来运行所有这个过程:
int main(int argc, char* argv[]) {
       ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"),
   8001);
       talk_to_svr::start(ep, "John");
       service.run();

使用协程,我们节约了15行代码,而且代码也变的更加易读。
在这里我们仅仅是触碰到了协程。如果你想要了解更多,请登录作者的个人主页:http://blog.think-async. com/2010_03_01_archive.html.

总结
我们已经了解了Boost.Asio如何玩转STL stream和streambuf对象。我们也了解了协程如何让我们的代码更简洁和易读。
下面就是重头戏了,比如Asio VS Boost.Asio,高级调试,SSL和平台相关特性












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