ET/LThtml
在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)
从字面上看, 意思是:EAGAIN: 再试一次,EWOULDBLOCK: 若是这是一个阻塞socket, 操做将被block,perror输出: Resource temporarily unavailablejava
总结:
这个错误表示资源暂时不够,能read时,读缓冲区没有数据,或者write时,写缓冲区满了。遇到这种状况,若是是阻塞socket,read/write就要阻塞掉。而若是是非阻塞socket,read/write当即返回-1, 同时errno设置为EAGAIN。
因此,对于阻塞socket,read/write返回-1表明网络出错了。但对于非阻塞socket,read/write返回-1不必定网络真的出错了。多是Resource temporarily unavailable。这时你应该再试,直到Resource available。linux
综上,对于non-blocking的socket,正确的读写操做为:
读:忽略掉errno = EAGAIN的错误,下次继续读
写:忽略掉errno = EAGAIN的错误,下次继续写程序员
对于select和epoll的LT模式,这种读写方式是没有问题的。但对于epoll的ET模式,这种方式还有漏洞。web
epoll的两种模式LT和ET
两者的差别在于level-trigger模式下只要某个socket处于readable/writable状态,不管何时进行epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。面试
因此,在epoll的ET模式下,正确的读写方式为:
读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN
写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN算法
正确的读apache
n = 0; |
while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) { |
n += nread; |
} |
if (nread == -1 && errno != EAGAIN) { |
perror("read error"); |
} |
正确的写编程
int nwrite, data_size = strlen(buf); |
n = data_size; |
while (n > 0) { |
nwrite = write(fd, buf + data_size - n, n); |
if (nwrite < n) { |
if (nwrite == -1 && errno != EAGAIN) { |
perror("write error"); |
} |
break; |
} |
n -= nwrite; |
} |
正确的accept,accept 要考虑 2 个问题
(1) 阻塞模式 accept 存在的问题
考虑这种状况:TCP链接被客户端夭折,即在服务器调用accept以前,客户端主动发送RST终止链接,致使刚刚创建的链接从就绪队列中移出,若是套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其余某个客户创建一个新的链接为止。可是在此期间,服务器单纯地阻塞在accept调用上,就绪队列中的其余描述符都得不处处理。缓存
解决办法是把监听套接口设置为非阻塞,当客户在服务器调用accept以前停止某个链接时,accept调用能够当即返回-1,这时源自Berkeley的实现会在内核中处理该事件,并不会将该事件通知给epool,而其余实现把errno设置为ECONNABORTED或者EPROTO错误,咱们应该忽略这两个错误。
(2)ET模式下accept存在的问题
考虑这种状况:多个链接同时到达,服务器的TCP就绪队列瞬间积累多个就绪链接,因为是边缘触发模式,epoll只会通知一次,accept只处理一个链接,致使TCP就绪队列中剩下的链接都得不处处理。
解决办法是用while循环抱住accept调用,处理完TCP就绪队列中的全部链接后再退出循环。如何知道是否处理完就绪队列中的全部链接呢?accept返回-1而且errno设置为EAGAIN就表示全部链接都处理完。
综合以上两种状况,服务器应该使用非阻塞地accept,accept在ET模式下的正确使用方式为:
while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) { |
handle_client(conn_sock); |
} |
if (conn_sock == -1) { |
if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) |
perror("accept"); |
} |
一道腾讯后台开发的面试题
使用Linuxepoll模型,水平触发模式;当socket可写时,会不停的触发socket可写的事件,如何处理?
第一种最广泛的方式:
须要向socket写数据的时候才把socket加入epoll,等待可写事件。
接受到可写事件后,调用write或者send发送数据。
当全部数据都写完后,把socket移出epoll。
这种方式的缺点是,即便发送不多的数据,也要把socket加入epoll,写完后在移出epoll,有必定操做代价。
一种改进的方式:
开始不把socket加入epoll,须要向socket写数据的时候,直接调用write或者send发送数据。若是返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,所有数据发送完毕后,再移出epoll。
这种方式的优势是:数据很少的时候能够避免epoll的事件处理,提升效率。
它会显示例以下面的信息:
TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1
经常使用的三个状态是:ESTABLISHED 表示正在通讯,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。
若是服务器出了异常,百分之八九十都是下面两种状况:
1.服务器保持了大量TIME_WAIT状态
2.服务器保持了大量CLOSE_WAIT状态
由于linux分配给一个用户的文件句柄是有限的(能够参考:http://blog.csdn.net/shootyou/article/details/6579139),而TIME_WAIT和CLOSE_WAIT两种状态若是一直被保持,那么意味着对应数目的通道就一直被占着,并且是“占着茅坑不使劲”,一旦达到句柄数上限,新的请求就没法被处理了,接着就是大量Too Many Open Files异常,
1.服务器保持了大量TIME_WAIT状态
这种状况比较常见,一些爬虫服务器或者WEB服务器(若是网管在安装的时候没有作内核参数优化的话)上常常会遇到这个问题,这个问题是怎么产生的呢?
从 上面的示意图能够看得出来,TIME_WAIT是主动关闭链接的一方保持的状态,对于爬虫服务器来讲他自己就是“客户端”,在完成一个爬取任务以后,他就 会发起主动关闭链接,从而进入TIME_WAIT的状态,而后在保持这个状态2MSL(max segment lifetime)时间以后,完全关闭回收资源。为何要这么作?明明就已经主动关闭链接了为啥还要保持资源一段时间呢?这个是TCP/IP的设计者规定 的,主要出于如下两个方面的考虑:
1.防止上一次链接中的包,迷路后从新出现,影响新链接(通过2MSL,上一次链接中全部的重复包都会消失)
2. 可靠的关闭TCP链接。在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会从新发fin, 若是这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。因此主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短期内接受大量请求或者受到攻击。
关于MSL引用下面一段话:
再引用网络资源的一段话:
五、RST出现缘由
TCP异常终止的常见情形
咱们在实际的工做环境中,致使某一方发送reset报文的情形主要有如下几种:
1,客户端尝试与服务器未对外提供服务的端口创建TCP链接,服务器将会直接向客户端发送reset报文。
2,客户端和服务器的某一方在交互的过程当中发生异常(如程序崩溃等),该方系统将向对端发送TCP reset报文,告之对方释放相关的TCP链接,以下图所示:
3,接收端收到TCP报文,可是发现该TCP的报文,并不在其已创建的TCP链接列表内(好比server机器直接宕机),则其直接向对端发送reset报文,以下图所示:
TCP_NODelay
TCP_NODELAYTCP/IP协议中针对TCP默认开启了 Nagle算法。Nagle算法经过减小须要传输的数据包,来优化网络。关于Nagle算法,@ 郭无意 同窗的答案已经说了很多了。在内核实现中,数据包的发送和接受会先作缓存,分别对应于写缓存和读缓存。
If set, disable the Nagle algorithm. This means that segments are always sent as soon as possible, even if there is only a small amount of data. When not set, data is buffered until there is a sufficient amount to send out, thereby avoiding the frequent sending of small packets, which results in poor utilization of the network. This option is overridden by TCP_CORK; however, setting this option forces an explicit flush of pending output, even if TCP_CORK is currently set.
if there is new data to send
if the window size >= MSS and available data is >= MSS send complete MSS segment now else if there is unconfirmed data still in the pipe enqueue data in the buffer until an acknowledge is received else send data immediately end if end if end if
The user-level solution is to avoid write-write-read sequences on sockets. write-read-write-read is fine. write-write-write is fine. But write-write-read is a killer. So, if you can, buffer up your little writes to TCP and send them all at once. Using the standard UNIX I/O package and flushing write before each read usually works.
连续进行屡次对小数据包的写操做,而后进行读操做,自己就不是一个好的网络编程模式;在应用层就应该进行优化。对于既要求低延时,又有大量小数据传输,还同时想提升网络利用率的应用,大概只能用UDP本身在应用层来实现可靠性保证了。好像企鹅家就是这么干的。