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

PS:昨晚上账号被盗了,好在客服态度很好,今天培训一整天,所以现在才发布

回显服务端/客户端

在这一章,我们将会实现一个小的客户端/服务端应用,这可能回事你写过的最简单的客户端/服务端应用。这就是回显应用,一个把客户端写过来的任何内容回显给其本身,然后关闭连接的的服务端。这个服务端可以处理任何数量的客户端。每个客户端连接,然后发送一个消息。服务端接收到全部小时然后发送回去。在那之后,服务端关闭连接。

因此,每个回显客户端连接到服务端,发送一个消息,然后读取服务端返回的结果,确保这是它发送给服务端的消息,然后结束和服务端的会话。

我们首先实现一个同步应用,然后实现一个异步应用,以便你可以很容易对比他们

为了节省空间,下面的代码有一些被裁剪掉了。你可以在附加在这本书的代码中看到全部的代码。

TCP回显服务端/客户端

对于TCP而言,我们需要一个额外的保证;每一个消息已换行符结束(‘\n’)。编写一个同步回显服务端/客户端非常简单。

我们会展示编码,比如同步客户端,一个同步服务端,一个异步客户端和一个异步服务端。

TCP同步客户端

在大多数有价值的例子中,客户端通常比服务端编码要简单(因为服务端需要处理多个客户端请求)。

下面的代码展示了这条规则的一个例外:

size_t read_complete(char * buf, const error_code & err, size_t bytes)

{

if ( err) return 0;

bool found = std::find(buf, buf + bytes, ‘\n‘) < buf + bytes;

// 我们一个一个读取直到读到回车,不缓存

return found ? 0 : 1;

}

void sync_echo(std::string msg) {

msg += "\n”;

ip::tcp::socket sock(service);

sock.connect(ep);

sock.write_some(buffer(msg));

char buf[1024];

int bytes = read(sock, buffer(buf), boost::bind(read_complete,buf,_1,_2));

std::string copy(buf, bytes - 1);

msg = msg.substr(0, msg.size() - 1);

std::cout << "server echoed our " << msg << ": "<< (copy == msg ? "OK" : "FAIL") << std::endl;

sock.close();

}

int main(int argc, char* argv[]) {

char* messages[] = { "John says hi", "so does James", "Lucy just got home", "Boost.Asio is Fun!", 0

};

boost::thread_group threads;

for ( char ** message = messages; *message; ++message) {

threads.create_thread( boost::bind(sync_echo, *message));

boost::this_thread::sleep( boost::posix_time::millisec(100));

}

threads.join_all();

}

核心功能sync_echo。它包含了连接到服务端,发送信息然后等待回显的所有逻辑。

你会发现,在读取时,我使用了自由函数read(),因为我想要读’\n’之前的所有内容。sock.read_some()方法满足不了这个一起哦 iu,因为它只会读可用的,而不是全部的消息。

read()方法的第三个参数是完成处理句柄。当读取到全部消息时,它返回0。否则,它会返回我下一步(直到读取结束)能都到的最大的缓冲区大小。在我们的例子中,返回结果始终是1,因为我永远不想比我们需要的读的更多。

在main()中,我们创建了几个线程;每个线程负责把消息发送到客户端,然后等待操作结束。如果你运行这个程序,你会看到下面的输出:

server echoed our John says hi: OK

server echoed our so does James: OK

server echoed our Lucy just got home: OK

server echoed our Boost.Asio is Fun!: OK

注意因为我们是同步的,所以不需要调用service.run()。

TCP同步服务端

回显同步服务端的编写非常容易,参考如下的代码片段:

io_service service;

size_t read_complete(char * buff, const error_code & err, size_t bytes) {

if ( err) return 0;

bool found = std::find(buff, buff + bytes, ‘\n‘) < buff + bytes;

// 我们一个一个读取直到读到回车,不缓存

return found ? 0 : 1;

}

void handle_connections() {

ip::tcp::acceptor acceptor(service, ip::tcp::endpoint(ip::tcp::v4(),8001));

char buff[1024];

while ( true) {

ip::tcp::socket sock(service);

acceptor.accept(sock);

int bytes = read(sock, buffer(buff),page71image1824

boost::bind(read_complete,buff,_1,_2));

std::string msg(buff, bytes);

sock.write_some(buffer(msg));

sock.close();

}

}

int main(int argc, char* argv[]) {

handle_connections();

}

服务端的逻辑主要在handle_connections()。因为我们是单线程,我们接受一个客户端请求,读取它发送给我们的消息,然后回显,然后等待下一个连接。可以确定,当两个客户端同时连接时,第二个客户端不要等待服务端服务完第一个客户端。

还是要注意因为我们是同步,所以不需要调用service.run()。

TCP异步客户端

当我们开始异步时,编码会变得稍微有点复杂。我们会构建在第二章 保持活动中展示的connection类。

观察这个章节中接下来的代码,你会发现没有个异步操作启动了新的异步操作,以保持service.run()一直工作。

首先,核心功能如下:

