可靠性疑问
TCP 是可靠的传输协议,不会丢包、乱序,其在理论上是非常可靠的,但在实际应用中需要区分场景。
- 发送方能不能知道已发送的数据对方是不是都收到了?或者收到多少?不能。
- 如果怀疑对方没收到,有没有办法可以确认对方没有收到?不能。
- 需要发送 123,对方会不会却收到 1223?会的。
第一个问题
众所周知 TCP 拥有 ACK,ACK 就是用来确认对方接收到了多少字节。但是 ACK 是 OS 的操作,OS 收到之后并不会通知用户程序。发送的流程如下:
- 应用程序把待发送的数据交给操作系统
- 操作系统把数据接收到自己的 buffer 里,接收完成后通知应用程序发送完成
- 操作系统进行实际的发送操作
- 操作系统收到对方的 ACK
如果在执行第二步之后,网络出现了暂时性故障,TCP 断开了连接,会发生什么?如果是网络游戏则很简单,可以将用户踢下线,让其重新登录。但如果是比较严肃的场景,当然希望能够支持 TCP 重连,但是重连后如何知道哪些数据已发送、哪些数据已丢失。
以Windows I/O completion ports举个例子。一般的网络库实现是这样的:在调用WSASend之前,malloc一个WSABuffer,把待发送数据填进去。等到收到操作系统的发送成功的通知后,把buffer释放掉(或者转给下一个Send用)。在这样的设计下,就意味着一旦遇上网络故障,丢失的数据就再也找不回来了。你可以reconnect,但是你没办法resend,因为buffer已经被释放掉了。所以这种管理buffer的方式是一个很失败的设计,释放buffer应当是在收到response之后。
方案:不要依赖于操作系统的发送成功通知,也不要依赖于TCP的ACK,如果你希望保证对方能收到,那就在应用层设计一个答复消息。再或者说,one-way RPC都是不可靠的,无论传输层是TCP还是UDP,都有可能会丢。
第二个问题
这是设计应用层协议的人很需要考虑的,简单来说,”成功一定意味着成功,而失败则未必意味着失败“。比如正在通过网银转账,这是出现“网络超时,转账操作可能失败”,这时并不能确定是否转账成功。即“失败”的定义可以包含多个层次。
方案:采用positioned write。即在客户端发给服务器的请求里加上文件偏移量(offset)。缺点是:若你想要多个客户端同时追加写入同一个文件,那几乎是不可能的。
第三个问题
方案:在应用层给每个message标记一个id,让接收者去重即可。
如何正确关闭连接
简单来说,谁是收到最后一条消息的人,谁来主动关闭 TCP 连接。另一方在 recv 返回 0 字节之后 close,千万不要主动 close。
在协议设计上,分两种情况:
- 协议是一问一答,类似于 HTTP,且发问的总是同一方。一方只问,另一方只答;
- 有显示 EOF 的消息通知对方 shutdown。
如果不满足以上两点的任何一点,那么就没有任何一方能够判断它收到的消息是不是最后一条。
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.