从socket中读取数据可以使用如下的代码:
while( (n = read(socketfd, buf, BUFSIZE) ) >0)
if( write(STDOUT_FILENO, buf, n) = n)
{
printf(“write error”);
exit(1);
}
当代码中的socketfd描述符所对应的文件表项是处于阻塞时,它会一直阻塞,直到有数据从网络的另一端发送过来。如果它是一个服务器程序,它要读写大量的socket,那么在某一个socket上的阻塞很明显会影响与其它socket的交互过程。类似的问题不单单出现在网络上,还可以出现在读写加锁的文件和FIFO等等一系列的情况。
一种比较好的解决方法似乎是采用非阻塞IO来实现。把所要读取数据的socketfd设置为非阻塞状态,依次用read函数检查是否有数据到来,如有,它会返回接到数据的个数,否则它会返回-1以表示当前还没有数据到达。这样,对于每个socket,如有数据到来则读取,没有也会马上返回。这就是非阻塞IO的好处拉。部分代码如下:
//clientfd[] 为客户端的socket描述符组数,假设数组的大小为MAX,并且所有客户端socket描述符都设置为非阻塞状态时。
for(i = 0; i < MAX; ++i)
{
int n;
if( (n = read(clientfd[i], buf, SZIE)) >0)
{
//send response to client in here.
}
}
这里代码看起来与上面的代码没有太大的区别,其实是有很大的区别;区别就是使使用了非阻塞IO进行整个交互过程,使得各个客户端都得到相对平等的时间待遇。这种模式我们通常称为这“轮询”模式。轮询模式同样有它的不足之处,在执行read函数时,实际上大部分时间还是没有数据可读的,但仍不断地执行read,浪费了很多CPU时间。
实际,对于上述的问题,一种比较好的技术就是I/O多路转接(I/O multiplexing)。它可谓是上面两种方法的接衷:先构造一张有关描述符的数据表,然后调用一个函数,仅当有一个或多个描述符已准备可以进行IO操作时才返回,否则一直阻塞。在返回时,它会告诉进程那些描述符已准备好可以进行IO。
select函数
#include <sys/select.h>
int select(int maxfdp1,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *tvptr);
//返回准备就绪的描述符数,若超时则返回0,若出错则返回-1
?怎么知道哪些描述符已经准备就绪
描述符编号从0开始,maxfdp1是要检查的描述符数(从描述符0开始),readfds、writefds、exceptfds是指向描述符集的指针,每个描述符集存放在一个fd_set数据类型中,这种数据类型为每一个可能的描述符保持了一位。tvptr指定愿意等待的时间:
struct timeval{
long tv_sec;//秒数
long tv_uses;//微秒
};
poll函数
#include <poll.h>
int poll(struct pollfd fdarray[],nfds_t nfds,int timeout);//返回准备就绪的描述符数,若超时返回0,出错返回-1
pollfd结构数组的每个数组元素指定一个描述符编号以及对其所关心的状态
struct pollfd{
int fd;
short events;
short revents;
};
效率与epoll效率基本相同
epoll模型的缺点:
1. 最大并发数限制,因为一个进程所打开的FD(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此Select模型的最大并发数就被相应限制了。自己改改这个FD_SETSIZE?想法虽好,可是先看看下面吧…
2. 效率问题,select每次调用都会线性扫描全部的FD集合,这样效率就会呈现线性下降,把FD_SETSIZE改大的后果就是,大家都慢慢来,什么?都超时了??!!
3. 内核/用户空间 内存拷贝问题,如何让内核把FD消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。
参考:
http://blog.csdn.net/linyt/article/details/1722445
http://blog.csdn.net/sparkliang/article/details/4770655
【Nginx】I/O多路转接之select、poll、epoll