I/O复用(poll)

  上文说到的实现IO复用的函数中的select,本文接着介绍第二种poll。

poll

  Poll相对于select来说突破了监听的文件描述符上限1024,最大文件描述符是系统所能允许的最大值,可以通过查看proc/sys/fs/file-max文件查看,这个值也可以改(通过limits.conf)。另一方面是实现了监听和就绪事件的分离。其他和select类似,也是在指定时间内轮询一定数量的文件描述符,检测其中是否有就绪的。还是先从API说起。

poll API

函数原形:

# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);

参数

poll函数的第一个参数fds是struct pollfd结构体类型的事件集,一般传入一个该类型的数组。
  pollfd结构体定义如下:

struct pollfd {

int fd;         /* 文件描述符 */
short events;         /* 等待的事件 */
short revents;       /* 实际发生了的事件,由内核填充 */
} ; 

该结构体有三个成员:
  第一个fds是要监听的文件描述符,
  第二个events是要监听的事件(POLLIN、POLLOUT、POLLERR),
  第三个revents是监控事件中满足条件返回的事件,内核通过修改这个参数来反馈监听的就绪事件。
  事件主要有以下几个:
  
  POLLIN         有数据可读。

  POLLRDNORM      有普通数据可读。

  POLLRDBAND      有优先数据可读。

  POLLPRI         有紧迫数据可读。

  POLLOUT       写数据不会导致阻塞。

  POLLWRNORM      写普通数据不会导致阻塞。

  POLLWRBAND      写优先数据不会

第二个参数nfds是监听的文件描述符的个数,
第三个参数timeout是设置超时时间,用法和select一样。是一个struct timeval结构体指针,该结构体定义如下:

struct timeval{

  long tv_sec;    //second

  long tv_usec;   //minisecond

  }

超时时间可以设置到毫秒级别,有三种设置情况:

  NULL:阻塞等待,直到某个文件描述符上发生了事件。

  0:仅检测描述符集合的状态,然后立即返回。

  > 0: 指定超时时间,如果在该时间段里没有事件发生,select将超时返回。

返回值

  成功: poll()返回结构体中revents域不为0的文件描述符个数;
  失败: poll()返回-1,并设置errno;
  超时: poll()返回0;

不足

  虽然epoll突破了最大监听1024个文件描述符的限制,但它还是存在缺点就是当大量连接上只有少量活跃的连接时,它也是采用轮询的机制每次还要遍历整个数组,效率太低。在下文中,将介绍一种更为高效的实现IO复用的函数epoll。

程序示例

  最后还是以一个poll实现的服务器的示例程序结束本文:
  

#include<stdio.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<ctype.h>
#include<poll.h>


#define   MYPORT  8888
#define  BACKLOG  10
#define MAXDATASIZE 1024
#define FILEMAX 3000

int main()
{
    int i,j,maxi;
    int listenfd,connfd,sockfd; //定义套接字描述符
    int nready; //接受pool返回值
    int numbytes; //接受recv返回值

    char buf[MAXDATASIZE]; //发送缓冲区
    struct pollfd client[FILEMAX]; //struct pollfd* fds

    //定义IPV4套接口地址结构
    struct sockaddr_in seraddr;        //service 地址

    struct sockaddr_in cliaddr;     //client 地址
    int sin_size;

    //初始化IPV4套接口地址结构

    seraddr.sin_family =AF_INET; //指定该地址家族
    seraddr.sin_port =htons(MYPORT);  //端口
    seraddr.sin_addr.s_addr = INADDR_ANY;  //IPV4的地址
    bzero(&(seraddr.sin_zero),8);

    //socket()函数
    if((listenfd = socket(AF_INET,SOCK_STREAM,0))==-1)
    {
        perror("socket");
        exit(1);
    }

    //地址重复利用
    int on = 1;
    if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)
    {
        perror("setsockopt");
        exit(1);
    }

    //bind()函数
    if(bind(listenfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr))==-1)
    {
        perror("bind");
        exit(1);
    }

    //listen()函数
    if(listen(listenfd,BACKLOG)==-1)
    {
        perror("listen");
        exit(1);
    }

    client[0].fd = listenfd; //将listenfd加入监听序列
    client[0].events = POLLIN; //监听读事件

    //初始化client[]中剩下的元素
    for(i = 1;i < FILEMAX;i++)
    {
        client[i].fd = -1; //不能用0,其也是文件描述符
    }

    maxi = 0; //client[]中最大元素下标
    while(1)
    {
        nready = poll(client,maxi+1,-1);//阻塞监听
        if(nready < 0)
        {
            perror("poll error!\n");
            exit(1);
        }

        if(client[0].revents & POLLIN)//位与操作;listenfd的读事件就绪
        {
            sin_size = sizeof(cliaddr);
            if((connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&sin_size))==-1)
            {
                perror("accept");
                exit(1);
            }
            printf("client IP: %s\t PORT : %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
            //将sockfd加入监听序列
            for(i = 1;i < FILEMAX;i++)
            {
                if(client[i].fd < 0)
                {
                    client[i].fd = connfd;
                    break;
                }
            }
            if(i == FILEMAX)
            {
                perror("too many clients!\n");
                exit(1);
            }

            client[i].events = POLLIN;//监听connfd的读事件
            if(i > maxi)
            {
                maxi = i;
            }

            //判断是否已经处理完事件
            if(--nready == 0)
            {
                continue;
            }
        }

        //检测客户端是否发来消息
        for(i = 1;i <= maxi;i++)
        {
            if((sockfd = client[i].fd) < 0)
            {
                continue;
            }
            if(client[i].revents & POLLIN)
            {
                memset(buf,0,sizeof(buf));
                numbytes = recv(sockfd,buf,MAXDATASIZE,0);

                if(numbytes < 0)
                {
                    if(errno == ECONNRESET)  //RET标志
                    {
                        printf("client[%d] aborted connection!\n",i);
                        close(sockfd);
                        client[i].fd = -1;
                    }
                    else
                    {
                        perror("recv error!\n");
                        exit(1);
                    }
                }
                else if(numbytes == 0)
                {
                    printf("client[%d],close!\n",i);
                    close(sockfd);
                    client[i].fd = -1;
                }
                else
                {
                    send(sockfd,buf,numbytes,0);
                }
                if(--nready == 0)
                {
                    break;
                }

            }
        }
    }


    return 0;
}

猜你喜欢

转载自blog.csdn.net/z_ryan/article/details/80879265