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

PS:至此终于完成了Boost.Asio C++ network programming一书的翻译,这是我人生第一本完整翻译的书,从开始的磕磕绊绊,到最后小有心得,我收获很多。我将把这个系列的博客进行整理和校对,希望有兴趣的人可以帮我一起,来给大家提供更好更专业的阅读体验。

句柄追踪信息到文件

默认情况下,句柄的追踪信息被输出到标准错误流(相当于std::cerr)。你想把输出重定向到其他地方的可能性是非常高的。对于控制台应用,输出和错误输出都被默认输出到相同的地方,也就是控制台。但是对于一个windows(非命令行)应用来说,默认的错误流是null。

你可以通过命令行把错误输出重定向,比如:

some_application 2>err.txt

或者,如果你不是很懒,你可以代码实现,就像下面的代码片段:

//  对于Windows
   HANDLE h = CreateFile("err.txt", GENERIC_WRITE, 0, 0, CREATE_ALWAYS,
   FILE_ATTRIBUTE_NORMAL , 0);
   SetStdHandle(STD_ERROR_HANDLE, h);
   // 对于Unix
   int err_file = open("err.txt", O_WRONLY);
   dup2(err_file, STDERR_FILENO);

SSL

Boost.Asio提供了一些支持基本SSL的类。它在后台使用OpenSSL,所以,如果你想使用SSL,首先从www.openssl.org下载OpenSSL然后构建它。你需要注意,构建OpenSSL通常来说不是一个简单的任务,尤其是你没有一个常用的编译器,比如Visual Studio。

假如你成功构建了OpenSSL,Boost.Asio就会有一些围绕它的封装类:

  • ssl::stream:它代替ip:<protocol>::socket来告诉你用什么
  • ssl::context:这是给第一次握手用的上下文
  • ssl::rfc2818_verification:使用这个类可以根据RFC 2818协议非常简单地通过证书认证一个主机名

首先,你创建和初始化SSL上下文,然后使用这个上下文打开一个连接到指定远程主机的socket,然后做SSL握手。握手一结束,你就可以使用

Boost.Asio的read*/write*等自由函数。

下面是一个连接到Yahoo!的HTTPS客户端例子:

  1. #include <boost/asio.hpp>
       #include <boost/asio/ssl.hpp>
       using namespace boost::asio;
       io_service service;
    
       int main(int argc, char* argv[]) {
    typedef ssl::stream<ip::tcp::socket> ssl_socket;
    
    ssl::context ctx(ssl::context::sslv23);
    ctx.set_default_verify_paths();
    
    // 打开一个到指定主机的SSL socket
    
    io_service service;
    ssl_socket sock(service, ctx);
    ip::tcp::resolver resolver(service);
    std::string host = "www.yahoo.com";
    ip::tcp::resolver::query query(host, "https");
    connect(sock.lowest_layer(), resolver.resolve(query));
    // SSL 握手
    sock.set_verify_mode(ssl::verify_none);
    sock.set_verify_callback(ssl::rfc2818_verification(host));
    sock.handshake(ssl_socket::client);
    
           std::string req = "GET /index.html HTTP/1.0\r\nHost: "
               + host + "\r\nAccept: */*\r\nConnection: close\r\n\r\n";
    
           write(sock, buffer(req.c_str(), req.length()));
           char buff[512];
           boost::system::error_code ec;
           while ( !ec) {
    
               int bytes = read(sock, buffer(buff), ec);
    
               std::cout << std::string(buff, bytes);
           }
    

    }

第一行能很好的自释。当你连接到远程主机,你使用sock.lowest_layer(),也就是说,你使用底层的socket(因为ssl::stream紧紧是一个封装)。接下来三行进行了握手。握手一结束,你使用Booat.Asio的write()方法做了一个HTTP请求,然后读取(read())所有进来的自己。

当实现SSL服务端的时候,事情会变的有点复杂。Boost.Asio有一个SSL服务端的例子,你可以在boost/libs/asio/example/ssl/server.cpp中找到。

Boost.Asio的Windows特性

接下来的特性只在Windows操作系统中实现

流处理

Boost.Asio允许你在一个Windows句柄上创建封装,这样你就可以使用大部分的自由函数,比如read(),read_until(),write(),async_read(),async_read_until()和async_write()。下面告诉你如何从一个文件读取一行:

HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0,
       OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);
   windows::stream_handle h(service, file);
   streambuf buf;
   int bytes = read_until(h, buf, ‘\n‘);
   std::istream in(&buf);
   std::string line;
   std::getline(in, line);
   std::cout << line << std::endl;

stream_handle类只有在I/O完成处理端口在使用中才有效(这是默认情况)。如果情况满足,BOOST_ASIO_HAS_WINDOWS_STREAM_HANDLE就被定义

随机访问句柄

Boost.Asio允许对一个指向普通文件的句柄进行随机读取和写入。同样,你对这个句柄创建一个封装,然后使用自由函数,比如read_at(),write_at(),async_read_at(),async_write_at()。要从1000的地方读取50个字节,你需要使用下面的代码片段:

HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0,
       OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);
   windows::random_access_handle h(service, file);
   char buf[50];
   int bytes = read_at(h, 1000, buffer( buf));
   std::string msg(buf, bytes);
   std::cout << msg << std::endl;

对于Boost.Asio,随机访问句柄只提供随机访问,你不能把它们当作流句柄使用。也就是说,自由函数,比如:read(),read_until(),write()以及他们的相对的异步方法都不能在一个随机访问的句柄上使用。

random_access_handle类只有在I/O完成处理端口在使用中才有效(这是默认情况)。如果情况满足,BOOST_ASIO_HAS_WINDOWS_RANDOM_ACCESS_HANDLE就被定义

对象句柄

