Linux C 网络编程——7.select poll epoll

来源:互联网 发布:三级分销java源程序 编辑:程序博客网 时间:2024/05/20 20:17

上一节中我们讲到了IO复用,总结起来也就是分为同步和异步模型,以及阻塞和非阻塞模型,本文主要分析其中的异步阻塞模型。


IO的复用可以通过select poll epoll实现


1. select

头文件:sys/select.h   sys/time.h

int select( int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout );

(1)maxfd

maxfd = max( readset, writeset, exceptset ) + 1;

(2)timeout

经过timeout时间后无论如何都要返回

timeout = 0 :非阻塞IO

timeout = NULL:永远等待

(3)当发生以下情况时select返回:

>readset中fd可读

>writeset中fd可写

>exceptset中fd发生异常


(4)返回值:

OK—— 准备好的fd的个数(0-n)

Fail—— -1   (非exceptset中的fd发生意外)



(5)FD_ISSET

如果返回值大于0,那么可用FD_ISSET测试readset中哪个文件准备好了

(6)实现

select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返回。

(7)缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024



FD_ZERO(int fd, fd_set* fds) FD_SET(int fd, fd_set* fds) FD_ISSET(int fd, fd_set* fds) FD_CLR(int fd, fd_set* fds) int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) 

SOCKADDR_IN addrSrv;int reuse = 1;SOCKET sockSrv,connsock;SOCKADDR_IN addrClient;pool pool;int len=sizeof(SOCKADDR);/*创建TCP*/sockSrv=socket(AF_INET,SOCK_STREAM,0);/*地址、端口的绑定*/addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);addrSrv.sin_family=AF_INET;addrSrv.sin_port=htons(port);if(bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR))<0){fprintf(stderr,"Failed to bind");return ;}if(listen(sockSrv,5)<0){fprintf(stderr,"Failed to listen socket");return ;}setsockopt(sockSrv,SOL_SOCKET,SO_REUSEADDR,(const char*)&reuse,sizeof(reuse));init_pool(sockSrv,&pool);while(1){/*通过selete设置为异步模式*/pool.ready_set=pool.read_set;pool.nready=select(pool.maxfd+1,&pool.ready_set,NULL,NULL,NULL);if(FD_ISSET(sockSrv,&pool.ready_set)){connsock=accept(sockSrv,(SOCKADDR *)&addrClient,&len);//loadDeal()/*连接处理*///printf("test\n");add_client(connsock,&pool);//添加到连接池}/*检查是否有事件发生*/check_client(&pool);}

上面是一个服务器代码的关键部分,设置为异步的模式,然后接受到连接将其添加到连接池中。监听描述符上使用select,接受客户端的连接请求,在check_client函数中,遍历连接池中的描述符,检查是否有事件发生。

         


2. poll 轮询函数

头文件:poll.h

poll函数类似于select,但是其调用形式不同。poll不是为每个条件构造一个描述符集,而是构造一个pollfd结构体数组,每个数组元素指定一个描述符标号及其所关心的条件。定义如下:

#include <sys/poll.h>int poll (struct pollfd *fds, unsigned int nfds, int timeout);struct pollfd {int fd; /* file descriptor可为负数 */short events; /* requested events to watch 用户设置*/short revents; /* returned events witnessed 内核返回*/};

(1)事件:

events:


POLLIN :可读除高优级外的数据,不阻塞

POLLRDNORM:可读普通数据,不阻塞

POLLRDBAND:可读O优先数据,不阻塞

POLLPRI:可读高优先数据,不阻塞

POLLOUT :可写普数据,不阻塞

POLLWRNORM:与POLLOUT相同

POLLWRBAND:写非0优先数据,不阻塞

revents:

POLLERR :已出错

POLLHUP:已挂起,当以描述符被挂起后,就不能再写向该描述符,但是仍可以从该描述符读取到数据。

POLLNVAL:此描述符并不引用一打开文件

(2)实现

poll的实现机制与select类似,其对应内核中的sys_poll,只不过poll向内核传递pollfd数组,然后对pollfd中的每个描述符进行poll,相比处理fdset来说,poll效率更高。
poll返回后,需要对pollfd中的每个元素检查其revents值,来得指事件是否发生。

(3)缺点:

基本上与select一样,只不过解决了描述符数量的限制。


3. epoll

epoll解决了select,poll的上述缺点。

(1)数据结构

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 */};

事件:

EPOLLIN:表示对描述符的可以读

EPOLLOUT:表示对描述符的可以写

EPOLLPRI:表示对描述符的有紧急数据可以读

EPOLLERR:发生错误

EPOLLHUP:挂起

EPOLLET:边缘触发

EPOLLONESHOT:一次性使用,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


(2)函数

int epoll_creae(int size);  

功能:该函数生成一个epoll专用的文件描述符

参数:size为epoll上能关注的最大描述符数

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
功能:用于控制某个epoll文件描述符时间,可以注册、修改、删除
参数:epfd由epoll_create生成的epoll专用描述符
    op操作:EPOLL_CTL_ADD 注册   EPOLL_CTL_MOD修改  EPOLL_DEL删除
            fd:关联的文件描述符
    evnet告诉内核要监听什么事件


int epoll_wait(int epfd,struct epoll_event*events,int maxevents,int timeout);  
功能:该函数等待i/o事件的发生。
参数:epfd要检测的句柄
    events:用于回传待处理时间的数组
    maxevents:告诉内核这个events有多大,不能超过之前的size
    timeout:为超时时间


#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <unistd.h>#include <fcntl.h>#include <sys/epoll.h>#include <errno.h>#define MAXEVENTS 64static intmake_socket_non_blocking (int sfd){  int flags, s;  flags = fcntl (sfd, F_GETFL, 0);  if (flags == -1)    {      perror ("fcntl");      return -1;    }  flags |= O_NONBLOCK;  s = fcntl (sfd, F_SETFL, flags);  if (s == -1)    {      perror ("fcntl");      return -1;    }  return 0;}static intcreate_and_bind (char *port){  struct addrinfo hints;  struct addrinfo *result, *rp;  int s, sfd;  memset (&hints, 0, sizeof (struct addrinfo));  hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  hints.ai_flags = AI_PASSIVE;     /* All interfaces */  s = getaddrinfo (NULL, port, &hints, &result);  if (s != 0)    {      fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));      return -1;    }  for (rp = result; rp != NULL; rp = rp->ai_next)    {      sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);      if (sfd == -1)        continue;      s = bind (sfd, rp->ai_addr, rp->ai_addrlen);      if (s == 0)        {          /* We managed to bind successfully! */          break;        }      close (sfd);    }  if (rp == NULL)    {      fprintf (stderr, "Could not bind\n");      return -1;    }  freeaddrinfo (result);  return sfd;}intmain (int argc, char *argv[]){  int sfd, s;  int efd;  struct epoll_event event;  struct epoll_event *events;  if (argc != 2)    {      fprintf (stderr, "Usage: %s [port]\n", argv[0]);      exit (EXIT_FAILURE);    }  sfd = create_and_bind (argv[1]);  if (sfd == -1)    abort ();  s = make_socket_non_blocking (sfd);  if (s == -1)    abort ();  s = listen (sfd, SOMAXCONN);  if (s == -1)    {      perror ("listen");      abort ();    }  efd = epoll_create1 (0);  if (efd == -1)    {      perror ("epoll_create");      abort ();    }  event.data.fd = sfd;  event.events = EPOLLIN | EPOLLET;  s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  if (s == -1)    {      perror ("epoll_ctl");      abort ();    }  /* Buffer where events are returned */  events = calloc (MAXEVENTS, sizeof event);  /* The event loop */  while (1)    {      int n, i;      n = epoll_wait (efd, events, MAXEVENTS, -1);      for (i = 0; i < n; i++){  if ((events[i].events & EPOLLERR) ||              (events[i].events & EPOLLHUP) ||              (!(events[i].events & EPOLLIN)))    {              /* An error has occured on this fd, or the socket is not                 ready for reading (why were we notified then?) */      fprintf (stderr, "epoll error\n");      close (events[i].data.fd);      continue;    }  else if (sfd == events[i].data.fd)    {              /* We have a notification on the listening socket, which                 means one or more incoming connections. */              while (1)                {                  struct sockaddr in_addr;                  socklen_t in_len;                  int infd;                  char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];                  in_len = sizeof in_addr;                  infd = accept (sfd, &in_addr, &in_len);                  if (infd == -1)                    {                      if ((errno == EAGAIN) ||                          (errno == EWOULDBLOCK))                        {                          /* We have processed all incoming                             connections. */                          break;                        }                      else                        {                          perror ("accept");                          break;                        }                    }                  s = getnameinfo (&in_addr, in_len,                                   hbuf, sizeof hbuf,                                   sbuf, sizeof sbuf,                                   NI_NUMERICHOST | NI_NUMERICSERV);                  if (s == 0)                    {                      printf("Accepted connection on descriptor %d "                             "(host=%s, port=%s)\n", infd, hbuf, sbuf);                    }                  /* Make the incoming socket non-blocking and add it to the                     list of fds to monitor. */                  s = make_socket_non_blocking (infd);                  if (s == -1)                    abort ();                  event.data.fd = infd;                  event.events = EPOLLIN | EPOLLET;                  s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);                  if (s == -1)                    {                      perror ("epoll_ctl");                      abort ();                    }                }              continue;            }          else            {              /* We have data on the fd waiting to be read. Read and                 display it. We must read whatever data is available                 completely, as we are running in edge-triggered mode                 and won't get a notification again for the same                 data. */              int done = 0;              while (1)                {                  ssize_t count;                  char buf[512];                  count = read (events[i].data.fd, buf, sizeof buf);                  if (count == -1)                    {                      /* If errno == EAGAIN, that means we have read all                         data. So go back to the main loop. */                      if (errno != EAGAIN)                        {                          perror ("read");                          done = 1;                        }                      break;                    }                  else if (count == 0)                    {                      /* End of file. The remote has closed the                         connection. */                      done = 1;                      break;                    }                  /* Write the buffer to standard output */                  s = write (1, buf, count);                  if (s == -1)                    {                      perror ("write");                      abort ();                    }                }              if (done)                {                  printf ("Closed connection on descriptor %d\n",                          events[i].data.fd);                  /* Closing the descriptor will make epoll remove it                     from the set of descriptors which are monitored. */                  close (events[i].data.fd);                }            }        }    }  free (events);  close (sfd);  return EXIT_SUCCESS;}


4. 参考文献

[1] Linux 环境下C编程指南

[2] select、poll、epoll的比较

原创粉丝点击