I/O多路复用

来源:互联网 发布:mac用破解版软件风险 编辑:程序博客网 时间:2024/05/16 05:28

select,poll,epoll都是I/O多路复用的机制。

Linux man page对select的描述是:
select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become “ready” for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., read(2)) without blocking.

使用select函数的过程一般是:
先调用宏FD_ZERO将指定的fd_set清零,然后调用宏FD_SET将需要测试的fd加入fd_set,接着调用函数select测试fd_set中的所有fd,select会把未就绪的fd清零,所以最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1就行了。

可以看到,select只会告诉你有fd就绪,你需要自己去遍历找到是哪个,调用select之前还要用FD_SET把每一个fd对应的位置位,还要自己维护maxfdp和fd总的数目。比较繁琐。

select函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。原型如下:

#include <sys/select.h>#include <sys/time.h>int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

返回就绪描述符的数目,超时返回0,出错返回-1。
函数参数介绍如下:
(1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2…maxfdp1-1均将被测试。
(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:

void FD_ZERO(fd_set *fdset);           //清空集合void FD_SET(int fd, fd_set *fdset);   //将一个给定的文件描述符加入集合之中void FD_CLR(int fd, fd_set *fdset);   //将一个给定的文件描述符从集合中删除int FD_ISSET(int fd, fd_set *fdset);   // 检查集合中指定的文件描述符是否可以读写 

(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。

struct timeval{    long tv_sec;   //seconds、    long tv_usec;  //microseconds};

这个参数有三种可能:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。

fd_set可以看成是一个具有1024个比特的数组,所以最多可以监控1024个fd。

原理图:
select

示例:
Server端示例见参考。
Client示例没必要用select,

#include <netinet/in.h>#include <sys/socket.h>#include <stdio.h>#include <string.h>#include <stdlib.h>#include <sys/select.h>#include <time.h>#include <unistd.h>#include <sys/types.h>#include <errno.h>#define MAXLINE 1024#define IPADDRESS "127.0.0.1"#define SERV_PORT 8787static void handle_recv_msg(int sockfd, char *buf){    printf("client recv msg is:%s\n", buf);    sleep(5);    write(sockfd, buf, strlen(buf) +1);}static void handle_connection(int sockfd){    char recvline[MAXLINE];    int n;    while (1)    {        n = recv(sockfd, recvline, MAXLINE, 0);        if (n <= 0)        {            fprintf(stderr,"client: server is closed.\n");            close(sockfd);            return;        }        handle_recv_msg(sockfd, recvline);    }}int main(int argc,char *argv[]){    int sockfd;    struct sockaddr_in servaddr;    sockfd = socket(AF_INET,SOCK_STREAM,0);    bzero(&servaddr,sizeof(servaddr));    servaddr.sin_family = AF_INET;    servaddr.sin_port = htons(SERV_PORT);    inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);    int retval = 0;    retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));    if (retval < 0)    {        fprintf(stderr, "connect fail,error:%s\n", strerror(errno));        return -1;    }    printf("client send to server .\n");    write(sockfd, "hello server", 32);    handle_connection(sockfd);    return 0;}

参考
IO多路复用之select总结
select(2) - Linux man page

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
性能没有大的提升。

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

参考:
select、poll、epoll之间的区别总结[整理]

/**fd_set相关操作的定义等价于以下,但实际可能更高效, 比如centos下就使用了汇编代码,源码见于/usr/include/sys/select.h和/usr/include/bits/select.h*/#define __FD_SETSIZE 1024typedef struct{    long fds_bits[__FD_SETSIZE / 32];} fd_set;# define __FDS_BITS(set) ((set)->fds_bits)#define __FD_ELT(d) ((d) / 32)#define __FD_MASK(d) ((long) 1 << ((d) % 32))// 下面的宏, d的类型为int, set的类型为fd_set*#define FD_ZERO(set) \((void) (memset(set,0,sizeof(fd_set))))#define FD_SET(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))#define FD_CLR(d, set) \((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))#define FD_ISSET(d, set) \((__FDS_BITS (set)[__FD_ELT (d)] & __FD_MASK (d)) != 0)
0 0
原创粉丝点击