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

同步VS异步

Boost.Asio的作者做了一个很惊艳的工作:它可以让你在同步和异步中自由选择,从而更好的适应你的应用。

在之前的章节中,我们学习了每种类型应用的框架,比如同步客户端,同步服务端,异步客户端,异步服务端。每一个你都可以作为你应用的基础。如果要更加深入地学习各种类型应用的细节,请继续。

混合同步异步编程

Boost.Asio库允许你进行同步和异步的混合编程。我个人认为这是一个坏主意,但是Boost.Asio(就像C++一样)在你需要的时候允许你深入底层。

通常来说,当你写一个异步应用时,你会很容易掉入这个陷阱。比如在响应一个异步write操作时,假设,你做了一个同步read操作:

io_service service;
   ip::tcp::socket sock(service);
   ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 8001);
   void on_write(boost::system::error_code err, size_t bytes) {
     char read_buff[512];
     read(sock, buffer(read_buff));
   }
   async_write(sock, buffer("echo"), on_write);

毫无疑问,同步read操作会阻塞当前的线程,从而导致其他任何正在等待的异步操作变成挂起状态(对这个线程)。这是一段糟糕的代码,因为它会导致整个应用变得无响应或者整个被阻塞掉(所有异步运行的端点都必须避免阻塞,而执行一个同步的操作违反了这个原则)。

当你写一个同步应用时,你不大可能执行异步的read或者write操作,因为同步地思考已经意味着用一种线性的方式思考(执行A,然后执行B,再执行C,等等)。

我唯一能想到的同步和异步同时工作的场景就是同步操作和异步操作是完全隔离的,比如,同步和异步从一个数据库进行读写。

从客户端传递信息到服务端VS从服务端传递信息到客户端

成功的客户端/服务端应用一个很重要的部分就是来回传递消息(服务端到客户端和客户端到服务端)。你需要指定用什么来标记一个消息。换句话说,当读取一个输入的消息时,你怎么判断它被完整读取了?

标记消息结尾的方式完全取决与你(标记消息的开始很简单,因为它就是前一个消息之后传递过来的第一个字节),但是要保证消息是简单且连续的。

你可以:

  • 消息大小固定(这不是一个很好的主意,如果我们需要发送更多的数据怎么办?)
  • 通过一个特殊的字符标记消息的结尾,比如’\n’或者’\0’
  • 再消息的头部指定消息的大小

我在整本书中间采用的方式都是“使用’\n’标记消息的结尾”。所以,每次读取一条消息都会如下:

  1. char buff_[512];
       // 同步读取
       read(sock_, buffer(buff_),
    
               boost::bind(&read_complete, this, _1, _2));
       // 异步读取
    
       async_read(sock_, buffer(buff_),MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2));
    
       size_t read_complete(const boost::system::error_code & err, size_t
       bytes) {
    
           if ( err) return 0;
           already_read_ = bytes;
           bool found = std::find(buff_, buff_ + bytes, ‘\n‘) < buff_ +
    
       bytes;
           // 一个一个读,直到读到回车,无缓存
           return found ? 0 : 1;
    

    }

我把在消息头部指定消息长度这种方式作为一个练习留给读者;它是非常简单的。

客户端应用中的同步I/O

同步客户端一般都能归类到如下两种情况中的一种:

  • 它向服务端请求一些东西,读取结果,然后处理它们。然后请求一些其他的东西,然后一直持续下去。事实上,这很像之前章节里说到的同步客户端。
  • 从服务端读取消息,处理它,然后写回结果。然后读取另外一条消息,然后一直持续下去。

两种情况都使用“发送请求-读取结果”的策略。换句话说,一个部分发送一个请求到另外一个部分然后另外一个部分返回结果。这是实现客户端/服务端应用非常简单的一种方式,同时这也是我非常推荐的一种方式。

你可以创建一个Mambo Jambo类型的客户端服务端应用,你可以随心所欲地写它们中间的任何一个部分,但是这会导致一场灾难。(你怎么知道当客户端或者服务端阻塞的时候会发生什么?)。

上面的情况看上去会比较相似,但是它们非常不同:

  • 前者,服务端响应请求(服务端等待来自客户端的请求然后回应)。这是一个请求式连接,客户端从服务端拉取它需要的东西。
  • 后者,服务端发送事件到客户端然后由客户端响应。这是一个推式连接,服务端推送通知/事件到客户端。

你大部分时间都在做请求式客户端/服务端应用,这也是比较简单,同时也是比较常见的。

你可以把拉取请求(客户端到服务端)和推送请求(服务端到客户端)结合起来,但是,这是非常复杂的,所以你最好避免这种情况。把这两种方式结合的问题在于:如果你使用“发送请求-读取结果”策略。就会发生系下面一系列事情:

客户端写入(发送请求)

服务端写入(发送通知到客户端)

客户端读取服务端写入的内容,然后将其作为请求的结果进行解析

服务端阻塞以等待客户端的返回的结果,这会在客户端发送新请求的时候发生

服务端把发送过来的请求当作它等待的结果进行解析

客户端会阻塞(服务端不会返回任何结果,因为它把客户端的请求当作它通知返回的结果)

在一个请求式客户端/服务端应用中,避免上面的情况是非常简单的。你可以通过实现一个ping操作的方式来模拟一个推送式请求,我们假设每5秒钟客户端ping一次服务端。如果没有事情需要通知,服务端返回一个类似ping ok的结果,如果有事情需要通知,服务端返回一个ping [event_name]。然后客户端就可以初始化一个新的请求去处理这个事件。

