socket-select ,poll ,epoll 代码分析

refer to:
linux programming 4
select、poll、epoll之间的区别总结[整理]
UNP 1


三种模型中主要的结构体



int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
typedef struct{ __fd_mask  fds_bits[__FD_SETSIZE / __NFDBITS];} fd_set;
typedef long int __fd_mask;
#define __NFDBITS (8 * (int) sizeof (__fd_mask))
#define __FD_SETSIZE1024

sizeof (__fd_mask) = 4
所以fd_set相当于int x[32] ,其每一位代表一个fd

使用方法:
1.将需要监视的fd加入readfds,FD_SET(fd, &readfds);
2.给select提供一个fd_set *readfds(和writefds,exceptfds)类型的参数,其实是32个int的数组。以便select去监视之。
只要fd_set *readfds(或writefds,exceptfds)中有任意一个fd有动作,select就会返回。
3.由于返回的仍然是整个fd_set,所以需要轮询整个所有fd_set的所有元素才能知道是哪个fds里的哪个fd在动作
在循环中,可以用FD_ISSET(fd,readfds)来检查某个fd是否可读。(因为select函数成功返回时会将未准备好的描述符位清零。通常我们使用FD_ISSET是为了检查在select函数返回后,某个描述符是否准备好,以便进行接下来的处理操作。)


int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd
  {
    int fd;/* File descriptor to poll.  */
    short int events;/* Types of events poller cares about.  */
    short int revents;/* Types of events that actually occurred.  */
  };
typedef unsigned long int nfds_t;

poll返回的是返回值int 和 参数 fds

1.将需要监视的fd通过放入pollfd[x].fd
2.给poll提供一个pollfd数组,poll会监视pollfd数组中的所有fd的动作。只要有一个有动作,就会返回。
3.由于返回的仍然是整个pollfd数组,所以需要轮询整个pollfd数组才能知道到底是pollfd数组中的哪个fd有动作。
在循环中,用  if( pollfd[x].revents& POLLIN )来测试是否pollfd[x].fd可读。


extern int epoll_wait (int __epfd, struct epoll_event *__events,
      int __maxevents, int __timeout);

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 */
} __attribute__ ((__packed__)); 

使用方法: 
1.将需要监视的fd加入到epoll_create创建的实例epollfd中:epoll_ctrl。此时,需要注意fd和epoll_event绑定(epoll_event.data.fd=fd):因为epoll_wait返回的是epoll_event,而不是fd。
2.给epoll_wait提供一个epoll_event的数组, epoll_wait 会监视epollfd中的所有fd,只要epollfd中有任意一个fd有动作,epoll_wait就会返回对应有动作的epoll_wait到epoll_event数组。
3.读写epoll_wait[i].data.fd即可。 


以上可知: 
1.在每次循环执行select之前需要重新设置select监视的fd_set。因为select返回时把监视的fd_set的未有动作的fd位清空了。这样,select返回之后,就能用 
FD_ISSET来判断是哪个fd准备好了。 
而poll使用的是pollfd结构体,此结构体有一个成员revents可以指示当前的fd是否可读或可写。 

不管怎么样,poll和select都要循环整个已被监视的数组,才能得知是哪个fd就绪了(有动作了)。 

2. 
epoll_wait返回的就是已就绪数组。 


select 模型 

