@HUST张友东 work@taobao zyd_com@126.com
分类: LINUX
2011-11-15 22:45:16
循环read(write)
在网络应用程序中,通常需要重复的调用read/write来读取到指定数量的数据,如下例:
int wrapper_read(int fd, char *buf, int size)
{
int ridx = 0;
int rlen = 0;
while(ridx <
size) {
rlen = read(fd, buf + ridx, size
– ridx);
if(rlen < 0)
{
return rlen;
}
ridx += rlen;
}
return ridx;
}
首先了解下read的返回值:
1. ret > 0,表示读到ret字节的数据;
2. ret = 0,表示对端关闭连接(调用了close)。
3. ret = -1,出错,errno被设置。
为什么在读取磁盘文件时即使请求很大数量的数据,也只需要调用一次read,而读取网络数据时需要重复读写呢?这是由磁盘、网络的特性的决定的。
读取磁盘文件,数据都在本地,只需要定位到读取位置,便能读到需求的数据(尽量满足需求,如果read返回值小于指定的大小,则说明数据不足,如已经读到文件末尾);但网络上的数据都是由socket的对端发送过来的,什么时候到,一次到多少都是不确定的,如果没有数据到达socket缓冲区read便会阻塞(默认为阻塞模式,可通过fcntl设置为nonblock模式),当有数据时,read就会读取并返回,此时的数据可能并不完整,还有部分仍在传输中,故要想获取到指定量的数据,则需不断的调用read,直到读到需要的所有数据。
EPOLL LT vs ET
epoll有两种模式,水平触发(LT)和边沿触发(ET),其主要区别是,对事件触发的定义不同,以EPOLLIN事件来说,在LT模式下,只要缓冲区有数据可读,就认为事件EPOLLIN事件发生;而ET模式下,只有在缓冲区由无数据转可读转换成有数据可读时(类比电平的0-1跳转),才认为EPOLLIN事件发生。
如某一个连接的缓冲区最开始是空的,接着收到2K数据,在ET和LT模式下,epoll调用时会告知该连接上EPOLLIN事件发生,之后缓冲区的数据被读取了1K,则在LT模式下,epoll再次调用时,仍会触发EPOLLIN事件(只要缓冲区有数据可读),而在ET模式下,则不会触发(不满足从无数据变到有数据这个条件)。
在LT模式加上多线程的环境下,需要对epoll的事件做些特殊处理。如当某个事件(请求)到达时,服务器创建一个线程为其服务,而主线程继续调用epoll_wait,如果服务线程不能及时消费掉缓冲区的数据,则该连接上的EPOLLIN事件会再次被触发;特别地,在最后对端close后,触发EPOLLIN,然后服务线程为其服务,如果在服务线程确认对端关闭连接并close前epoll_wait被再次调用,EPOLLIN将再次被触发,服务线程将再次被触发,如果前一个服务线程已将连接关闭,这是read会返回EBADF(此时文件描述符已失效)。为了简化处理逻辑并提供性能,服务器应让epoll处理尽可能少的文件描述符,在确定某个文件描述符需要关闭时就应主动将其从epoll事件中移除,并调用close,而不是等待read返回0时被动调用close。
在ET模式下,在读取连接上的数据时也需要做些特殊处理,首先需将socket设置为nonblock,然后read直到返回EAGAIN,即消费掉所有的数据(非阻塞模式下,无数据时会立即返回EAGAIN)。
ET和LT模式在内核中的实现原理是一样的,ET相比LT的优势在于减少了每次需要返回的 I/O句柄数量,在并发量极多的时候能加快epoll_wait 的处理; 但ET在处理请求时,因需要处理完所有的数据,通常会增加额外的处理逻辑及资源开销,总体收益如何只有经过实际测试方能确定。