复习一下,第一种情况就是之前章节中的同步客户端应用,它的主循环如下:

void loop() {
       // 对于我们登录操作的结果
       write("login " + username_ + "\n");
       read_answer();
       while ( started_) {
           write_request();
           read_answer();
           ...

} }

我们对其进行修改以适应第二种情况:

void loop() {
       while ( started_) {
           read_notification();
           write_answer();
       }
   }
   void read_notification() {
       already_read_ = 0;
       read(sock_, buffer(buff_),
               boost::bind(&talk_to_svr::read_complete, this, _1, _2));
       process_notification();
   }
   void process_notification() {
     // ... 看通知是什么,然后准备回复
   }

PS:新的迭代再次来袭…

时间: 2024-10-27 03:26:48

Boost.Asio c++ 网络编程翻译(21)的相关文章

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

第一次翻译,希望大家多多指正 实战出精华 Boost.Asio C++ 网络编程 用具体的C++网络编程例子来提升你的技能 John Torjan 用具体的C++网络编程例子来提升你的技能 Copyright ? 2013 Packt Publishing 版权所有,除了在鉴定文章或者评论中进行简单引用,如果没有经过出版者事先的书面授权,该书的任何部分都不能被转载.存储在检索系统中.或者以任何形式和方式传阅. 在这本书准备发行之前,我们已经尽我们最大的努力去保证书中信息的准确性.但是,这本书中包

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

异步服务端 这个图表是相当复杂的:从Boost.Asio出来你可以看到4个箭头指向on_accept,on_read,on_write和on_check_ping.着也就意味着你永远不知道哪个异步调用是下一个完成的调用,但是你可以确定的是它是这4个操作中的一个. 现在,我们是异步的了:我们可以继续保持单线程.接受客户端连接是最简单的部分,如下所示: ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(), 8001)

Boost.Asio c++ 网络编程翻译(30)[完结]

PS:至此终于完成了Boost.Asio C++ network programming一书的翻译,这是我人生第一本完整翻译的书,从开始的磕磕绊绊,到最后小有心得,我收获很多.我将把这个系列的博客进行整理和校对,希望有兴趣的人可以帮我一起,来给大家提供更好更专业的阅读体验. 句柄追踪信息到文件 默认情况下,句柄的追踪信息被输出到标准错误流(相当于std::cerr).你想把输出重定向到其他地方的可能性是非常高的.对于控制台应用,输出和错误输出都被默认输出到相同的地方,也就是控制台.但是对于一个w

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

保持活动 假如,你需要做下面的操作: io_service service; ip::tcp::socket sock(service); char buff[512]; ... read(sock, buffer(buff)); 在这个例子中,sock和buff的存在时间都必须比read()调用的时间要长.也就是说,在调用read()返回之前,它们都必须有效.这就是你期望的:你传给一个方法的所有参数在参数内部都必须有效.当我们采用异步方式时,事情会变得越复杂. io_service servi

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

Boost.Asio入门 什么是Boost.Asio 简单来说,Boost.Asio是一个跨平台的.主要用于网络和其他一些底层输入/输出编程的C++库. 计算机网络的设计方式有很多种,但是Boost.Asio的的方式远远优于它们.它在2005年就被包含进Boost,然后被广大Bosot的用户测试并在很多项目中使用,比如Remobo(http://www.remobo.com),可以让你创建你自己的即时私有网络(IPN),libtorrent(http://www.rasterbar.com/pr

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

*_at方法 这些方法在一个流上面做随机存取操作.你来指定read和write操作从什么地方開始(offset): async_read_at(stream, offset, buffer [, completion], handler):这种方法在一个指定的流上从offset处開始运行一个异步的read操作,当操作结束时,他会调用handler. handler的格式为:void handler(const boost::system::error_code&  err, size_t byt

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

Boost.Asio-其他特性 这章我们讲了解一些Boost.Asio不那么为人所知的特性.标准的stream和streambuf对象有时候会更难用一些,但正如你所见.它们也有它们的益处.最后,你会看到姗姗来迟的Boost.Asio协程的入口,它能够让你的异步代码变的很易读.这是很惊人的一个特性. 标准stream和标准I/O buffer 读这一章节之前你须要对STL stream和STL streambuf对象有所了解. Boost.Asio在处理I/O操作时支持两种类型的buffer: b

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

io_service类 你应该已经发现大部分使用Boost.Asio编写的代码都会使用几个ios_service的实例.ios_service是这个库里面最重要的类:它负责和操作系统打交道,等待所有异步操作的结束,然后为每一个异步操作调用完成处理程序. 如果你选择用同步的方式来创建你的应用,你不需要考虑我将在这一节向你展示的东西. 你可以用几种不同的方式来使用io_service.在下面的例子中,我们有3个异步操作,2个socket连接和一个计时器等待: 有一个io_service和一个处理线程

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

Boost.Asio基本原理 这一章涵盖了你使用Boost.Asio时必须知道的一些事情.我们也将深入研究比同步编程更机警.更有乐趣的异步编程. 网络API 这一部分包含了当使用Boost.Asio编写网络应用程序时你必须知道的事情. Boost.Asio命名空间 Boost.Asio的一切都需要包含在boost::asio的命名空间或者其子命名空间内. boost::asio:这是核心类和函数所在的地方.重要的类有io_service和streambuf.类似read, read_at, re