您的位置:首页 > 理论基础 > 计算机网络

浅谈TCP/IP网络编程中socket的行为

2018-01-23 23:01 253 查看
点击打开链接 socket
我认为,想要熟练掌握Linux下的TCP/IP网络编程,至少有三个层面的知识需要熟悉:

 1. TCP/IP协议(如连接的建立和终止、重传和确认、滑动窗口和拥塞控制等等)2. Socket I/O系统调用(重点如read/write),这是TCP/IP协议在应用层表现出来的行为。3. 编写Performant, Scalable的服务器程序。包括多线程、IO Multiplexing、非阻塞、异步等各种技术。

一. read/write的语义:为什么会阻塞?

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
首先,write成功返回,只是buf中的数据被复制到了kernel中的TCP发送缓冲区。至于数据什么时候被发往网络,什么时候被对方主机接收,什么时候被对方进程读取,系统调用层面不会给予任何保证和通知。write在什么情况下会阻塞?当kernel的该socket的发送缓冲区已满时。对于每个socket,拥有自己的send buffer和receive buffer。从Linux 2.6开始,两个缓冲区大小都由系统来自动调节(autotuning),但一般在default和max之间浮动。

已经发送到网络的数据依然需要暂存在send buffer中,只有收到对方的ack后,kernel才从buffer中清除这一部分数据,为后续发送数据腾出空间。接收端将收到的数据暂存在receive buffer中,自动进行确认。但如果socket所在的进程不及时将数据从receive buffer中取出,最终导致receive buffer填满,由于TCP的滑动窗口和拥塞控制,接收端会阻止发送端向其发送数据。这些控制皆发生在TCP/IP栈中,对应用程序是透明的,应用程序继续发送数据,最终导致send buffer填满,write调用阻塞。一般来说,由于接收端进程从socket读数据的速度跟不上发送端进程向socket写数据的速度,最终导致发送端write调用阻塞。而read调用的行为相对容易理解,从socket的receive buffer中拷贝数据到应用程序的buffer中。read调用阻塞,通常是发送端的数据没有到达。

二. blocking(默认)和nonblock模式下read/write行为的区别:将socket fd设置为nonblock(非阻塞)是在服务器编程中常见的做法,采用blocking IO并为每一个client创建一个线程的模式开销巨大且可扩展性不佳(带来大量的切换开销),更为通用的做法是采用线程池+Nonblock I/O+Multiplexing(select/poll,以及Linux上特有的epoll)。几个重要的结论:1. read总是在接收缓冲区有数据时立即返回,而不是等到给定的read buffer填满时返回。只有当receive buffer为空时,blocking模式才会等待,而nonblock模式下会立即返回-1(errno = EAGAIN或EWOULDBLOCK)2. blocking的write只有在缓冲区足以放下整个buffer时才返回(与blocking read并不相同)nonblock write则是返回能够放下的字节数,之后调用则返回-1(errno = EAGAIN或EWOULDBLOCK) 对于blocking的write有个特例:当write正阻塞等待时对面关闭了socket,则write则会立即将剩余缓冲区填满并返回所写的字节数,再次调用则write失败(connection reset by peer),这正是下个小节要提到的:

三. read/write对连接异常的反馈行为:对应用程序来说,与另一进程的TCP通信其实是完全异步的过程:1. 我并不知道对面什么时候、能否收到我的数据2. 我不知道什么时候能够收到对面的数据3. 我不知道什么时候通信结束(主动退出或是异常退出、机器故障、网络故障等等)对于1和2,采用write() -> read() -> write() -> read() ->...的序列,通过blocking read或者nonblock read+轮询的方式,应用程序基于可以保证正确的处理流程。对于3,kernel将这些事件的“通知”通过read/write的结果返回给应用层四. 还需要做什么?至此,我们知道了仅仅通过read/write来检测异常情况是不靠谱的,还需要一些额外的工作:1. 使用TCP的KEEPALIVE功能?2. 进行应用层的心跳严格的网络程序中,应用层的心跳协议是必不可少的。虽然比TCP自带的keep alive要麻烦不少(怎样正确地实现应用层的心跳,我或许会用一篇专门的文章来谈一谈),但有其最大的优点:可控。当然,也可以简单一点,针对连接做timeout,关闭一段时间没有通信的”空闲“连接。这里可以参考一篇文章:Muduo 网络编程示例之八:Timing wheel 踢掉空闲连接 by 陈硕
点击打开链接 陈硕
尊重原创,文章出处已给!!!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: