首页 > 代码库 > Boost.Asio c++ 网络编程翻译(28)
Boost.Asio c++ 网络编程翻译(28)
协程
Boost.Asio的作者在2009-2010年间实现了非常酷的一个部分,协程,它能让你更简单地设计你的异步应用。
它们可以让你同时享受同步和异步两个世界中最好的部分,这就是:异步编程但是很简单就能遵循流程控制,就好像应用是按流程实现的。
正常的流程已经在情形1种展示了,如果使用协程,你会尽可能的接近情形2。
简单来说,就是协程允许在一个方法中的指定位置存在入口来暂停和恢复运行。
如果你要使用协程,你需要只能在boost/libs/asio/example/http/server4目录下找到的两个头文件:yield.hpp和coroutine.hpp。在这里,Boost.Asio定义了两个虚拟的关键词(宏)和一个类:
- coroutine:这个类在实现协程时被你的连接类继承或者使用。
- reenter(entry):这个是协程的主体。参数entry是一个指向coroutine实例的指针,它被当作一个代码块在整个方法中使用。
- yield code:它把一个声明当作协程的一部分来运行。当下一次进入方法时,操作会在这段代码之后执行。
为了更好的理解,我们来看一个例子。我们会重新实现第四章,异步客户端中的应用,这是一个简单的可以登录,ping,然后能告诉你其他客户端登录的客户端应用。
核心代码和下面的代码片段类似:
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)
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。