你可以通过Windows 句柄等待核心对象,比如修改通知,控制台输入,事件,内存资源通知,进程,信号量,线程或者可等待的计时器。或者简单来说,所有你可以调用WaitForSingleObject的东西。你可以在他们上面创建一个object_handle封装,使用wait()或者async_wait():

void on_wait_complete(boost::system::error_code err) {}
   ...
   HANDLE evt = ::CreateEvent(0, true, true, 0);
   windows::object_handle h(service, evt);
   // 同步等待
   h.wait();
   // 异步等待
   h.async_wait(on_wait_complete);

Boost.Asio POSIX特性

这些特性只在Unix操作系统上可用

本地socket

Boost.Asio提供了对本地socket的基本支持(也就是著名的Unix 域socket)。

本地socket是一种只能被运行在主机上的应用访问的socket。你可以使用本地socket来实现简单的进程间通讯。你可以用客户端或者服务端的方式来连接两端。对于本地socket,端点时一个文件,比如/tmp/whatever。很酷的一件事情是你可以给指定的文件赋予权限,从而禁止机器上指定的用户在文件上创建socket。

你可以用客户端socket的方式连接,如下面的代码片段:

local::stream_protocol::endpoint ep("/tmp/my_cool_app");
   local::stream_protocol::socket sock(service);
   sock.connect(ep);

你可以创建一个服务端socket,如下面的代码片段:

::unlink("/tmp/my_cool_app");
   local::stream_protocol::endpoint ep("/tmp/my_cool_app");
   local::stream_protocol::acceptor acceptor(service, ep);
   local::stream_protocol::socket sock(service);
   acceptor.accept(sock);

只要socket被成功创建,你就可以像用普通socket一样使用它;它和其他socket类有相同的成员方法,而且你也可以在使用socket的自由函数中使用。

注意本地socket只有在目标操作系统支持它们的时候才可用,也就是BOOST_ASIO_HAS_LOCAL_SOCKETS(如果被定义)

连接本地socket

最终,你可以连接两个socket,或者是无连接的(数据报),或者基于连接(流):

// 基于连接
   local::stream_protocol::socket s1(service);
   local::stream_protocol::socket s2(service);
   local::connect_pair(s1, s2);
   // 数据报
   local::datagram_protocol::socket s1(service);
   local::datagram_protocol::socket s2(service);
   local::connect_pair(s1, s2);

在内部,connect_pair使用的是不是很著名的POSIX socketpair()方法。基本上它所作的事情就是在没有复杂的socket创建过程的情况下连接两个socket;只需要一行代码就完成了。这在过去用来实现线程间通信。 而在现代编程中,你需要避免它,然后你会发现在处理使用socket的遗留代码时它非常有用。

POSIX文件描述符

Boost.Asio允许在一些POSIX文件描述符,比如管道,标准I/O和其他设备(但是不是在不同文件上)上做一些同步和异步的操作。

一旦你为这样一个POSIX文件描述符创建了一个stream_descriptor实例,你就可以使用一些Boost.Asio提供的自由函数。比如read(),read_until(),write(),async_read(),async_read_until()和async_write()。

下面告诉你如何从stdin读取一行让后输出到stdout:

size_t read_up_to_enter(error_code err, size_t bytes) { ... }
   posix::stream_descriptor in(service, ::dup(STDIN_FILENO));
   posix::stream_descriptor out(service, ::dup(STDOUT_FILENO));
   char buff[512];
   int bytes = read(in, buffer(buff), read_up_to_enter);
   write(out, buffer(buff, bytes));

stream_descriptor类只在目标操作系统支持的情况下有效,即是BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR(如果定义了)

Fork

Boost.Asio支持使用了fork()系统调用的程序。你需要告诉io_service实例fork()方法什么时候会发生以及什么时候发生了。参考下面的代码片段:

service.notify_fork(io_service::fork_prepare);
   if (fork() == 0) {
       // 孩子
       service.notify_fork(io_service::fork_child);
       ...
   } else {
       // 父亲
       service.notify_fork(io_service::fork_parent);

... }

这里建议在不同的线程使用即将被调用的service,尽管Boost.Asio允许这样,我还是强烈推荐你使用多线程,因为使用boost::thread简直就是小菜一碟。

总结

为了使你的代码简单明了而奋斗。学习和使用协程。这会让你需要做的调试工作最小化,但是仅仅是在代码中有潜在的bug的情况下,Boost.Asio会伸出援手,这个我们在调试的章节就看到过。

如果你需要使用SSL,Boost.Asio允许基本的SSL编码

最终,如果你知道你的应用是针对专门的操作系统的,你可以享用Boost.Asio为那个特定的操作系统准备的特性。

网络编程在当前是非常重要的。Boost.Asio是任何21世纪C++程序员的必学内容。我们也深入理解了理论,然后付诸实践;把这本书当作一个参考以及一个便携的Boost.Asio例子的集合,因为你可以很简单的阅读,测试,理解和扩展。希望读这本书是一大乐趣。也希望编程是一大乐趣。

时间: 2024-10-21 12:02:13

Boost.Asio c++ 网络编程翻译(30)[完结]的相关文章

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

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

同步VS异步 首先,异步编程和同步编程是有极大的不同的.在同步编程中,你所有的操作都是顺序执行的,比如从一个socket中读取(请求),然后写入(回应)到socket中.每一个操作操作都是阻塞的.因为操作是阻塞的,所以为了不影响主程序,当读写一个socket时,通常创建一个或多个线程来处理socket的输入/输出.因此,同步的服务端/客户端通常是多线程的. 相反的,异步编程是事件驱动的.你启动了一个操作,但是你不知道它何时会结束:你只是提供一个回调,当操作结束时,它会调用这个API,并返回操作结