第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地址和本地文件名作爲命令行選項傳遞。 客戶端應將文件傳輸到服務器,然後將其保存到當前工作目錄中。 在傳輸過程中,客戶端應顯示某種進度指示器,以便用戶知道傳輸正在進行中。 用回調實現客戶端和服務端。

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