[root@localhost chapter15]# cat server5.c 
/*  For our final example, server5.c, 
    we include the sys/time.h and sys/ioctl.h headers in place of signal.h
    in our last program and declare some extra variables to deal with select.  */

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#define DEBUG
#ifdef DEBUG  
#define DBG(...) fprintf(stderr, " DBG(%s, %s(), %d): ", __FILE__, __FUNCTION__, __LINE__); fprintf(stderr, __VA_ARGS__)  
#else  
#define DBG(...)  
#endif  
int main()
{
    int server_sockfd, client_sockfd;
    int server_len, client_len;
    struct sockaddr_in server_address;
    struct sockaddr_in client_address;
    int result;
    fd_set readfds, testfds;

/*  Create and name a socket for the server.  */

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
	DBG("server_sockfd = %d \n",server_sockfd );
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = htonl(INADDR_ANY);
    server_address.sin_port = htons(9734);
    server_len = sizeof(server_address);

    bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

/*  Create a connection queue and initialize readfds to handle input from server_sockfd.  */

    listen(server_sockfd, 5);

    FD_ZERO(&readfds);
    FD_SET(server_sockfd, &readfds);

/*  Now wait for clients and requests.
    Since we have passed a null pointer as the timeout parameter, no timeout will occur.
    The program will exit and report an error if select returns a value of less than 1.  */

    while(1) {
        char ch;
        int fd;
        int nread;

        testfds = readfds;

        DBG("server waiting\n");
       // result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0);  
        result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0,NULL);
           

        if(result < 1) {
            perror("server5");
            exit(1);
        }

/*  Once we know we've got activity,
    we find which descriptor it's on by checking each in turn using FD_ISSET.  */
	DBG("FD_SETSIZE = %d-------\n",FD_SETSIZE);
	DBG("%d times cycle---\n",FD_SETSIZE);
        for(fd = 0; fd < FD_SETSIZE; fd++) {
	  DBG("the %d times cycle---\n",fd);
          if(FD_ISSET(fd,&testfds)) {
		DBG("the %d times cycle catched------\n",fd);
/*  If the activity is on server_sockfd, it must be a request for a new connection
    and we add the associated client_sockfd to the descriptor set.  */

                if(fd == server_sockfd) {
                    client_len = sizeof(client_address);
                    client_sockfd = accept(server_sockfd,(struct sockaddr *)&client_address, &client_len);
		     DBG("client_sockfd = %d \n",client_sockfd );
                    FD_SET(client_sockfd, &readfds);
                    printf("adding client on fd %d\n", client_sockfd);
                }

/*  If it isn't the server, it must be client activity.
    If close is received, the client has gone away and we remove it from the descriptor set.
    Otherwise, we 'serve' the client as in the previous examples.  */

                else {
                    ioctl(fd, FIONREAD, &nread);
	            //FIONREAD,用于获取输入缓冲区的可用字节数
                    if(nread == 0) {
                        close(fd);
                        FD_CLR(fd, &readfds);
                        DBG("removing client on fd %d\n", fd);
                    }

                    else {
                        read(fd, &ch, 1);
                       // sleep(5);
                        DBG("serving client on fd %d\n", fd);
                        ch++;
                        write(fd, &ch, 1);
                    }
                }
            }
        }
    }
}
[root@localhost chapter15]# 

[root@localhost chapter15]# ./server5&
[1] 16058
[root@localhost chapter15]#  DBG(server5.c, main(), 31): server_sockfd = 3
 DBG(server5.c, main(), 57): server waiting
 ./client3
 DBG(server5.c, main(), 69): FD_SETSIZE = 1024------- 
 DBG(server5.c, main(), 70): 1024 times cycle--- 
 DBG(server5.c, main(), 72): the 0 times cycle--- 
 DBG(server5.c, main(), 72): the 1 times cycle--- 
 DBG(server5.c, main(), 72): the 2 times cycle--- 
 DBG(server5.c, main(), 72): the 3 times cycle--- 
 DBG(server5.c, main(), 74): the 3 times cycle catched------ 
 DBG(server5.c, main(), 82): client_sockfd = 4  
adding client on fd 4 
 DBG(server5.c, main(), 72): the 4 times cycle--- 
 DBG(server5.c, main(), 72): the 5 times cycle--- 
... 
 DBG(server5.c, main(), 72): the 1022 times cycle--- 
 DBG(server5.c, main(), 72): the 1023 times cycle--- 
 DBG(server5.c, main(), 57): server waiting 
 DBG(server5.c, main(), 69): FD_SETSIZE = 1024------- 
 DBG(server5.c, main(), 70): 1024 times cycle--- 
 DBG(server5.c, main(), 72): the 0 times cycle--- 
 DBG(server5.c, main(), 72): the 1 times cycle--- 
 DBG(server5.c, main(), 72): the 2 times cycle--- 
 DBG(server5.c, main(), 72): the 3 times cycle--- 
 DBG(server5.c, main(), 72): the 4 times cycle--- 
 DBG(server5.c, main(), 74): the 4 times cycle catched------ 
 DBG(server5.c, main(), 103): serving client on fd 4 
 DBG(server5.c, main(), 72): the 5 times cycle--- 
 DBG(server5.c, main(), 72): the 6 times cycle---
... 
 DBG(server5.c, main(), 72): the 142 times cycle--- 
char from server = B 
 DBG(server5.c, main(), 72): the 143 times cycle---
... 
 DBG(server5.c, main(), 72): the 1022 times cycle--- 
 DBG(server5.c, main(), 72): the 1023 times cycle--- 
 DBG(server5.c, main(), 57): server waiting 
 DBG(server5.c, main(), 69): FD_SETSIZE = 1024------- 
 DBG(server5.c, main(), 70): 1024 times cycle--- 
 DBG(server5.c, main(), 72): the 0 times cycle--- 
 DBG(server5.c, main(), 72): the 1 times cycle--- 
 DBG(server5.c, main(), 72): the 2 times cycle--- 
 DBG(server5.c, main(), 72): the 3 times cycle--- 
 DBG(server5.c, main(), 72): the 4 times cycle--- 
 DBG(server5.c, main(), 74): the 4 times cycle catched------ 
 DBG(server5.c, main(), 97): removing client on fd 4 
 DBG(server5.c, main(), 72): the 5 times cycle--- 
... 
 DBG(server5.c, main(), 72): the 1022 times cycle--- 
 DBG(server5.c, main(), 72): the 1023 times cycle--- 
 DBG(server5.c, main(), 57): server waiting

 client3和上一篇一样 
line45 向readfds添加一个server_sockfd描述符,从打印结果看出server_sockfd=3 
刚好是2开始的最小数 
刚开始,进程被select阻塞,侦听testfds文件描述符集合的动作,从打印结果FD_SETSIZE=1024, 
可以看出testfds集合默认可以容纳1024个文件的动作,以被select侦听 
当testfds有动作,就检查是哪个fd的动作(由于testfds里面现还未添加client_sockfd,大半是server_sockfd动作) 
而server_sockfd的动静肯定是有客户请求连接 
于是用accept函数请求连接,accept会自动复制一份server_sockfd成为client_sockfd 
以后进程自动用client_sockfd与这个客户通信 
从打印结果看client_sockfd=4 
将client_sockfd加入testfds,现在集合里面有2个sockets描述符了 
... 

进程执行1024次检查之后,又被select阻塞,

testfds第2次动作是4号sockets引起,由于客户已经将数据写入,4号sockets里面有数据了
nread>0,可判断此种情况
...

testfds第3次动作是4号sockets引起,由于客户读取过数据已经退出, 
nread=0,可判断此种情况 

可以看出,testfds有三次动作 
而每次testfds有动作,进程都要执行1024次循环去检查testfds中的具体哪个文件有动作 
这个有点浪费,不如直接去检查3和4号sockets 
在多客户的情况下,检查到最大的那个client_sockfd就可以了 

FD_ISSET 检查指定fd是否属于指定set 

       Four macros are provided to manipulate the sets.   FD_ZERO()  clears  a 
       set.   FD_SET()  and  FD_CLR() respectively add and remove a given file 
       descriptor from a set.  FD_ISSET() tests to see if a file descriptor is 
       part of the set; this is useful after select() returns. 
... 






poll模型如下 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include <netinet/in.h>
#include <sys/socket.h>
#include <poll.h>
#include <unistd.h>
#include <sys/types.h>

