第32章.Boost.Asio-网络编程

The Boost C++ Libraries

网络编程

即使Boost.Asio可以异步处理任何类型的数据,它也主要用于网络编程。 这是因为Boost.Asio在添加了其他I / O对象之前很早就支持网络功能。 网络功能非常适合异步操作,因为通过网络传输数据可能会花费很长时间,这意味着确认和错误可能无法像发送或接收数据的功能可以执行的速度那样快。

Boost.Asio提供了许多I / O对象来开发网络程序。 示例32.5使用类boost :::asio::ip::tcp::socket建立与另一台计算机的连接。 本示例将HTTP请求发送到Web服务器以下载主页。

示例32.5.使用boost::asio::ip::tcp::socket的Web客户端

#include <boost/asio/io_service.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <array>
#include <string>
#include <iostream>

using namespace boost::asio;
using namespace boost::asio::ip;

io_service ioservice;
tcp::resolver resolv{ioservice};
tcp::socket tcp_socket{ioservice};
std::array<char, 4096> bytes;

void read_handler(const boost::system::error_code &ec,
  std::size_t bytes_transferred)
{
  if (!ec)
  {
    std::cout.write(bytes.data(), bytes_transferred);
    tcp_socket.async_read_some(buffer(bytes), read_handler);
  }
}

void connect_handler(const boost::system::error_code &ec)
{
  if (!ec)
  {
    std::string r =
      "GET / HTTP/1.1\r\nHost: theboostcpplibraries.com\r\n\r\n";
    write(tcp_socket, buffer(r));
    tcp_socket.async_read_some(buffer(bytes), read_handler);
  }
}

void resolve_handler(const boost::system::error_code &ec,
  tcp::resolver::iterator it)
{
  if (!ec)
    tcp_socket.async_connect(*it, connect_handler);
}

int main()
{
  tcp::resolver::query q{"theboostcpplibraries.com", "80"};
  resolv.async_resolve(q, resolve_handler);
  ioservice.run();
}

Example 32.5 uses three handlers: connect_handler() and read_handler() are called when the connection is established and data is received. resolve_handler() is used for name resolution.

示例32.5使用三个处理函数:当建立连接和接收数据时,将分别调用connect_handler()和read_handler()。 resolve_handler()用于域名解析。

由于只能在建立连接后才能接收数据,并且由于只能在解析域名后才能建立连接,因此将在处理程序中启动各种异步操作。在resolve_handler()中,它的迭代器指向从域名解析的端点,与tcp_socket一起使用以建立连接。在connect_handler()中,访问tcp_socket以发送HTTP请求并开始接收数据。由于所有操作都是异步的,因此将处理程序传递给相应的函数。根据操作,可能需要传递其他参数。例如,迭代器引用从域名解析的端点。字节数组用于存储接收到的数据。

在main()中,实例化boost::asio::ip::tcp::resolver::query创建对象q。 q表示对域名解析器的查询,名称解析器是类型为boost::asio::ip::tcp::resolver的I / O对象。通过将q传递给async_resolver(),将启动异步操作来解析域名。例32.5解析了域名theboostcpplibraries.com。启动异步操作后,将在I / O服务对象上调用run()以将控制权传递给操作系统。

域名解析后,将调用resolve_handler()。处理程序首先检查域名解析是否成功。在这种情况下,ec为0。只有在那时,套接字才被访问以建立连接。第二个参数提供了要连接的服务器地址,该参数的类型为bboost::asio::ip::tcp::resolver::iterator。此参数是域名解析的结果。

调用async_connect()之后再调用处理程序connect_handler()。再次首先检查ec以确定是否可以建立连接。如果是这样,则在套接字上调用async_read_some()。通过此调用,开始读取数据。接收到的数据存储在字节数组中,该字节数组作为第一个参数传递给async_read_some()。

当接收到一个或多个字节并将其复制到字节时,将调用read_handler()。类型为std::size_t的参数bytes_transferred包含已接收的字节数。与往常一样,处理程序应首先检查ec异步操作是否成功完成。只有在这种情况下,数据才会写入标准输出。

