第11.10章: socket 文件

English

概述

fast_io 把 socket 当作文件。一旦你有了 socket 文件对象,就用熟悉的 print、println 和 scan API 收发数据 — 不需要单独的 socket 库。本节介绍两个主要的 socket 类型,以及创建它们的自由函数。

::fast_io::native_socket_file

::fast_io::native_socket_file 是平台原始 socket 句柄之上的一层薄包装:

native_socket_file 给你不带缓冲的原始访问。对典型的 TCP I/O,你会想要下面那个带缓冲的变体。

::fast_io::iobuf_socket_file

::fast_io::iobuf_socket_file 把一个 native_socket_file 包进 basic_iobuf,加上用户态缓冲。这是 TCP I/O 的标准类型。

重要:在非 Windows 平台上(包括 POSIX、Cygwin 和 MSYS2),::fast_io::iobuf_socket_file 实际上与 ::fast_io::iobuf_file 是同一个类型。这与 native_socket_file 和 native_file 的关系一致 — 在 POSIX 系统上 socket 就是文件描述符,因此类型层面没有区别。


#include <fast_io.h>

int main() {
    using namespace ::fast_io::iomnp;

    // Initialize networking on Windows (no-op on POSIX)
    ::fast_io::net_service net_svc;

    // Resolve hostname and connect
    auto dns = ::fast_io::native_dns_file(u8"example.com");
    ::fast_io::iobuf_socket_file sock{::fast_io::tcp_connect(::fast_io::to_ip(dns, 80u16))};

    // Send an HTTP GET request using the normal print API
    print(sock,
        "GET / HTTP/1.1\r\n"
        "Host: example.com\r\n"
        "Connection: close\r\n"
        "\r\n");

    // Read the response line by line using the normal scan API
    ::fast_io::string line;
    while (scan<::fast_io::getline>(sock, line)) {
        println(line);
    }
}

由于 socket 是带缓冲的,许多小的 print 调用会被合并成更少的系统调用,与 iobuf_file 完全一样。

::fast_io::u8http_header_buffer

::fast_io::u8http_header_buffer 是专门用于解析 HTTP 响应(或请求)头的缓冲区。它可以被 scan_context_define 扫描,所以你可以直接把它喂进普通的 scan 流水线:

默认内部缓冲区大小是 4096 字节,足以应付现实世界中绝大多数 HTTP 头。


#include <fast_io.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::net_service net_svc;

    auto dns = ::fast_io::native_dns_file(u8"example.com");
    ::fast_io::iobuf_socket_file sock{::fast_io::tcp_connect(::fast_io::to_ip(dns, 80u16))};

    print(sock,
        "GET / HTTP/1.1\r\n"
        "Host: example.com\r\n"
        "Connection: close\r\n"
        "\r\n");

    // Parse the HTTP response header
    ::fast_io::u8http_header_buffer hdr;
    scan(sock, hdr);

    println("Status code: ", hdr.code());
    println("Reason:      ", hdr.reason());
    println("Header len:  ", hdr.header_length);

    // The socket is now positioned at the start of the body.
    // Continue reading the body with normal scan / print calls.
}

socket 相关的自由函数

下面这些自由函数用于创建和配置 socket 连接。它们返回的句柄可用于构造 native_socket_file 或 iobuf_socket_file 对象。

函数 说明
tcp_connect(host, port) 打开到 host:port 的 TCP 连接,返回 native_socket_file。
tcp_listen(port) 在给定 port 上创建 TCP 监听 socket。
posix_bind(address) 把 POSIX socket 绑定到指定地址。
posix_listen(sockfd, backlog) 把已绑定的 POSIX socket 标记为监听状态,backlog 为队列长度。
posix_accept(sockfd) 在监听中的 POSIX socket 上接受一个到来的连接,为被接受的连接返回一个新的 native_socket_file。

TCP 服务器示例

把这些串起来,下面是一个最小的 TCP 服务器:监听 8080 端口,接受一个连接,发送一句问候,然后关闭:


#include <fast_io.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::net_service net_svc;

    // Listen on port 8080
    auto listener = ::fast_io::tcp_listen(8080u16);

    // Accept one incoming connection
    ::fast_io::native_socket_file conn{::fast_io::posix_accept(listener)};

    // Wrap in a buffered socket for convenient I/O
    ::fast_io::iobuf_socket_file buf_conn{::fast_io::io::transmit(conn)};

    // Send a greeting using the normal print API
    println(buf_conn, "Hello from fast_io server!\n");
}

::fast_io::net_service

在 Windows 上,任何 socket 操作之前都必须先初始化网络子系统。这通过在程序开始时调用 WSAStartup()、在退出时调用 WSACleanup() 完成。::fast_io::net_service 是一个自动处理这件事的 RAII 包装:


#include <fast_io.h>
#include <fast_io_driver/tcp.h>

int main() {
    using namespace ::fast_io::iomnp;

    // Initialize networking on Windows (no-op on POSIX)
    ::fast_io::net_service net_svc;

    // Now safe to use socket operations
    ::fast_io::iobuf_socket_file sock{::fast_io::tcp_connect("example.com", 80u16)};

    print(sock,
        "GET / HTTP/1.1\r\n"
        "Host: example.com\r\n"
        "Connection: close\r\n"
        "\r\n");

    ::fast_io::string line;
    while (scan<::fast_io::getline>(sock, line)) {
        println(line);
    }
}

如果你的程序使用 socket,请总是在 main() 开头创建一个 ::fast_io::net_service 对象。这保证了跨平台可移植性 — 在 Windows 上它初始化网络子系统,在 POSIX 上它只是无害的空操作。

DNS 解析

当你需要按名字(而不是 IP 地址)连接主机时,必须先解析主机名到 IP 地址。fast_io 为此提供 ::fast_io::native_dns_file。它包装操作系统的 DNS 解析设施(POSIX 上的 getaddrinfo(),Windows NT 上的 GetAddrInfoW())。

native_dns_file 可迭代 — 它可能解析出多个 IP 地址(IPv4 与 IPv6 都有)。你可以遍历全部,或者用 ::fast_io::to_ip() 取第一个结果,得到一个带端口号、可直接交给 tcp_connect() 的 IP 地址。


#include <fast_io.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::net_service net_svc;

    // Resolve hostname to IP addresses
    ::fast_io::native_dns_file dns(u8"example.com");

    // Option 1: Use the first resolved address with a port
    ::fast_io::iobuf_socket_file sock{::fast_io::tcp_connect(::fast_io::to_ip(dns, 80u16))};

    print(sock, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");

    // Option 2: Iterate over all resolved addresses
    for (auto const& ent : dns) {
        println("Resolved IP: ", ::fast_io::to_ip_address(ent));
    }
}

关键函数有:

附录

下列附录提供额外的参考资料:

要点