【发布时间】: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 套接字之间发送未知数量的数据时有用的一些技巧。描述的技巧如下:
- 利用发送方的 SO_LINGER 选项。这将有助于在调用
close(2)或shutdown(2)时保持套接字打开,直到所有数据都已成功发送。 - 在接收器上,在实际接收循环之前要注意待处理的可读数据。这可能会导致立即发送重置。
- 利用
shutdown(2)向接收者发出发送者已完成发送数据的信号。 - 在实际发送文件之前,让接收者知道将要发送的文件的大小。
- 让接收者向发送者确认接收循环已经结束。这将有助于防止发送方过早关闭套接字。
阅读文章后,我升级了代码以实现技巧 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