请注意,将数据写入std::cout之后,read_handler()再次调用async_read_some()。这是必需的,因为您无法确定整个首页是否已通过一次异步操作下载并复制到字节中。仅当连接关闭时,对async_read_some()的重复调用之后,对read_handler()的重复调用才结束,这是在Web服务器发送了整个主页时才发生的。然后read_handler()报告ec中的错误。在这一点上,没有进一步的数据写入std::cout,并且套接字上没有调用async_read()。因为没有挂起的异步操作,程序将退出。

示例32.6. 使用boost::asio::ip::tcp::acceptor的时间服务器

#include <boost/asio/io_service.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <string>
#include <ctime>

using namespace boost::asio;
using namespace boost::asio::ip;

io_service ioservice;
tcp::endpoint tcp_endpoint{tcp::v4(), 2014};
tcp::acceptor tcp_acceptor{ioservice, tcp_endpoint};
tcp::socket tcp_socket{ioservice};
std::string data;

void write_handler(const boost::system::error_code &ec,
  std::size_t bytes_transferred)
{
  if (!ec)
    tcp_socket.shutdown(tcp::socket::shutdown_send);
}

void accept_handler(const boost::system::error_code &ec)
{
  if (!ec)
  {
    std::time_t now = std::time(nullptr);
    data = std::ctime(&now);
    async_write(tcp_socket, buffer(data), write_handler);
  }
}

int main()
{
  tcp_acceptor.listen();
  tcp_acceptor.async_accept(tcp_socket, accept_handler);
  ioservice.run();
}

示例32.6 是一个时间服务器。您可以与telnet客户端连接以获取当前时间。之后,时间服务器将关闭。

时间服务器使用I/O对象boost::asio::ip::tcp::acceptor接受来自另一个程序的传入连接。您必须初始化对象,以便它知道在哪个端口上使用哪种协议。在示例中,类型为boost::asio::ip::tcp::endpoint的变量tcp_endpoint用于告诉tcp_acceptor在端口2014上接受Internet协议版本4的传入连接。

接受器初始化后,调用listen()使接受器开始侦听。然后调用async_accept()接受第一次连接尝试。必须将套接字作为第一个参数传递给async_accept(),该套接字将用于在新连接上发送和接收数据。

一旦另一个程序建立连接,就会调用accept_handler()。如果成功建立连接,则使用 boost::asio::async_write()发送当前时间。此函数将数据中的所有数据写入套接字。 boost::asio::ip::tcp::socket还提供了成员函数async_write_some()。发送至少一个字节后,此函数将调用处理程序。然后,处理程序必须检查发送了多少字节以及仍然必须发送多少字节。然后,它必须再次调用async_write_some()。通过使用boost::asio::async_write()可以避免重复计算要发送的剩余字节数并调用async_write_some()。仅当发送了数据中的所有字节后,才以该功能开始的异步操作完成。

发送数据后,将调用write_handler()。此函数使用参数boost::asio::ip::tcp::socket::shutdown_send调用shutdown(),表示程序已通过套接字发送数据。由于没有待处理的异步操作,因此将退出示例32.6。请注意,尽管数据仅在accept_handler()中使用,但它不能是局部变量。数据通过boost::asio::buffer()传递给boost::asio::async_write()。当boost::asio::async_write()和accept_handler()返回时,异步操作已开始,但尚未完成。数据必须存在,直到异步操作完成。如果数据是全局变量,则可以保证。

练习

开发可以将文件从一台计算机传输到另一台计算机的客户端和服务端。 服务端启动时,应显示所有本地接口的IP地址列表,并等待客户端连接。 启动客户端时,应将服务器的IP地址和本地文件名作为命令行选项传递。 客户端应将文件传输到服务器,然后将其保存到当前工作目录中。 在传输过程中,客户端应显示某种进度指示器,以便用户知道传输正在进行中。 用回调实现客户端和服务端。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章