#define IPADDRESS   "127.0.0.1"
#define PORT        8787
#define MAXLINE     1024
#define LISTENQ     5
#define OPEN_MAX    1000
#define INFTIM      -1

//函数声明
//创建套接字并进行绑定
static int socket_bind(const char* ip,int port);
//IO多路复用poll
static void do_poll(int listenfd);
//处理多个连接
static void handle_connection(struct pollfd *connfds,int num);

int main(int argc,char *argv[])
{
    int  listenfd,connfd,sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    listenfd = socket_bind(IPADDRESS,PORT);
    listen(listenfd,LISTENQ);
    do_poll(listenfd);
    return 0;
}

static int socket_bind(const char* ip,int port)
{
    int  listenfd;
    struct sockaddr_in servaddr;
    listenfd = socket(AF_INET,SOCK_STREAM,0);
    if (listenfd == -1)
    {
        perror("socket error:");
        exit(1);
    }
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1)
    {
        perror("bind error: ");
        exit(1);
    }
    return listenfd;
}

static void do_poll(int listenfd)
{
    int  connfd,sockfd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    struct pollfd clientfds[OPEN_MAX];
    int maxi;
    int i;
    int nready;
    //添加监听描述符
    clientfds[0].fd = listenfd;
    clientfds[0].events = POLLIN;
    //初始化客户连接描述符
    for (i = 1;i < OPEN_MAX;i++)
        clientfds[i].fd = -1;
    maxi = 0;
    //循环处理
    for ( ; ; )
    {
        //获取可用描述符的个数
        nready = poll(clientfds,maxi+1,INFTIM);
        if (nready == -1)
        {
            perror("poll error:");
            exit(1);
        }
        //测试监听描述符是否准备好
        if (clientfds[0].revents & POLLIN)
        {
            cliaddrlen = sizeof(cliaddr);
            //接受新的连接
            if ((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen)) == -1)
            {
                if (errno == EINTR)
                    continue;
                else
                {
                   perror("accept error:");
                   exit(1);
                }
            }
            fprintf(stdout,"accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
            //将新的连接描述符添加到数组中
            for (i = 1;i < OPEN_MAX;i++)
            {
                if (clientfds[i].fd < 0)
                {
                    clientfds[i].fd = connfd;
                    break;
                }
            }
            if (i == OPEN_MAX)
            {
                fprintf(stderr,"too many clients.\n");
                exit(1);
            }
            //将新的描述符添加到读描述符集合中
            clientfds[i].events = POLLIN;
            //记录客户连接套接字的个数
            maxi = (i > maxi ? i : maxi);
            if (--nready <= 0)
                continue;
        }
        //处理客户连接
        handle_connection(clientfds,maxi);
    }
}

static void handle_connection(struct pollfd *connfds,int num)
{
    int i,n;
    char buf[MAXLINE];
    memset(buf,0,MAXLINE);
    for (i = 1;i <= num;i++)
    {
        if (connfds[i].fd < 0)
            continue;
        //测试客户描述符是否准备好
        if (connfds[i].revents & POLLIN)
        {
            //接收客户端发送的信息
            n = read(connfds[i].fd,buf,MAXLINE);
            if (n == 0)
            {
                close(connfds[i].fd);
                connfds[i].fd = -1;
                continue;
            }
           // printf("read msg is: ");
            write(STDOUT_FILENO,buf,n);
            //向客户端发送buf
            write(connfds[i].fd,buf,n);
        }
    }
}




epoll模型如下 

#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#include "sys/epoll.h"
#include <netinet/in.h>   
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <netinet/ip.h>0
#include <arpa/ftp.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>

#define MAXEVENTS 64
 
static int
create_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;
}

static int
make_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;
}


int
main (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, 1024);
  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)//如果返回的是sfd,说明是新连接请求,这点和select的返回一致
        {
              /* 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;
}

参考文献:

http://www.voidcn.com/article/p-nbdiazny-wm.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值