C++网络编程详细讲解

来自:网络
时间:2023-01-01
阅读:
目录

一、网络编程

尽管 Boost.Asio 可以异步处理任何类型的数据,但它主要用于网络编程。这是因为 Boost.Asio 早在添加额外的 I/O 对象之前就支持网络功能。网络函数非常适合异步操作,因为通过网络传输数据可能需要很长时间,这意味着确认和错误可能不会像发送或接收数据的函数那样快。

二、库示例

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

示例 32.5。带有 boost::asio::ip::tcp::socket 的网络客户端

#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();
}

Example32.5

示例 32.5 使用了三个处理程序:connect_handler() 和 read_handler() 在建立连接并接收到数据时被调用。 resolve_handler() 用于名称解析。

因为只有在建立连接之后才能接收数据,并且因为只有在解析名称之后才能建立连接,所以各种异步操作都是在处理程序中启动的。在 resolve_handler() 中,指向从名称解析的端点的迭代器 it 与 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_resolve(),启动异步操作来解析名称。示例 32.5 解析名称 theboostcpplibraries.com。异步操作启动后,在 I/O 服务对象上调用 run() 以将控制权传递给操作系统。

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

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

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

请注意,在将数据写入 std::cout 后,read_handler() 会再次调用 async_read_some()。这是必需的,因为您无法确定整个主页是否已在单个异步操作中下载并复制到字节中。对 async_read_some() 的重复调用和对 read_handler() 的重复调用仅在连接关闭时结束,这发生在网络服务器发送整个主页时。然后 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();
}

Example32.6

示例 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() 发送。此函数将 data 中的所有数据写入套接字。 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 退出。请注意,虽然 data 仅在 accept_handler() 中使用,但它不能是局部变量。数据通过 boost::asio::buffer() 引用传递到 boost::asio::async_write()。当 boost::asio::async_write() 和 accept_handler() 返回时,异步操作已开始,但尚未完成。数据必须存在,直到异步操作完成。如果数据是一个全局变量,这是有保证的。

练习

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

返回顶部
顶部