【问题标题】:C sockets, Incomplete file transferC套接字,不完整的文件传输
【发布时间】:2014-10-18 00:56:23
【问题描述】:

我正在编写一个 C 程序来将一个固定大小的文件(略大于 2Mb)从服务器传输到客户端。我在 Linux 上使用 TCP 套接字,我编写的代码如下:

服务器(发件人)

while (1) {
    int nread = read(file, buffer, bufsize);
    if (nread == 0) // EOF 
        break;

    if (nread < 0) {
        // handle errors
    }

    char* partial = buffer;
    while (nread > 0) {
        int nwrite = write(socket, partial, nread);
        if (nwrite <= 0) {
            // handle errors
        }
        nread   -= nwrite;
        partial += nwrite;
    }
}

// file sent
shutdown(socket, SHUT_WR);

客户(接收方)

while (filesize > 0) {
    nread = read(socket, buffer, bufsize);
    if (nread == 0) {
        // EOF - if we reach this point filesize is still > 0
        // so the transfer was incomplete
        break;
    }

    if (nread < 0) {
        // handle errors
    }

    char* partial = buffer;
    while (nread > 0) {
        nwrite = write(file, partial, nread);
        if (nwrite <= 0) {
            // handle errors
        }
        nread    -= nwrite;
        partial  += nwrite;
        filesize -= nwrite;
    }
}

if (filesize > 0) {
    // incomplete transfer
    // handle error
}

close(socket);

在我的笔记本电脑上测试代码时(客户端和服务器“都”在本地主机上,并且通信发生在环回接口上),有时客户端退出,因为read 收到 EOF,并且不是因为它收到了所有filesize 字节。由于我在服务器上使用shutdown,这应该意味着没有其他数据可以读取。 (注意服务器发送了所有字节并正确执行了shutdown

你能解释一下为什么会这样吗?

丢失的字节去哪儿了?

-----

编辑 1 - 澄清

一些用户问了一些澄清,所以我在这里发布答案:

  • 程序正在使用TCP阻塞套接字
  • filesize 是一个固定值,在客户端和服务器中都是硬编码的。
  • 没有启用/使用特殊套接字选项,例如 SO_LINGER
  • 发生错误时,服务器(发送方)已经发送了所有数据并正确执行了shutdown
  • 截至今天,在使用客户端和服务器在不同机器上测试应用程序时从未发生过该错误(通过真实网络接口而不是环回接口传输)

编辑 2

用户Cornstalks pointed me 到一个非常有趣的article 关于 TCP 的非始终可靠的行为。

这篇非常值得一读的文章描述了在 TCP 套接字之间发送未知数量的数据时有用的一些技巧。描述的技巧如下:

  1. 利用发送方的 SO_LINGER 选项。这将有助于在调用 close(2)shutdown(2) 时保持套接字打开,直到所有数据都已成功发送。
  2. 在接收器上,在实际接收循环之前要注意待处理的可读数据。这可能会导致立即发送重置。
  3. 利用shutdown(2) 向接收者发出发送者已完成发送数据的信号。
  4. 在实际发送文件之前,让接收者知道将要发送的文件的大小。
  5. 让接收者向发送者确认接收循环已经结束。这将有助于防止发送方过早关闭套接字。

阅读文章后,我升级了代码以实现技巧 1 和 5。

这就是我实现技巧 5 的方式:

服务器(发件人)

// sending loop ...

// file sent
shutdown(socket, SHUT_WR);

// wait acknowledgement from the client
ack = read(socket, buffer, bufsize);
if (ack < 0) {
    // handle errors
}

客户(接收方)

// receiving loop..

if (filesize > 0) {
    // incomplete transfer
    // handle error
}

// send acknowledgement to the server
// this will send a FIN and trigger a read = 0 on the server
shutdown(socket, SHUT_WR);

close(socket);

第 2、3 和 4 个技巧呢?

技巧号 2 不需要,因为一旦服务器接受连接,应用程序就会继续进行文件传输。不会交换额外的消息。

技巧 3 已经实施

技巧 4 也已实施。如前所述,文件大小是硬编码的,因此无需交换。

这解决了我原来的问题吗?

我的问题没有解决。该错误仍在发生,直到今天,它仅在使用本地主机上的客户端和服务器测试应用程序时发生。

你怎么看?

有没有办法防止这种情况发生?

【问题讨论】:

  • 您是否在我们的套接字上将任何特殊选项或模式设置为 SO_LINGER 或非阻塞选项?
  • 客户端如何在传输开始前知道filesize的值?
  • 是的,TCP 阻塞套接字,不,没有 SO_LINGER 的特殊选项。我使用的唯一套接字选项是服务器上的 SO_REUSEADDR ,但这不应该是造成这种情况的原因。文件大小(如果固定)始终相同,并且略高于 2 Mb
  • 您可能对why is my tcp not reliable感兴趣。
  • 您的代码中似乎忽略了这些细节:最大发送缓冲区大小为 1 048 576 字节。 SO_SNDBUF 选项的默认值为 32 767。对于 TCP 套接字,您可以指定的最大长度为 1 GB。由于您声明您没有设置套接字选项,因此您尝试将 2m 字节写入 32k 缓冲区。

标签: c sockets network-programming blocking


【解决方案1】:

你是:

  1. 假设read 填满了缓冲区,即使
  2. 您正在为write() 而不是写入整个缓冲区进行出色的辩护。

您需要执行 (1),并且您不需要执行 (2),因为您处于阻塞模式,并且 POSIX 确保 write() 在写入所有数据之前不会返回。

两个循环的简单版本:

while ((nread = read(inFD, buffer, 0, sizeof buffer)) > 0)
{
    write(outFD, buffer, 0, nread);
}
if (nread == -1)
    ; // error

更正确的版本当然会检查write() 的结果是否有错误。

【讨论】:

  • 在你的代码中,为什么 read 和 write 有 4 个参数而不是 3 个?错字?
  • @EJP - 这如何解决问题?在 OP 的代码示例中,他似乎使用读取的返回值 (nread) 作为写入循环中使用的字节数。他的代码似乎没有假设 read 返回一个完整的缓冲区。
  • @selbie 这并不能解决我的问题,我认为@EJP 只是想指出我不需要检查部分write,因为我使用的是阻塞套接字。
  • 我不确定 POSIX 是否确保 write() 在写入所有数据之前不会返回 是准确的。请参阅write如果 write() 请求写入的字节数超过了空间,则只应写入有空间的字节...如果 write() 在之后被信号中断它成功写入一些数据,它应该返回写入的字节数。
  • 我认为关于阻塞套接字上的写入/发送是否会返回仅指示发送的部分字节数的旧问题存在长期争论。 IIRC,除了错误,没有人见过send 在阻塞套接字上执行此操作。但没有人相信它是有保证的。 readrecv 肯定会一直返回部分数据。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多