SO_LINGER页面,或者:为什么我的TCP不可靠(2009)

2021-06-13 00:43:52

编辑这篇文章是关于TCP网络编程的一个晦涩角落,一个角落在哪里几乎每个人都没有完全得到正在发生的事情。我曾经认为Iderstood它,但上周发现我没有。

所以我决定拖网网络并咨询专家,承诺他们曾经一劳永逸地送去了智慧,希望这个主题能够越来越努力。

专家(H. Willstrand,Evgeniy Polyakov,Bill Fink,Ilpo Jarvinen和Herbert Xu)回应了,这是我的写作。

即使我引用了Linux TCP实现的很多,也不是Linux特定于Linux的,并且可以在任何操作系统上发生。

有时,我们必须从一个地点发送未知数量的数据。 TCP,可靠的传输控制协议,声音就像我们需要的东西。来自Linux TCP(7)人类:

“TCP在IP(7)顶部的两个套接字之间提供了可靠,流定向的全双工连接,适用于V4和V6版本。 TCP保证数据以顺序到达并重新发送丢失的数据包。它生成并检查每个数据包校验和以捕获传输错误。“

但是,当我们天真地使用TCP来发送我们需要传输的数据时,它通常无法执行我们想要的 - 使用最终千字节或有时传输从未到达的数据。

假设我们在两个POSIX兼容操作系统上运行以下两个程序,目的是从程序发送100万字节的TOPORAGAM B(可以在此处找到程序):

sock = socket(af_inet,sock_stream,0); Connect(袜子,&遥控器,尺寸(远程));写(袜子,缓冲,1000000); //返回1000000关闭(袜子);

int sock = socket(af_inet,sock_stream,0);绑定(袜子,& local,sizeof(本地));听(袜子,128); int client =接受(袜子,& local,locallen);写(客户," 220欢迎\ r \ n" 13); int bytesread = 0,Res; for(;;){res = read(客户端,缓冲区,4096); if(res< 0){perror("阅读");出口(1); }如果(!res)休息; bytesread + = res; printf("%d \ n" bytesread);

a)1000000b)少于1000000c)它将退出报告错误)可能是上述任何一个

悲伤地答案,是'd'。但这可能怎么发生?课程以至于所有数据已被正确发送!

通过TCP套接字发送数据真的不提供与写入普通文件的写作相同的“它命中”语义(如果您记得callfsync())。

事实上,在TCP世界中的所有成功写作()是指Kernelhas接受了您的数据,现在将尝试在自己的甜蜜时间内传输它。即使内核感受到携带数据的数据包,实际上,它们只会被移到网络适配器,这甚至可能在感觉时发送数据包。

从那时起,数据将遍历许多这样的适配器和队列网络,直到它到达远程主机。内核将在接收到收到的数据,如果拥有套接字正处于关注并尝试从中读取的进程,则数据将到达应用程序,并且在文件系统中,“命中磁盘”。

请注意,已发送的确认仅表示内核显示数据--IT并不意味着应用程序所做的!

好的,我得到了所有的,但为什么所有数据都不在上面的例子?

当我们在TCP / IP套接字上发出Close()时,根据情况,内核可以完全做到:关闭套接字,并使用它与其一起使用的IT QUC / IP连接。

这实际上发生了 - 尽管你的一些数据仍然被发送或被发送但未被发送,但未确认:内核可以整个连接。

此问题导致邮件列表中的大量帖子,Usenet和索拉,以及这些帖子在SO_LINGER套接字选项上全部快速零,请考虑到以下问题,以何种问题写入:

“启用时,关闭(2)或关闭(2),直到已成功发送套接字的所有排队消息或已达到Linger超时。否则,呼叫立即返回,结束在后台完成。当套接字作为出口(2)的一部分关闭时,它总是在后台徘徊。“

所以,我们设置了这个选项,重新运行我们的计划。它仍然不起作用,才批准我们百万字节到达。

事实证明,在这种情况下,RFC 1122的第4.2.2.13节告诉我们,带有任何挂起可读数据的ACLOSE()可能会导致发送立即复位。

“主机可以实现”半双工“TCP关闭序列,以便称为关闭的应用程序无法继续读取来自连接的数据。如果此类主机在接收数据仍处于TCP中仍处于待处理的时关闭呼叫,或者在关闭后收到新数据时,其TCP应发送RST以显示数据丢失。“

在我们的情况下,我们有这样的数据挂明:“220欢迎\ r \ n”在程序b中Welansmited,但在程序A中从未阅读过!

如果程序B未发送该行,则最有可能所有Ourdata都能正确到达。

并不真地。 close()调用真的没有传达我们尝试的东西内核:请通过write()发送所有数据后关闭连接。

幸运的是,系统调用shutdown()可用,这介绍了kernelexactly。但是,它一个人还不够。当shutdown()返回时,WISTILL没有迹象表明程序B的所有内容都收到。

然而,我们可以做的是发出Shutdown(),这将导致FinPacket被发送到程序B.程序B依次关闭ITSocket,我们可以从程序A中检测到这一点:后续读取()读取()reclorreturn 0。

sock = socket(af_inet,sock_stream,0); Connect(袜子,&遥控器,尺寸(远程));写(袜子,缓冲,1000000); //返回1000000关闭(袜子,shut_wr); for(;;){res = read(袜子,缓冲区,4000); if(res< 0){perror("阅读");出口(1); }如果(!res)休息;关闭(袜子);

嗯..如果我们查看HTTP协议,通常在HTTP响应的开始时通常将数据发送为HTTP响应的开始信息(所谓的'块状'模式)。

他们这样做是有原因的。只有这样,才能接收结束才能收到它发送的所有信息。

使用上面的Shutdown()技术真的只告诉我们RemoteClased连接。它实际上并没有保证所有数据通过程序B正确地进行了正确。

最好的建议是发送长度信息,并主动承认收到所有数据。

如果您需要将流数据传输到“墙壁中的”愚蠢的TCP / IP孔“,因为我必须做多次,可能无法遵循上面的SageDvice关于发送长度信息并获得确认。

在这种情况下,可能不足以接受插座的封闭侧的关闭,以表明一切都到达。

幸运的是,Linux会跟踪无法使用Siocoutq IOCTL()查询的未致查询的数量。一旦我们看到Thingnumber命中0,我们就可以合理地确定我们的数据至少达到了传动系统。

与上述关闭()技术不同,SIOCOUTQ出现在BELINUX特定。欢迎其他操作系统的更新。

只要你没有未读的待定数据,星星和月亮都是对齐的,你的操作系统就是一定的版本,你可能会毫不掩战,由上面的故事毫不掩抗,事情会经常“justwork”。但不要指望它。

通信量已经致力于SO_Lingerversus非阻塞(O_NONBLOCK)插座的复杂性。从我可以说的那样,总结遍及:不要这样做。依赖shutdown() - 随后读取() - Eoftechnique。使用适当的调用来调用/ epoll / select(),当然。

还应注意,Linux系统调用sendfile()和ispice()在介于之间的位置命中 - 这些通常设法即使您在返回后立即调用close()即使您立即发送的文件内容。

这与拼接()(在哪个sendfile()是基于sendfile()的事实有关,只能安全地返回所有数据包以来,它自Itzero副本以来击中TCP堆栈,如果修改a,则不能很好地改变其行为呼叫返回后的文件!

请注意,函数不等待,直到确认所有数据,它只等待它直到已发送。