【问题标题】:Is TCP not reliable?TCP不可靠吗?
【发布时间】:2017-12-04 19:01:35
【问题描述】:

我相信 TCP 是可靠的。如果write(socket, buf, buf_len)close(socket) 返回没有错误,接收者将收到与buf 完全相同的数据,长度为buf_len

但是this article 说 TCP 不可靠。

答:

  sock = socket(AF_INET, SOCK_STREAM, 0);  
  connect(sock, &remote, sizeof(remote));
  write(sock, buffer, 1000000);             // returns 1000000
  close(sock);

乙:

int sock = socket(AF_INET, SOCK_STREAM, 0);
bind(sock, &local, sizeof(local));
listen(sock, 128);
int client=accept(sock, &local, locallen);
write(client, "220 Welcome\r\n", 13);

int bytesRead=0, res;
for(;;) {
    res = read(client, buffer, 4096);
    if(res < 0)  {
        perror("read");
        exit(1);
    }
    if(!res)
        break;
    bytesRead += res;
}
printf("%d\n", bytesRead);

测验问题 - 程序 B 完成后会打印什么?

A) 1000000 
B) something less than 1000000 
C) it will exit reporting an error 
D) could be any of the above

遗憾的是,正确答案是“D”。但这怎么会发生呢?程序 A 报告所有数据都已正确发送!

如果这篇文章是真的,我必须改变主意。但我不确定这篇文章是否属实。

这篇文章是真的吗?

【问题讨论】:

  • 例如,在尝试读取数据时,数据可能尚未到达接收方,因此 read() 返回 0 并过早地跳出循环。

标签: c sockets tcp


【解决方案1】:

TCP 可靠的(至少在较低级别的协议是可靠的),但程序员可能以不可靠的方式使用它。

这里的问题是,在另一端正确接收到所有发送的数据之前,不应关闭套接字:关闭信号可能会在最后一个数据仍在传输之前破坏连接。

确保对等方正确接收的正确方法是graceful shutdown

【讨论】:

    【解决方案2】:

    TCP/IP 是可靠的,因为“可靠”一词具有非常特殊(且有限)的含义。

    具体来说,当 write() 返回 1000000 时,它会让你做出以下承诺:

    1. 您的 1000000 字节数据已复制到 TCP 套接字的传出数据缓冲区中,从现在开始,TCP/IP 堆栈负责将这些字节传递给远程程序。
    2. 如果可以传递这些字节(通过合理的努力),那么即使某些传输的 TCP 数据包在传递过程中被丢弃,它们也会被传递。
    3. 如果字节确实被传递,它们将被准确且有序地传递(相对于彼此,也相对于传递给同一套接字上之前和之后的 write() 调用的数据)。

    但也有一些 write() 不提供的保证(通常也不能)。特别是:

    1. write() 无法保证接收应用程序在调用 recv() 以获取所有 1000000 个字节之前不会退出或崩溃。
    2. write() 不能保证接收应用程序会对它接收到的字节做正确的事情(即它可能只是忽略它们或错误处理它们,而不是对它们采取行动)
    3. write() 不能保证接收应用程序将调用 recv() 到 recv() 那些字节(即它可能只是保持套接字打开但从不调用 recv())
    4. write() 不能保证您的计算机和远程计算机之间的网络基础设施能够传送字节——即,如果某些数据包被丢弃,没问题,TCP 层会重新发送它们,但是例如如果有人从您的电缆调制解调器中拔出电源线,那么字节根本无法到达目的地,并且在尝试并未能获得 ACK 几分钟后,TCP 层将放弃并出错连接。
    5. write() 不能保证接收应用程序 100% 正确处理套接字关闭问题(如果不能,那么您在远程程序关闭套接字之前发送的任何数据都可能被静默丢弃,因为没有接收器可以接收它)
    6. write() 不保证接收应用程序会以与您发送它们相同的段大小接收字节。即,仅仅因为您在一次调用 send() 中发送了 1000000 个字节并不能保证意味着接收应用程序可以在一次调用 recv() 时接收 1000000 个字节。相反,他可能会以任意大小的块接收数据,例如1-byte-per-recv()-call,或 1000-bytes-per-recv-call,或 TCP 层感觉提供的任何其他大小。

    请注意,在上面列出的所有情况下,问题都发生在 write() 返回之后,这就是为什么 write() 不能在这些情况发生时只返回错误代码。 (当然,稍后调用 write() 可能会返回一个错误代码,但这不会特别帮助您了解已交付字节和未交付字节之间的界线的位置)

    TLDR:TCP/IP 比 UDP 更可靠,但不是 100% 铁定保证不会出错。如果你真的想确保你的字节在接收端得到处理,你需要对你的接收应用程序进行编程,以发送回某种更高级别的确认,它接收到(并成功处理!)你发送的字节。

    【讨论】:

      【解决方案3】:

      TCP/IP 提供可靠性,这意味着它允许重新传输丢失的数据包,从而确保(最终)接收到所有传输的数据超时错误。要么您的东西送达,要么您遇到超时错误

      What is the correct way of reading from a TCP socket in C/C++?

      顺便说一句,TCP/IP 在某种程度上是可靠的,它保证在网络正常工作的情况下传送......如果你拔掉电缆,TCP/IP 将不会传送你的数据包

      附:您至少应该在buffer 缓冲区内推进指针

      void ReadXBytes(int socket, unsigned int x, void* buffer)
      {
          int bytesRead = 0;
          int result;
          while (bytesRead < x)
          {
              result = read(socket, buffer + bytesRead, x - bytesRead);
              if (result < 1 )
              {
                  // Throw your error.
              }
      
              bytesRead += result;
          }
      }
      

      【讨论】:

      • 为什么是if (result &lt; 1 )read 返回零并不一定意味着错误。
      • 因为调用被阻塞了。它以某种东西 (>= 1) 或连接关闭/错误 (=1 数据到达,0 = EOF,-1 = 错误 man7.org/linux/man-pages/man2/read.2.html
      【解决方案4】:

      程序 A 报告所有数据都已正确发送!

      没有。程序A中write的返回仅表示返回的字节数已经传送到操作系统的内核。它并不声称数据已被本地系统发送,已被远程系统接收,甚至已被远程应用程序处理。

      我相信 TCP 是可靠的。

      TCP 仅在传输层提供可靠性。这仅意味着它将确保检测到数据丢失(并重新传输数据包)、检测到数据重复(并丢弃重复数据)以及检测并修复数据包重新排序。

      TCP 不要求更高层的任何可靠性。如果应用程序需要它,它必须由应用程序本身提供。

      【讨论】:

        猜你喜欢
        • 2016-09-19
        • 1970-01-01
        • 2011-05-09
        • 2013-08-13
        • 1970-01-01
        • 2013-12-05
        • 1970-01-01
        • 1970-01-01
        • 2012-03-05
        相关资源
        最近更新 更多