范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

纯技术干货一文读懂selectpollepoll的用法

  select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。 但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的 ,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。一、select实现1.1.基本概念
  IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。
  IO多路复用适用如下场合: 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。 当一个客户同时处理多个接口时,而这种情况是可能的,但很少出现。 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
  与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。 1.2.select函数
  该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下: #include  #include   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均将被测试。
  因为文件描述符是从0开始的。
  (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 };
  更多LInux内核视频教程文档资料免费领取后台私信【内核】自行获取。
  这个参数有三种可能:
  (1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
  (2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
  (3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。 1.3.测试程序
  写一个TCP回射程序,程序的功能是:客户端向服务器发送信息,服务器接收并原样发送给客户端,客户端显示出接收到的信息。
  服务端程序如下:   1 #include    2 #include    3 #include    4 #include    5 #include    6 #include    7 #include    8 #include    9 #include   10 #include   11 #include   12 #include   13   14 #define IPADDR      "127.0.0.1"  15 #define PORT        8787  16 #define MAXLINE     1024  17 #define LISTENQ     5  18 #define SIZE        10  19   20 typedef struct server_context_st  21 {  22     int cli_cnt;        /*客户端个数*/  23     int clifds[SIZE];   /*客户端的个数*/  24     fd_set allfds;      /*句柄集合*/  25     int maxfd;          /*句柄最大值*/  26 } server_context_st;  27 static server_context_st *s_srv_ctx = NULL;  28 /*===========================================================================  29  * ==========================================================================*/  30 static int create_server_proc(const char* ip,int port)  31 {  32     int  fd;  33     struct sockaddr_in servaddr;  34     fd = socket(AF_INET, SOCK_STREAM,0);  35     if (fd == -1) {  36         fprintf(stderr, "create socket fail,erron:%d,reason:%s ",  37                 errno, strerror(errno));  38         return -1;  39     }  40   41     /*一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/  42     int reuse = 1;  43     if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {  44         return -1;  45     }  46   47     bzero(&servaddr,sizeof(servaddr));  48     servaddr.sin_family = AF_INET;  49     inet_pton(AF_INET,ip,&servaddr.sin_addr);  50     servaddr.sin_port = htons(port);  51   52     if (bind(fd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) {  53         perror("bind error: ");  54         return -1;  55     }  56   57     listen(fd,LISTENQ);  58   59     return fd;  60 }  61   62 static int accept_client_proc(int srvfd)  63 {  64     struct sockaddr_in cliaddr;  65     socklen_t cliaddrlen;  66     cliaddrlen = sizeof(cliaddr);  67     int clifd = -1;  68   69     printf("accpet clint proc is called. ");  70   71 ACCEPT:  72     clifd = accept(srvfd,(struct sockaddr*)&cliaddr,&cliaddrlen);  73   74     if (clifd == -1) {  75         if (errno == EINTR) {  76             goto ACCEPT;  77         } else {  78             fprintf(stderr, "accept fail,error:%s ", strerror(errno));  79             return -1;  80         }  81     }  82   83     fprintf(stdout, "accept a new client: %s:%d ",  84             inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);  85   86     //将新的连接描述符添加到数组中  87     int i = 0;  88     for (i = 0; i < SIZE; i++) {  89         if (s_srv_ctx->clifds[i] < 0) {  90             s_srv_ctx->clifds[i] = clifd;  91             s_srv_ctx->cli_cnt++;  92             break;  93         }  94     }  95   96     if (i == SIZE) {  97         fprintf(stderr,"too many clients. ");  98         return -1;  99     } 100 101 } 102  103 static int handle_client_msg(int fd, char *buf)  104 { 105     assert(buf); 106     printf("recv buf is :%s ", buf); 107     write(fd, buf, strlen(buf) +1); 108     return 0; 109 } 110  111 static void recv_client_msg(fd_set *readfds) 112 { 113     int i = 0, n = 0; 114     int clifd; 115     char buf[MAXLINE] = {0}; 116     for (i = 0;i <= s_srv_ctx->cli_cnt;i++) { 117         clifd = s_srv_ctx->clifds[i]; 118         if (clifd < 0) { 119             continue; 120         } 121         /*判断客户端套接字是否有数据*/ 122         if (FD_ISSET(clifd, readfds)) { 123             //接收客户端发送的信息 124             n = read(clifd, buf, MAXLINE); 125             if (n <= 0) { 126                 /*n==0表示读取完成,客户都关闭套接字*/ 127                 FD_CLR(clifd, &s_srv_ctx->allfds); 128                 close(clifd); 129                 s_srv_ctx->clifds[i] = -1; 130                 continue; 131             } 132             handle_client_msg(clifd, buf); 133         } 134     } 135 } 136 static void handle_client_proc(int srvfd) 137 { 138     int  clifd = -1; 139     int  retval = 0; 140     fd_set *readfds = &s_srv_ctx->allfds; 141     struct timeval tv; 142     int i = 0; 143  144     while (1) { 145         /*每次调用select前都要重新设置文件描述符和时间,因为事件发生后,文件描述符和时间都被内核修改啦*/ 146         FD_ZERO(readfds); 147         /*添加监听套接字*/ 148         FD_SET(srvfd, readfds); 149         s_srv_ctx->maxfd = srvfd; 150  151         tv.tv_sec = 30; 152         tv.tv_usec = 0; 153         /*添加客户端套接字*/ 154         for (i = 0; i < s_srv_ctx->cli_cnt; i++) { 155             clifd = s_srv_ctx->clifds[i]; 156             /*去除无效的客户端句柄*/ 157             if (clifd != -1) { 158                 FD_SET(clifd, readfds); 159             } 160             s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd); 161         } 162  163         /*开始轮询接收处理服务端和客户端套接字*/ 164         retval = select(s_srv_ctx->maxfd + 1, readfds, NULL, NULL, &tv); 165         if (retval == -1) { 166             fprintf(stderr, "select error:%s. ", strerror(errno)); 167             return; 168         } 169         if (retval == 0) { 170             fprintf(stdout, "select is timeout. "); 171             continue; 172         } 173         if (FD_ISSET(srvfd, readfds)) { 174             /*监听客户端请求*/ 175             accept_client_proc(srvfd); 176         } else { 177             /*接受处理客户端消息*/ 178             recv_client_msg(readfds); 179         } 180     } 181 } 182  183 static void server_uninit() 184 { 185     if (s_srv_ctx) { 186         free(s_srv_ctx); 187         s_srv_ctx = NULL; 188     } 189 } 190  191 static int server_init() 192 { 193     s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st)); 194     if (s_srv_ctx == NULL) { 195         return -1; 196     } 197  198     memset(s_srv_ctx, 0, sizeof(server_context_st)); 199  200     int i = 0; 201     for (;i < SIZE; i++) { 202         s_srv_ctx->clifds[i] = -1; 203     } 204  205     return 0; 206 } 207  208 int main(int argc,char *argv[]) 209 { 210     int  srvfd; 211     /*初始化服务端context*/ 212     if (server_init() < 0) { 213         return -1; 214     } 215     /*创建服务,开始监听客户端请求*/ 216     srvfd = create_server_proc(IPADDR, PORT); 217     if (srvfd < 0) { 218         fprintf(stderr, "socket create or bind fail. "); 219         goto err; 220     } 221     /*开始接收并处理客户端请求*/ 222     handle_client_proc(srvfd); 223     server_uninit(); 224     return 0; 225 err: 226     server_uninit(); 227     return -1; 228 }
  客户端程序如下:  1 #include   2 #include   3 #include   4 #include   5 #include   6 #include   7 #include   8 #include   9 #include  10 #include  11  12 #define MAXLINE 1024 13 #define IPADDRESS "127.0.0.1" 14 #define SERV_PORT 8787 15  16 #define max(a,b) (a > b) ? a : b 17  18 static void handle_recv_msg(int sockfd, char *buf)  19 { 20 printf("client recv msg is:%s ", buf); 21 sleep(5); 22 write(sockfd, buf, strlen(buf) +1); 23 } 24  25 static void handle_connection(int sockfd) 26 { 27 char sendline[MAXLINE],recvline[MAXLINE]; 28 int maxfdp,stdineof; 29 fd_set readfds; 30 int n; 31 struct timeval tv; 32 int retval = 0; 33  34 while (1) { 35  36 FD_ZERO(&readfds); 37 FD_SET(sockfd,&readfds); 38 maxfdp = sockfd; 39  40 tv.tv_sec = 5; 41 tv.tv_usec = 0; 42  43 retval = select(maxfdp+1,&readfds,NULL,NULL,&tv); 44  45 if (retval == -1) { 46 return ; 47 } 48  49 if (retval == 0) { 50 printf("client timeout. "); 51 continue; 52 } 53  54 if (FD_ISSET(sockfd, &readfds)) { 55 n = read(sockfd,recvline,MAXLINE); 56 if (n <= 0) { 57 fprintf(stderr,"client: server is closed. "); 58 close(sockfd); 59 FD_CLR(sockfd,&readfds); 60 return; 61 } 62  63 handle_recv_msg(sockfd, recvline); 64 } 65 } 66 } 67  68 int main(int argc,char *argv[]) 69 { 70 int sockfd; 71 struct sockaddr_in servaddr; 72  73 sockfd = socket(AF_INET,SOCK_STREAM,0); 74  75 bzero(&servaddr,sizeof(servaddr)); 76 servaddr.sin_family = AF_INET; 77 servaddr.sin_port = htons(SERV_PORT); 78 inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr); 79  80 int retval = 0; 81 retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); 82 if (retval < 0) { 83 fprintf(stderr, "connect fail,error:%s ", strerror(errno)); 84 return -1; 85 } 86  87 printf("client send to server . "); 88 write(sockfd, "hello server", 32); 89  90 handle_connection(sockfd); 91  92 return 0; 93 }
  4、程序结果
  启动服务程序,执行三个客户程序进行测试,结果如下图所示:
  select的调用过程如下所示:(1)使用copy_from_user从用户空间拷贝fd_set到内核空间 (2)注册回调函数__pollwait (3)遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sock_poll,sock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll) (4)以tcp_poll为例,其核心实现就是__pollwait,也就是上面注册的回调函数。 (5)__pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcp_poll来说,其等待队列是sk->sk_sleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。 (6)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。 (7)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用schedule_timeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(schedule_timeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。 (8)把fd_set从内核空间拷贝到用户空间。
  总结:
  select的几大缺点: 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 select支持的文件描述符数量太小了,默认是1024 二,poll实现
  poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。
  1、基本知识
  poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
  2.2。poll函数
  函数格式如下所示: # include  int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
  pollfd结构体定义如下: struct pollfd { int fd; /* 文件描述符 */ short events; /* 等待的事件 */ short revents; /* 实际发生了的事件 */ } ;
  每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下: POLLIN         有数据可读。 POLLRDNORM      有普通数据可读。 POLLRDBAND      有优先数据可读。 POLLPRI         有紧迫数据可读。 POLLOUT       写数据不会导致阻塞。 POLLWRNORM      写普通数据不会导致阻塞。 POLLWRBAND      写优先数据不会导致阻塞。 POLLMSGSIGPOLL     消息可用。 此外,revents域中还可能返回下列事件:
  POLLER   指定的文件描述符发生错误。 POLLHUP   指定的文件描述符挂起事件。 POLLNVAL  指定的文件描述符非法。
  这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。
  使用poll()和select()不一样,你不需要显式地请求异常情况报告。
  POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
  timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
  返回值和错误代码
  成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一: EBADF   一个或多个结构体中指定的文件描述符无效。 EFAULTfds   指针指向的地址超出进程的地址空间。 EINTR     请求的事件之前产生一个信号,调用可以重新发起。 EINVALnfds  参数超出PLIMIT_NOFILE值。 ENOMEM   可用内存不足,无法完成请求。
  2.3测出程序
  编写一个echo server程序,功能是客户端向服务器发送信息,服务器接收输出并原样发送回给客户端,客户端接收到输出到终端。
  服务器端程序如下:   1 #include    2 #include    3 #include    4 #include    5    6 #include    7 #include    8 #include    9 #include   10 #include   11   12 #define IPADDRESS   "127.0.0.1"  13 #define PORT        8787  14 #define MAXLINE     1024  15 #define LISTENQ     5  16 #define OPEN_MAX    1000  17 #define INFTIM      -1  18   19 //函数声明  20 //创建套接字并进行绑定  21 static int socket_bind(const char* ip,int port);  22 //IO多路复用poll  23 static void do_poll(int listenfd);  24 //处理多个连接  25 static void handle_connection(struct pollfd *connfds,int num);  26   27 int main(int argc,char *argv[])  28 {  29     int  listenfd,connfd,sockfd;  30     struct sockaddr_in cliaddr;  31     socklen_t cliaddrlen;  32     listenfd = socket_bind(IPADDRESS,PORT);  33     listen(listenfd,LISTENQ);  34     do_poll(listenfd);  35     return 0;  36 }  37   38 static int socket_bind(const char* ip,int port)  39 {  40     int  listenfd;  41     struct sockaddr_in servaddr;  42     listenfd = socket(AF_INET,SOCK_STREAM,0);  43     if (listenfd == -1)  44     {  45         perror("socket error:");  46         exit(1);  47     }  48     bzero(&servaddr,sizeof(servaddr));  49     servaddr.sin_family = AF_INET;  50     inet_pton(AF_INET,ip,&servaddr.sin_addr);  51     servaddr.sin_port = htons(port);  52     if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)  53     {  54         perror("bind error: ");  55         exit(1);  56     }  57     return listenfd;  58 }  59   60 static void do_poll(int listenfd)  61 {  62     int  connfd,sockfd;  63     struct sockaddr_in cliaddr;  64     socklen_t cliaddrlen;  65     struct pollfd clientfds[OPEN_MAX];  66     int maxi;  67     int i;  68     int nready;  69     //添加监听描述符  70     clientfds[0].fd = listenfd;  71     clientfds[0].events = POLLIN;  72     //初始化客户连接描述符  73     for (i = 1;i < OPEN_MAX;i++)  74         clientfds[i].fd = -1;  75     maxi = 0;  76     //循环处理  77     for ( ; ; )  78     {  79         //获取可用描述符的个数  80         nready = poll(clientfds,maxi+1,INFTIM);  81         if (nready == -1)  82         {  83             perror("poll error:");  84             exit(1);  85         }  86         //测试监听描述符是否准备好  87         if (clientfds[0].revents & POLLIN)  88         {  89             cliaddrlen = sizeof(cliaddr);  90             //接受新的连接  91             if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1)  92             {  93                 if (errno == EINTR)  94                     continue;  95                 else  96                 {  97                    perror("accept error:");  98                    exit(1);  99                 } 100             } 101             fprintf(stdout,"accept a new client: %s:%d ", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); 102             //将新的连接描述符添加到数组中 103             for (i = 1;i < OPEN_MAX;i++) 104             { 105                 if (clientfds[i].fd < 0) 106                 { 107                     clientfds[i].fd = connfd; 108                     break; 109                 } 110             } 111             if (i == OPEN_MAX) 112             { 113                 fprintf(stderr,"too many clients. "); 114                 exit(1); 115             } 116             //将新的描述符添加到读描述符集合中 117             clientfds[i].events = POLLIN; 118             //记录客户连接套接字的个数 119             maxi = (i > maxi ? i : maxi); 120             if (--nready <= 0) 121                 continue; 122         } 123         //处理客户连接 124         handle_connection(clientfds,maxi); 125     } 126 } 127  128 static void handle_connection(struct pollfd *connfds,int num) 129 { 130     int i,n; 131     char buf[MAXLINE]; 132     memset(buf,0,MAXLINE); 133     for (i = 1;i <= num;i++) 134     { 135         if (connfds[i].fd < 0) 136             continue; 137         //测试客户描述符是否准备好 138         if (connfds[i].revents & POLLIN) 139         { 140             //接收客户端发送的信息 141             n = read(connfds[i].fd,buf,MAXLINE); 142             if (n == 0) 143             { 144                 close(connfds[i].fd); 145                 connfds[i].fd = -1; 146                 continue; 147             } 148            // printf("read msg is: "); 149             write(STDOUT_FILENO,buf,n); 150             //向客户端发送buf 151             write(connfds[i].fd,buf,n); 152         } 153     } 154 }
  客户端代码如下所示:  1 #include   2 #include   3 #include   4 #include   5 #include   6 #include   7 #include   8 #include   9 #include  10  11 #define MAXLINE     1024 12 #define IPADDRESS   "127.0.0.1" 13 #define SERV_PORT   8787 14  15 #define max(a,b) (a > b) ? a : b 16  17 static void handle_connection(int sockfd); 18  19 int main(int argc,char *argv[]) 20 { 21     int                 sockfd; 22     struct sockaddr_in  servaddr; 23     sockfd = socket(AF_INET,SOCK_STREAM,0); 24     bzero(&servaddr,sizeof(servaddr)); 25     servaddr.sin_family = AF_INET; 26     servaddr.sin_port = htons(SERV_PORT); 27     inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr); 28     connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); 29     //处理连接描述符 30     handle_connection(sockfd); 31     return 0; 32 } 33  34 static void handle_connection(int sockfd) 35 { 36     char    sendline[MAXLINE],recvline[MAXLINE]; 37     int     maxfdp,stdineof; 38     struct pollfd pfds[2]; 39     int n; 40     //添加连接描述符 41     pfds[0].fd = sockfd; 42     pfds[0].events = POLLIN; 43     //添加标准输入描述符 44     pfds[1].fd = STDIN_FILENO; 45     pfds[1].events = POLLIN; 46     for (; ;) 47     { 48         poll(pfds,2,-1); 49         if (pfds[0].revents & POLLIN) 50         { 51             n = read(sockfd,recvline,MAXLINE); 52             if (n == 0) 53             { 54                     fprintf(stderr,"client: server is closed. "); 55                     close(sockfd); 56             } 57             write(STDOUT_FILENO,recvline,n); 58         } 59         //测试标准输入是否准备好 60         if (pfds[1].revents & POLLIN) 61         { 62             n = read(STDIN_FILENO,sendline,MAXLINE); 63             if (n  == 0) 64             { 65                 shutdown(sockfd,SHUT_WR); 66         continue; 67             } 68             write(sockfd,sendline,n); 69         } 70     } 71 }三、epoll3.1基本知识
  epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。 3.2epoll接口
  epoll操作过程需要三个接口,分别如下: #include  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);
  (1) int epoll_create(int size);
  创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
  (2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
  EPOLL_CTL_ADD:注册新的fd到epfd中;
  EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  EPOLL_CTL_DEL:从epfd中删除一个fd;
  第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:struct epoll_event {   __uint32_t events;  /* Epoll events */   epoll_data_t data;  /* User data variable */ };
  events可以是以下几个宏的集合:
  EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  EPOLLOUT:表示对应的文件描述符可以写;
  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  EPOLLERR:表示对应的文件描述符发生错误;
  EPOLLHUP:表示对应的文件描述符被挂断;
  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
  (3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。3.3.工作模式
  epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
  LT模式: 当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
  ET模式: 当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。 3.4.测试程序
  编写一个服务器回射程序echo,练习epoll过程。
  服务器代码如下所示:   1 #include    2 #include    3 #include    4 #include    5    6 #include    7 #include    8 #include    9 #include   10 #include   11 #include   12   13 #define IPADDRESS   "127.0.0.1"  14 #define PORT        8787  15 #define MAXSIZE     1024  16 #define LISTENQ     5  17 #define FDSIZE      1000  18 #define EPOLLEVENTS 100  19   20 //函数声明  21 //创建套接字并进行绑定  22 static int socket_bind(const char* ip,int port);  23 //IO多路复用epoll  24 static void do_epoll(int listenfd);  25 //事件处理函数  26 static void  27 handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf);  28 //处理接收到的连接  29 static void handle_accpet(int epollfd,int listenfd);  30 //读处理  31 static void do_read(int epollfd,int fd,char *buf);  32 //写处理  33 static void do_write(int epollfd,int fd,char *buf);  34 //添加事件  35 static void add_event(int epollfd,int fd,int state);  36 //修改事件  37 static void modify_event(int epollfd,int fd,int state);  38 //删除事件  39 static void delete_event(int epollfd,int fd,int state);  40   41 int main(int argc,char *argv[])  42 {  43     int  listenfd;  44     listenfd = socket_bind(IPADDRESS,PORT);  45     listen(listenfd,LISTENQ);  46     do_epoll(listenfd);  47     return 0;  48 }  49   50 static int socket_bind(const char* ip,int port)  51 {  52     int  listenfd;  53     struct sockaddr_in servaddr;  54     listenfd = socket(AF_INET,SOCK_STREAM,0);  55     if (listenfd == -1)  56     {  57         perror("socket error:");  58         exit(1);  59     }  60     bzero(&servaddr,sizeof(servaddr));  61     servaddr.sin_family = AF_INET;  62     inet_pton(AF_INET,ip,&servaddr.sin_addr);  63     servaddr.sin_port = htons(port);  64     if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)  65     {  66         perror("bind error: ");  67         exit(1);  68     }  69     return listenfd;  70 }  71   72 static void do_epoll(int listenfd)  73 {  74     int epollfd;  75     struct epoll_event events[EPOLLEVENTS];  76     int ret;  77     char buf[MAXSIZE];  78     memset(buf,0,MAXSIZE);  79     //创建一个描述符  80     epollfd = epoll_create(FDSIZE);  81     //添加监听描述符事件  82     add_event(epollfd,listenfd,EPOLLIN);  83     for ( ; ; )  84     {  85         //获取已经准备好的描述符事件  86         ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);  87         handle_events(epollfd,events,ret,listenfd,buf);  88     }  89     close(epollfd);  90 }  91   92 static void  93 handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf)  94 {  95     int i;  96     int fd;  97     //进行选好遍历  98     for (i = 0;i < num;i++)  99     { 100         fd = events[i].data.fd; 101         //根据描述符的类型和事件类型进行处理 102         if ((fd == listenfd) &&(events[i].events & EPOLLIN)) 103             handle_accpet(epollfd,listenfd); 104         else if (events[i].events & EPOLLIN) 105             do_read(epollfd,fd,buf); 106         else if (events[i].events & EPOLLOUT) 107             do_write(epollfd,fd,buf); 108     } 109 } 110 static void handle_accpet(int epollfd,int listenfd) 111 { 112     int clifd; 113     struct sockaddr_in cliaddr; 114     socklen_t  cliaddrlen; 115     clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen); 116     if (clifd == -1) 117         perror("accpet error:"); 118     else 119     { 120         printf("accept a new client: %s:%d ",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); 121         //添加一个客户描述符和事件 122         add_event(epollfd,clifd,EPOLLIN); 123     } 124 } 125  126 static void do_read(int epollfd,int fd,char *buf) 127 { 128     int nread; 129     nread = read(fd,buf,MAXSIZE); 130     if (nread == -1) 131     { 132         perror("read error:"); 133         close(fd); 134         delete_event(epollfd,fd,EPOLLIN); 135     } 136     else if (nread == 0) 137     { 138         fprintf(stderr,"client close. "); 139         close(fd); 140         delete_event(epollfd,fd,EPOLLIN); 141     } 142     else 143     { 144         printf("read message is : %s",buf); 145         //修改描述符对应的事件,由读改为写 146         modify_event(epollfd,fd,EPOLLOUT); 147     } 148 } 149  150 static void do_write(int epollfd,int fd,char *buf) 151 { 152     int nwrite; 153     nwrite = write(fd,buf,strlen(buf)); 154     if (nwrite == -1) 155     { 156         perror("write error:"); 157         close(fd); 158         delete_event(epollfd,fd,EPOLLOUT); 159     } 160     else 161         modify_event(epollfd,fd,EPOLLIN); 162     memset(buf,0,MAXSIZE); 163 } 164  165 static void add_event(int epollfd,int fd,int state) 166 { 167     struct epoll_event ev; 168     ev.events = state; 169     ev.data.fd = fd; 170     epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev); 171 } 172  173 static void delete_event(int epollfd,int fd,int state) 174 { 175     struct epoll_event ev; 176     ev.events = state; 177     ev.data.fd = fd; 178     epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev); 179 } 180  181 static void modify_event(int epollfd,int fd,int state) 182 { 183     struct epoll_event ev; 184     ev.events = state; 185     ev.data.fd = fd; 186     epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev); 187 }
  客户端也用epoll实现,控制STDIN_FILENO、STDOUT_FILENO、和sockfd三个描述符,程序如下所示:   1 #include    2 #include    3 #include    4 #include    5 #include    6 #include    7 #include    8 #include    9 #include   10 #include   11   12 #define MAXSIZE     1024  13 #define IPADDRESS   "127.0.0.1"  14 #define SERV_PORT   8787  15 #define FDSIZE        1024  16 #define EPOLLEVENTS 20  17   18 static void handle_connection(int sockfd);  19 static void  20 handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf);  21 static void do_read(int epollfd,int fd,int sockfd,char *buf);  22 static void do_read(int epollfd,int fd,int sockfd,char *buf);  23 static void do_write(int epollfd,int fd,int sockfd,char *buf);  24 static void add_event(int epollfd,int fd,int state);  25 static void delete_event(int epollfd,int fd,int state);  26 static void modify_event(int epollfd,int fd,int state);  27   28 int main(int argc,char *argv[])  29 {  30     int                 sockfd;  31     struct sockaddr_in  servaddr;  32     sockfd = socket(AF_INET,SOCK_STREAM,0);  33     bzero(&servaddr,sizeof(servaddr));  34     servaddr.sin_family = AF_INET;  35     servaddr.sin_port = htons(SERV_PORT);  36     inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);  37     connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));  38     //处理连接  39     handle_connection(sockfd);  40     close(sockfd);  41     return 0;  42 }  43   44   45 static void handle_connection(int sockfd)  46 {  47     int epollfd;  48     struct epoll_event events[EPOLLEVENTS];  49     char buf[MAXSIZE];  50     int ret;  51     epollfd = epoll_create(FDSIZE);  52     add_event(epollfd,STDIN_FILENO,EPOLLIN);  53     for ( ; ; )  54     {  55         ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1);  56         handle_events(epollfd,events,ret,sockfd,buf);  57     }  58     close(epollfd);  59 }  60   61 static void  62 handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf)  63 {  64     int fd;  65     int i;  66     for (i = 0;i < num;i++)  67     {  68         fd = events[i].data.fd;  69         if (events[i].events & EPOLLIN)  70             do_read(epollfd,fd,sockfd,buf);  71         else if (events[i].events & EPOLLOUT)  72             do_write(epollfd,fd,sockfd,buf);  73     }  74 }  75   76 static void do_read(int epollfd,int fd,int sockfd,char *buf)  77 {  78     int nread;  79     nread = read(fd,buf,MAXSIZE);  80         if (nread == -1)  81     {  82         perror("read error:");  83         close(fd);  84     }  85     else if (nread == 0)  86     {  87         fprintf(stderr,"server close. ");  88         close(fd);  89     }  90     else  91     {  92         if (fd == STDIN_FILENO)  93             add_event(epollfd,sockfd,EPOLLOUT);  94         else  95         {  96             delete_event(epollfd,sockfd,EPOLLIN);  97             add_event(epollfd,STDOUT_FILENO,EPOLLOUT);  98         }  99     } 100 } 101  102 static void do_write(int epollfd,int fd,int sockfd,char *buf) 103 { 104     int nwrite; 105     nwrite = write(fd,buf,strlen(buf)); 106     if (nwrite == -1) 107     { 108         perror("write error:"); 109         close(fd); 110     } 111     else 112     { 113         if (fd == STDOUT_FILENO) 114             delete_event(epollfd,fd,EPOLLOUT); 115         else 116             modify_event(epollfd,fd,EPOLLIN); 117     } 118     memset(buf,0,MAXSIZE); 119 } 120  121 static void add_event(int epollfd,int fd,int state) 122 { 123     struct epoll_event ev; 124     ev.events = state; 125     ev.data.fd = fd; 126     epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev); 127 } 128  129 static void delete_event(int epollfd,int fd,int state) 130 { 131     struct epoll_event ev; 132     ev.events = state; 133     ev.data.fd = fd; 134     epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev); 135 } 136  137 static void modify_event(int epollfd,int fd,int state) 138 { 139     struct epoll_event ev; 140     ev.events = state; 141     ev.data.fd = fd; 142     epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev); 143 }
  epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。 对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。 对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。 对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max查看,一般来说这个数目和系统内存关系很大。
  总结:
  (1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在"醒着"的时候要遍历整个fd集合,而epoll在"醒着"的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
  (2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

决策参考微信公众号改版芒果TV成功置入快乐购1微信公众号改版信息流事件6月20日晚间,微信发布iOS版v6。7。0,订阅号界面大变身,以信息流的方式,按发布时间顺序直接展示文章。微信希望提高阅读的体验和效率,并鼓励作者更专注决策参考中国国际电视总公司和阿里巴巴签订合作协议1中国国际电视总公司和阿里巴巴签订合作协议事件近日,中央广播电视总台下属的中国国际电视总公司与阿里巴巴集团签订技术合作协议,双方将在云平台大数据移动客户端信息化平台建设等方面进行合Facebook新成立区块链部门抖音成一季度全球下载最多iPhone应用1Facebook宣布成立以来最大重组新成立区块链部门事件近日,据国外媒体报道,Facebook日前宣布了创办15年以来的最大规模重组,并新成立了区块链技术部门。Facebook创优秀传统文化双创需要创新话语体系我国是一个有着五千年璀璨历史的文明古国,有着丰富的优秀传统文化资源,但如何科学地传承和发展优秀传统文化却困难重重,尤其是有效地把接触难看不懂的博物馆文物活起来更是个大难题。党的十九边牧作为智力排名第一的狗,为什么很多人还是不愿意养?图片来自网络,侵删最大的伤害不是伤害,而是侮辱即所谓伤害性不大,侮辱性很强。最大的侮辱是对智商的侮辱,很多人不愿意养边牧,可能就是不想自己在智商上被它侮辱。不过我是真喜欢边牧。有一你爱八卦?铁齿铜牙纪晓岚说狐狸都瞧不起你阅微草堂笔记是纪晓岚在大约66岁时才开始写的志怪小说集,历时十年乃成,以四库全书总纂官的大手笔,于人笔俱老之年出之,绝对是十年辛苦不寻常的杰作,从这方面讲,这本书就是纪昀的红楼梦。小米商标与华为商标构成近似?终审判决出炉众所周知,小米和华为都是卖手机的,经营业务有相似的地方,到底是什么商标让两家公司都想要呢?据悉,小米公司申请的第39448815号MISERVICE商标(简称诉争商标)被国家知识产等等党又胜利了?想入手iMAC的朋友有福啦对于那些希望在不久的将来开始使用iMac的人来说,目前最合理的选择应该是等等党。目前,苹果公司的27英寸iMac的供应量继续下降,中高端机型的发货时间甚至已推迟到9月。据了解,27参考参考快手日活破3亿5年后近一半美国家庭将弃传统有线电视Version1。0StartHTML000000538EndHTML000644692StartFragment000608610EndFragment000644624Star市场报告英伟达在独立gpu市场上仍处于主导地位根据市场报告显示能够了解到,AMD已经在CPU市场重新崛起,但是在独立GPU市场上,Nvidia仍然占据主导地位。从2020年第二季度到2021年第二季度,AMD失去了约3的市场份干货知识分享外贸网站图片优化技巧外贸网站上的图片应该如何选择?才能最大程度的保证美观度以及谷歌SEO的效果,接下我们就为大家介绍一些网站图片优化的小技巧。外贸网站的图片最常见的误区直接从素材网站下载了某图片,然后
华为5G新机下月发布,Mate50最早6月见,携麒麟鸿蒙挑战iPhone13自从麒麟断供华为5G手机一机难求之后,苹果就迅速用技术与价格优势占据了高端手机市场的大半份额,iPhone13系列强势出击,增配减价成为有史以来最具性价比的iPhone机型。近期,徐起realme有史以来最快闪充技术2月28日MWC见IT之家2月21日消息,今日下午,realme副总裁中国区总裁全球营销总裁徐起通过社交媒体宣布,realme有史以来最快闪充技术将于2月28日在MWC亮相。值得一提的是,根据徐起的借鉴蚂蚁?小米旗下支付平台天星数科变更随星贷服务运营商至小米消金2月21日,资本邦了解到,针对此前有关媒体报道的业务线大幅缩减一事,近日,小米旗下金融科技板块天星数科做出回应。天星数科表示,公司不存在缩减的情况,相关业务线本着聚焦主业服务实体的遇到这种微信号,千万别转账通过手机进行支付交易如今已是相当普遍但各类骗局也往往暗藏其中比如像这样的聊天记录就让人极为绝望付完款,催发货时,发现已被拉黑卖家人间蒸发,只剩下可怜的你钱货两空。出现风险提醒格外当第四大运营商要来了!中国广电强势加入移动通讯市场,号段公布第四个运营商来了众所周知,目前我们中国大陆只有三个电信运营商,都不用我说大家肯定知道是中国移动,中国联通,中国电信。而在近日,据中国广电通知,中国广电将正式进入移动通讯市场,现在第易华录公司已在京津冀等全国算力网络国家枢纽节点建设数据湖与发改委东数西算工程规划相当契合易华录公司已在京津冀等全国算力网络国家枢纽节点建设数据湖与发改委东数西算工程规划相当契合财联社2月21日电,易华录在互动平台表示,公司已在京津冀长三角成渝贵州宁夏等全国算力网络国家网络赌博,是否成为当前影响最大危害最严重的赌博活动之一?网络赌博确实是具有相当大危害性,因为这些都是涉及到很多控制为极差的青少年,他们不懂得金钱来之不易,更无法做到小赌贻情的境界!有时他们夜以继日废寝忘食地赌,每天几百几千的耗尽却不懂得没有中间商赚差价!特斯拉的直营模式颠覆了传统4S店相信很多人买车都遭受过4S店的洗礼,被销售人员忽悠的五迷三道,跟对方拉锯战式的讨价还价,或是因为一点优惠而吵得急赤白脸,谈到4S店人们估计恨的比较多。什么是4S店?它包括整车销售零冬奥床火出圈,钱报记者专访麒盛科技董事长让床说话,护航百姓睡眠和身体健康钱江晚报小时新闻记者王燕平(图片由采访对象提供)北京冬奥会昨晚落下帷幕,但冬奥热仍在持续。冬奥会期间,为冬奥村供应了近7000张智能电动床的浙江嘉兴上市公司麒盛科技迅速火出圈,公司马斯克误导消费者?外媒称特斯拉把自动驾驶吹得天花乱坠据法新社报道,特斯拉公司首席执行官埃隆马斯克经常大谈近在眼前的全自动驾驶汽车时代的到来,不过这样的未来距现在到底有多远仍是个未知数。此外,在美国往往对新兴技术采取放任自流态度的监管把电池织进衣服!复旦大学彭慧胜团队开发大规模连续制备纤维电池新方法2022年1月21日,复旦大学彭慧胜团队在NatureNanotechnology发表题为Industrialscaleproductionoffibrebatteriesbyas