SOCKS 握手成功了。现在中继从客户端读取、写入目标、等待完整响应,然后才再次读取客户端。这个顺序可能让一个完全正常的应用死锁。

一旦建立 TCP 隧道,SOCKS 并不定义其中的消息。目标可能先发送。双方可能各自独立发送。一方可能发出一条短记录,然后等待另一方的答复。

转发字节,而不是猜测的事务

一次接收调用会按其标志和套接字状态返回可用的字节。它并不会告诉通用中继 应用响应已经完整。等待任意大的缓冲区填满,可能妨碍转发对端继续所需的短消息。

使用应用现有的并发模型来服务两个方向:事件循环、异步 I/O 或其他刻意选择的设计。重要的行为是,一个方向的进展不需要在另一个方向凭空发明消息边界。

保留两个有界的待发缓冲区

从客户端读到的数据可能仍在等待送达目标,而来自目标的数据则在等待送达客户端。分别跟踪这些待发字节。

一次发送写入的字节可能少于请求的数量。保留未写入的部分;不要丢弃它,也不要重放整个缓冲区。暂时不可写的套接字是等待正确就绪事件的理由,而不是不停空转的理由。

给缓冲区设界。如果目标很慢,继续读取不受限制的客户端数据只会把队列搬进中继的内存。背压意味着在相应的待发缓冲区满时暂停读取,并在写入腾出空间时恢复。

一个方向的结束需要策略

TCP 可以在一个方向关闭发送,而另一个方向仍有有用的数据。决定在缓冲字节排空后如何传播这一状况,并区分有序关闭与错误或超时。

这一切都不需要中继解密或解析应用的负载。它需要正确的流处理。仅仅增大缓冲区无法修复一个等错方向、或假定每次读取都对应一个完整请求的中继。

相关文章