06-Daytime.1-A synchronous TCP daytime client

本部分的教程程序展示了如何使用asio开发简单的客户端和服务器程序。这些教程程序基于daytime协议,该协议同时支持TCP和UDP。
前三个教程程序使用TCP实现了daytime协议。

#include <array>

#include <asio.hpp>
#include <spdlog/spdlog.h>

using asio::ip::tcp;

int main(int argc, char* argv[])
{
    try {
        // 此应用程序的目的是访问日间服务,因此我们需要用户指定服务器。
        if (argc != 2) {
            spdlog::error("Usage: {} <ip>", argv[0]);
            return 1;
        }

        asio::io_context io;

        // 我们需要将应用程序参数中指定的服务器名称转换为TCP端点。
        // 为此,我们使用一个ip::tcp::resolver对象。
        tcp::resolver resolver(io);

        // 解析器接收主机名和服务名,并将其转换为一组端点列表。
        // 我们使用服务器名称(在argv[1]中指定)和服务名称(本例中为"daytime")执行解析调用。  
        //
        // 端点列表通过类型为ip::tcp::resolver::results_type的对象返回。该对象是一个范围,
        // 具有begin()和end()成员函数,可用于遍历结果。
        tcp::resolver::results_type endpoints = resolver.resolve(argv[1], "daytime");

        // 现在我们创建并连接套接字。上面获取的端点列表可能同时包含IPv4和IPv6端点,因此我们需要逐一尝试直到找到可用的连接。
        // 这使得客户端程序不依赖于特定的IP版本。asio::connect()函数会自动为我们完成这一过程。
        tcp::socket socket(io);
        asio::connect(socket, endpoints);

        for (;;) {
            // 连接已建立。现在我们需要做的就是读取来自daytime服务的响应。
            // 我们使用std::array来保存接收到的数据。asio::buffer()函数会自动确定数组的大小,以防止缓冲区溢出。
            // 除了std::array,我们也可以使用char[]或std::vector。
            std::array<char, 128> buf;
            std::error_code error;

            size_t len = socket.read_some(asio::buffer(buf), error);
            // 当服务器关闭连接时,ip::tcp::socket::read_some() 函数会以 asio::error::eof 错误退出,
            // 这就是我们如何知道要退出循环的。
            if (error == asio::error::eof) {
                break;// 连接被对方正常关闭
            } else if (error) {
                throw std::system_error(error);// 其他错误
            }

            if (len) {
                spdlog::info("Received: {}", std::string(buf.data(), len));
            }
        }
    } catch (const std::exception& e) {
        spdlog::error("Error: {}", e.what());
        return 1;
    }
}