select()linux linux打开软件命令

Linux内核中select,poll和epoll的区别

在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select、poll

和epoll是Linux API提供的I/O复用方式,自从Linux 2.6中加入了epoll之后,在高性能服务器领域得到广泛的

应用,现在比较出名的nginx就是使用epoll来实现I/O复用支持高并发,目前在高并发的场景下,nginx越来越

收到欢迎。

select:

下面是select的函数接口:

[cpp] view plain copy

int select(int n, fd_set*readfds, fd_set*writefds, fd_set*exceptfds, struct timeval*timeout);

select函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直

到有描述副就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为

null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一个缺点在于单个进程

能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的

方式提升这一限制,但是这样也会造成效率的降低。

poll:

[cpp] view plain copy

int poll(struct pollfd*fds, unsigned int nfds, int timeout);

不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。

[cpp] view plain copy

struct pollfd{

int fd;/* file descriptor*/

short events;/* requested events to watch*/

short revents;/* returned events witnessed*/

};

pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有

最大数量限制(但是数量过大后性能也是会下降)。和select函数一样,poll返回后,需要轮询pollfd来获取

就绪的描述符。

从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的

大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll:

epoll的接口如下:

[cpp] view plain copy

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event*event);

typedef union epoll_data{

void*ptr;

int fd;

__uint32_t u32;

__uint64_t u64;

} epoll_data_t;

struct epoll_event{

__uint32_t events;/* Epoll events*/

epoll_data_t data;/* User data variable*/

};

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

主要是epoll_create,epoll_ctl和epoll_wait三个函数。epoll_create函数创建epoll文件描述符,参数size并

'不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。返回是epoll描

述符。-1表示创建失败。epoll_ctl控制对指定描述符fd执行op操作,event是与fd关联的监听事件。op操作

有三种:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和

修改对fd的监听事件。epoll_wait等待epfd上的io事件,最多返回maxevents个事件。

在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通

过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,

迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

epoll的优点主要是一下几个方面:

1.监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,

举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat/proc/sys/fs/file-max察看,一般来说这个

数目和系统内存关系很大。select的最大缺点就是进程打开的fd是有数量限制的。这对于连接数量比较大的

服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面

创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是

一种完美的方案。

2. IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的

回调函数来实现的。只有就绪的fd才会执行回调函数。

3.支持电平触发和边沿触发(只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取

行动,那么它将不会再次告知,这种方式称为边缘触发)两种方式,理论上边缘触发的性能要更高一些,但是

代码实现相当复杂。

4.mmap加速内核与用户空间的信息传递。epoll是通过内核于用户空间mmap同一块内存,避免了无畏的内存拷贝。

windows select模型 和linux的区别

windows select模型

在widows下提供了众多非阻塞的I/O模型,如select、WSAAsyncSelect、WSAEventSelect、overlapped、completion port,其中IO completion port(IOCP)提供了较好的伸缩性,在windows应用比较广泛

说明:而select模型主要是解决在单一线程模式下只能处理一个套接字的问题,这样可以避免线程膨胀问题,但是。。。下面看完原型再讲起不足之处

select模型:winsock库主要有两个版本,这里主要以winsock2版本为说明,select模型

int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,const struct timeval* timeout);

其实主要是两者采用的标准一致,所以接口基本跟linux一致,不过nfds在windows其实是没有意义的,主要是为了兼容其他版本

处理过程:假设以read为例,在这里windows主要是先将套接字s添加到readfds集合中,然后等待select函数返回,在select函数里面会移除没有未决的

I/O操作的套接字句柄,即已经处理过的IO套接字句柄,然后看s是否认仍然还是readfs集合中,在就说明s可读了,但是这里面可读,不一定有数据

有几种情况都会引发:数据可读、连接关闭/重启/中断、监听套接字被调用,此时还有连接未决,accept函数接受新的套接字成功

不足:其实添加到fd_set套接字数量是有限制的,winsock2.h定义的64,自定义也不超过1024,因为值太大,会对服务器的性能有影响,更高的就是可伸缩的IOCP

linux select模型

其实原理跟windows是差不多的,只是处理过程在底层上有点区别