#define MEM_FN(x)       boost::bind(&self_type::x, shared_from_this())
   #define MEM_FN1(x,y)    boost::bind(&self_type::x, shared_from_
   this(),y)
   #define MEM_FN2(x,y,z)  boost::bind(&self_type::x, shared_from_
   this(),y,z)
   class talk_to_svr : public boost::enable_shared_from_this<talk_to_svr>
                     , boost::noncopyable {
       typedef talk_to_svr self_type;
       talk_to_svr(const std::string & message)
         : sock_(service), started_(true), message_(message) {}
       void start(ip::tcp::endpoint ep) {
           sock_.async_connect(ep, MEM_FN1(on_connect,_1));
       }

public:

typedef boost::system::error_code error_code;

       typedef boost::shared_ptr<talk_to_svr> ptr;
       static ptr start(ip::tcp::endpoint ep, const std::string &
   message) {
           ptr new_(new talk_to_svr(message));
           new_->start(ep);
           return new_;
       }
       void stop() {
           if ( !started_) return;
           started_ = false;
           sock_.close();
       }
       bool started() { return started_; }
       ...
   private:
       ip::tcp::socket sock_;
       enum { max_msg = 1024 };
       char read_buffer_[max_msg];
       char write_buffer_[max_msg];
       bool started_;
       std::string message_; 

};

我们一直需要使用指向talk_to_svr的智能指针,这样的话当在tack_to_svr的实例上有异步操作时,那个实例是一直活动的。为了避免错误,比如在栈上构建一个talk_to_svr对象的实例时,我把构造方法设置成了私有而且不允许拷贝构造(继承自boost::noncopyable)。

我们有了核心方法,比如start(),stop()和started(),它们所做的事情也正如它们名字表达的一样。如果需要建立连接,调用talk_to_svr::start(endpoint, message)即可。我们同时还有一个read缓冲区和一个write缓冲区。(read_buufer_和write_buffer_)。

MEM_FN*是一个方便使用的宏,它们强制通过shared_ptr_from_this()方法使用一个指向*this的智能指针。

下面的几行代码和之前解释的非常不同:

//等同于 "sock_.async_connect(ep, MEM_FN1(on_connect,_1));"
   sock_.async_connect(ep,
       boost::bind(&talk_to_svr::on_connect,shared_ptr_from_this(),_1));
   sock_.async_connect(ep, boost::bind(&talk_to_svr::on_connect,this,_1));

在上述例子中,我们正确的创建了async_connect的完成处理句柄;在调用完成处理句柄之前它会保留一个指向talk_to_server实例的智能指针,从而保证当其发生时talk_to_server实例还是保持活动的。

在接下来的例子中,我们错误的创建了完成处理句柄,当它被调用时,talk_to_server实例很可能已经被释放了。

从socket读取或写入时,你使用如下的代码片段:

void do_read() {
       async_read(sock_, buffer(read_buffer_),
                   MEM_FN2(read_complete,_1,_2), MEM_FN2(on_read,_1,_2));
   }
   void do_write(const std::string & msg) {
       if ( !started() ) return;
       std::copy(msg.begin(), msg.end(), write_buffer_);
       sock_.async_write_some( buffer(write_buffer_, msg.size()),
                               MEM_FN2(on_write,_1,_2));
   }
   size_t read_complete(const boost::system::error_code & err, size_t
   bytes) {
       // 和TCP客户端中的类似
   }

do_read()方法会保证当on_read()被调用的时候,我们从服务端读取一行。do_write()方法会先把信息拷贝到缓冲区(考虑到当async_write发生时msg可能已经超出范围被释放),然后保证实际的写入发生时on_write()被调用。

然后时最重要的方法,这个方法包含了类的主要逻辑:

void on_connect(const error_code & err) {
       if ( !err)      do_write(message_ + "\n");
       else            stop();
   }
   void on_read(const error_code & err, size_t bytes) {
       if ( !err) {
           std::string copy(read_buffer_, bytes - 1);
           std::cout << "server echoed our " << message_ << ": "

std::endl; }

stop(); }

<< (copy == message_ ? "OK" : "FAIL") <<
   void on_write(const error_code & err, size_t bytes) {
       do_read();

}

当我们连接上之后,我们发送消息到服务端,do_write()。当write操作结束时,on_write()被调用,它初始化了一个do_read()方法,当do_read()完成式。on_read()被调用;这里,我们简单的检查一下返回的信息是否是服务端的回显,然后推出服务。

我们会发送三个消息到服务端让它变得更有趣一点:

 int main(int argc, char* argv[]) {
       ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"),
   8001);
       char* messages[] = { "John says hi", "so does James", "Lucy got
   home", 0 };
       for ( char ** message = messages; *message; ++message) {
           talk_to_svr::start( ep, *message);
           boost::this_thread::sleep( boost::posix_time::millisec(100));
       }
       service.run();
   }

上述的代码会生成如下的输出:

server echoed our John says hi: OK
   server echoed our so does James: OK
   server echoed our Lucy just got home: OK
时间: 2024-10-14 15:36:28

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

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++ 网络编程翻译(28)

协程 Boost.Asio的作者在2009-2010年间实现了非常酷的一个部分,协程,它能让你更简单地设计你的异步应用. 它们可以让你同时享受同步和异步两个世界中最好的部分,这就是:异步编程但是很简单就能遵循流程控制,就好像应用是按流程实现的. 正常的流程已经在情形1种展示了,如果使用协程,你会尽可能的接近情形2. 简单来说,就是协程允许在一个方法中的指定位置存在入口来暂停和恢复运行. 如果你要使用协程,你需要只能在boost/libs/asio/example/http/server4目录下找

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和一个处理线程