客户端发送完毕,代理看到 EOF,应答却消失了。立即关闭代理的两个套接字恰好会造成这种顺序。在 TCP 中继中,一个方向有序结束并不意味着另一个方向也结束了。

设想一个客户端发出请求、关闭自己的发送方向,然后等待应答:

client -- request, then FIN --> proxy --> service
client <-------- reply ------- proxy <-- service

代理有两条 TCP 连接和两个各自独立推进的字节流。它必须在转发先行字节之后,继续把输入结束的指示传下去。

转发 EOF 之前先排空队列

在 TCP 流上收到非零长度时,返回零表示该入向流有序结束。把这个读取方向标记为完成。不要再等它带来更多输入,但保留已经为对端套接字排队的字节。

当客户端到服务端的队列排空后,在 POSIX 套接字上调用 shutdown(service_socket, SHUT_WR)。这会终止继续向服务端发送,同时让服务端到客户端的方向保持可用。当服务端完成应答后,排空应答队列再施加镜像操作。

关闭描述符应发生在两个方向及其待发缓冲都已完成之时,或错误、取消要求终止之时。对永不完成剩余半程的对端,要保留有上限的超时。

可读是一种读取邀请

可读的套接字可能有数据,也可能处于 EOF 状态。调用 recv 并解释其结果。正数是待转发的字节数,零表示该方向结束。在非阻塞套接字上,EAGAIN 或 EWOULDBLOCK 表示当前没有更多输入,而 EINTR 可能需要重试。两者都不是 EOF 这一事件。

不要在返回的字节缓冲区里寻找 FIN 标志。TCP 交给这个 API 的是有序字节流;包边界和 FIN 段不是普通的应用负载。

这是裸字节隧道的生命周期。解析报文的 HTTP 代理仍需遵守 HTTP 分帧,终止 TLS 的代理也有 TLS 关闭协议。把这些协议层面的判断放在按方向转发 TCP 之上。

相关文章