模型:int select(int maxfd,fd_set*readfds,fd_set* writefds,fd_set*exceptfds,const struct timeval*timeout)

这里主要是maxfd,文件描述符的范围,比待检测的最大文件描述符大1

处理过程:也是先将监控的文件添加到文件描述符集合中,调用select监控,判断文件是否发生变化,但是在底层调用的确是poll方法;首先使用poll_wait将等待队列添加到poll_table中,返回描述符的掩码

poll原型:unsigned int(*poll)(struct file*filp,poll_wait* wait)

看如下一个简单的处理过程

unsigned int mem_poll(struct file*filp, poll_table*wait)

{

struct mem_dev*dev= filp->private_data;

unsigned int mask= 0;

/*将等待队列添加到poll_table*/

poll_wait(filp,&dev->inq, wait);

if(have_data)

mask|= POLLIN| POLLRDNORM;/* readable*/

return mask;

}

在这里只是添加队列,返回可读可写的掩码,真正阻塞的不是这里,是在do_select(...)函数中,在linux内核fs/select.c里面

int do_select(int n, fd_set_bits*fds, struct timespec*end_time)这个函数

if(file){

f_op= file->f_op;

mask= DEFAULT_POLLMASK;

if(f_op&& f_op->poll){

wait_key_set(wait, in, out, bit);

mask=(*f_op->poll)(file, wait);

}

这里面是先判断文件存在,然后读取你自己定义的操作设备I/O的f_op函数,这里有一个默认的mask,接着才判断然后返回描述符mask=(*f_op->poll)(file, wait);用于区分当前哪个集合被触发了;接着判断f_op&f_op->poll在这里我们默认定义了poll函数,所以这里会进入此判断语句,mask=(*f_op->poll)(file, wait);这个就是调用默认的poll函数进行处理,关键的是如何区分不同的读、写、异常过程?

(mask& POLLIN_SET)&&(in& bit),这里面就是对当前的可读、写、异常的&&过程,就是为了判断和区分当前的套接字只是某一个具体的fd_set集合下;当然某一个套接字也可能同时在可读可写里面,这时候两个会进行判断。

if(retval|| timed_out|| signal_pending(current))

break;

上面的retval如果为0,且其他也不满足就会导致空循环状态,就处于阻塞状态了

推荐你看看linux就该这么学,地址3w(dot)linuxprobe(dot)com,good luck

c语言中select函数的作用

在编程的过程中,经常会遇到许多阻塞的函数,好像read和网络编程时使用的recv, recvfrom函数都是阻塞的函数,当函数不能成功执行的时候,程序就会一直阻塞在这里,无法执行下面的代码。这是就需要用到非阻塞的编程方式,使用selcet函数就可以实现非阻塞编程。

selcet函数是一个轮循函数,即当循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续往下执行。

Select的函数格式:

int select(int maxfdp,fd_set*readfds,fd_set*writefds,fd_set*errorfds,struct timeval*timeout);

select函数有5个参数

第一个是所有文件节点的最大值加1,如果我有三个文件节点1、4、6,那第一个参数就为7(6+1)

第二个是可读文件节点集,类型为fd_set。通过FD_ZERO(&readfd);初始化节点集;然后通过FD_SET(fd,&readfd);把需要监听是否可读的节点加入节点集

第三个是可写文件节点集中,类型为fd_set。操作方法和第二个参数一样。

第四个参数是检查节点错误集。

第五个参数是超时参数,类型为struct timeval,然后可以设置超时时间,分别可设置秒timeout.tv_sec和微秒timeout.tv_usec。

然后调用select函数,用FD_ISSET()函数判断节点是否可读写。返回值不为0表示可读写,为0表示不可读写。select函数的返回值为是一个整数,表示有几个节点可读写。

先说明两个结构体:

第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set*),将一个给定的文件描述符加入集合之中FD_SET(int,fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int,fd_set*)。

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

具体解释select的参数:

int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

fd_set* readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

fd_set* writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

fd_set* errorfds同上面两个参数的意图,用来监视文件错误异常。

struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

返回值:返回状态发生变化的描述符总数。

负值:select错误

正值:某些文件可读写或出错

0:等待超时,没有可读写或错误的文件

阅读剩余
THE END