SELECT
1.函数原型
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
函数功能
当readfds或writefds中映象的文件可读或可写或超时,本次select()
就结束返回。程序员利用一组系统提供的宏在select()结束时便可判
断哪一文件可读或可写。
宏:
FD_ZERO(fd_set *fds); 清空fds与所有文件句柄的联系
FD_ISSET(int fd, fd_set &read_fds); 检查fds联系的文件句柄fd是否
可读写,当>0表示可读写。
FD_SET(int fd, fd_set &); 建立文件句柄fd与fds的联系。
参数解释
- nfd:指定被监听文件描述符总数
- readfds,writefds,exceptfds:可读可写和异常等事件对应的文件描述符集合。
- timeout:设置select函数的超时时间。
- 主要的宏:
FD_ZERO(fd_set *fds); 清空fds与所有文件句柄的联系
FD_ISSET(int fd, fd_set &read_fds); 检查fds联系的文件句柄fd是否
可读写,当>0表示可读写。
FD_SET(int fd, fd_set &); 建立文件句柄fd与fds的联系。
使用实例
char buf[1024];
fd_set read_fds;
fd_set exception_fds;
FD_ZERO( &read_fds );
FD_ZERO( &exception );
while(1)
{
memset(buf, '\0', sizeof(buf));
FD_SET(connfd, &read_fds); //将connfd加入read_fds集合
FD_SET(connfd, &exception_fds);
ret = select(connfd+1, &read_fds, NULL, &exception_fd, NULL);
assert(ret != -1);
if(FD_ISSET(connfd, &read_fds))
{
ret = recv(connfd, buf, sizeof(buf)-1, 0);
if(ret < 0)
{
break;
}
printf("get %d bytes of normal data:%s\n",ret ,buf);
}
else if(FD_ISSET(connfd, &exception))
{
ret = recv(connfd, buf, sizeof(buf)-1, MSG_OOB);
if(ret <= 0)
{
break;
}
printf("get %d bytes of obb data: %s\n", ret, buf);
}
}
close(connfd);
close(sock);
POLL & EPOLL
函数原型
* int poll(struct pollfd *fds, nfds_t nfds, int timeout);
* int epoll_create(int size);
* int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
* int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
函数功能
poll 函数与select 类似,将保存文件描述符上发生的事件的数组轮询,以测试是否有就绪者。
epoll_create 函数创建一个文件描述符,唯一的标识内核中的一个可读事件表,把用户关心的文件描述符都写进去。epoll_wait 函数与其他不同,将所有就绪的事件从内核事件表(epfd)中复制到第二个参数指定的数组(events)中。
区别:
- 调用poll时返回就绪事件的个数,只用来判断是否有事件发生,如果有,再去数组中逐一通过POLLIN判断是否是发生事件的文件描述符。
- 调用epoll_wait时不仅返回就绪事件个数,同时将就绪事件统统复制到一个数组中。然后通过返回值和数组来得到文件描述符。
ET模式和LT模式
概念
LT:电平触发模式,当epoll_wait检测到其上有事件发生时不立即处理该事件,当应用程序下次调用epoll_wait时再次向应用程序通知该事件。
ET:检测到事件发生时立即处理,后面不提醒。
代码实现中,LT模式当传输字节超过缓存区大小时,多次触发多次读取。
ET模式当传输字节超过时,触发一次,使用while循环多次读取。
EPOLLONESHOT事件
有时候可能出现这种情况,一个线程处理读取socket上的数据后开始处理这些数据,在数据处理过程重socket又可读,此时为了避免另一个线程处理此socket,就需要对该socket注册EPOLLONESHOT事件。此时,操作系统作多触发其上注册的一个可读,可写,可执行事件。但在处理完后,必须重置EPOLLONESHOT事件保证下一次正常触发。
将套接字设置为非阻塞
int setnonblocking( int fd )
{
int old_option = fcntl( fd, F_GETEL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETEL, new_option );
return old_option;
}
EPOLL工作原理:
epoll只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,一旦某个文件描述符就绪时,内核会采用回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
然后select()和poll()函数都是采用不断轮询所有文件描述符的方式判断有没有事件发生。