第11.10章: socket 文件
概述
fast_io 把 socket 当作文件。一旦你有了 socket 文件对象,就用熟悉的 print、println 和 scan API 收发数据 — 不需要单独的 socket 库。本节介绍两个主要的 socket 类型,以及创建它们的自由函数。
::fast_io::native_socket_file
::fast_io::native_socket_file 是平台原始 socket 句柄之上的一层薄包装:
-
POSIX(Linux、macOS、FreeBSD 等):就是普通的文件描述符。在 POSIX 系统上 socket 就是文件描述符,所以它只是一个
int包装。重要:在 POSIX 系统上(包括 Cygwin 与 MSYS2),::fast_io::native_socket_file实际上与::fast_io::native_file是同一个类型。这是因为 POSIX 把 socket 当作文件描述符,在类型层面并不区分“socket 文件”和“普通文件”。 -
Windows:
SOCKET句柄包装。Windows 的 socket 不是文件描述符,所以这个类型携带的是原生SOCKET值。只有在原生 Windows(Win32)上,native_socket_file才与native_file不同。
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 流水线:
header_length— 头部消耗的总字节数。request()— 请求方法(用于请求头)。code()— HTTP 状态码(用于响应头)。reason()— 原因短语(例如"OK"、"Not Found")。
默认内部缓冲区大小是 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 包装:
-
Windows(Win32):构造时调用
WSAStartup(),析构时调用WSACleanup()。在main()开头创建一个实例,以保证网络子系统在你的程序整个生命周期内都已初始化。 -
POSIX 及其他系统:
::fast_io::net_service是一个空操作类型,构造和析构都没有开销。为了可移植性你仍可以创建实例,但它什么也不做。
#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));
}
}
关键函数有:
::fast_io::native_dns_file(hostname)— 把主机名解析为 IP 地址::fast_io::to_ip(dns_entry, port)— 把一条 DNS 记录转换为带端口的 IP 地址::fast_io::to_ip_address(dns_entry)— 把一条 DNS 记录转换为纯 IP 地址(不带端口)
附录
下列附录提供额外的参考资料:
- 第11.10.1章: 网络基础 — OSI 模型、IP 地址、常用端口
- 第11.10.2章: DNS — DNS 记录、DoH、DoT
- 第11.10.3章: 现代网络协议 — HTTP/1.1/2/3、QUIC、WebSocket
- 第11.10.4章: fast_io 中的协议支持 — TCP、UDP、Unix socket 等
要点
-
::fast_io::native_socket_file包装原始 socket 句柄 — POSIX 上是文件描述符,Windows 上是SOCKET。在非 Windows 平台(包括 Cygwin/MSYS2)上,它与::fast_io::native_file是同一个类型。 -
::fast_io::iobuf_socket_file加上用户态缓冲,是 TCP I/O 的标准类型。在非 Windows 平台上,它与::fast_io::iobuf_file是同一个类型。 -
用
::fast_io::native_dns_file把主机名解析为 IP 地址。配合::fast_io::to_ip(dns, port)与tcp_connect()即可按主机名连接。 -
::fast_io::u8http_header_buffer通过scan解析 HTTP 头。它暴露header_length、request()、code()和reason()。默认缓冲区大小为 4096 字节。 -
自由函数有:
tcp_connect()、tcp_listen()、posix_bind()、posix_listen()、posix_accept()。 -
::fast_io::net_service是初始化 Windows 网络子系统(WSAStartup/WSACleanup)的 RAII 类型。在 POSIX 系统上它是空操作。为了可移植性,总是在main()开头创建一个。 -
在
fast_io中 socket 就是文件 — 你已经熟悉的print、scan和定制点同样适用于 socket。