天天看点

IO多路复用--poll详细介绍与编程使用

引入poll

相对于select来说,poll 也是在指定时间内论询一定数量的文件描述符,来测试其中是否有就绪的;比起select来,poll代码少,也方便。使用方式的区别也并不大。但是比select要灵活。

poll是一个系统调用,其内核入口函数为sys_poll,sys_poll几乎不做任何处理直接调用do_sys_poll,do_sys_poll的执行过程可以分为三个部分:
   1,将用户传入的pollfd数组拷贝到内核空间,因为拷贝操作和数组长度相关,时间上这是一个O(n)操作,这一步的代码在do_sys_poll中包括从函数开始到调用do_poll前的部分。
   2,查询每个文件描述符对应设备的状态,如果该设备尚未就绪,则在该设备的等待队列中加入一项并继续查询下一设备的状态。查询完所有设备后如果没有一个设备就绪,这时则需要挂起当前进程等待,直到设备就绪或者超时,挂起操作是通过调用schedule_timeout执行的。设备就绪后进程被通知继续运行,这时再次遍历所有设备,以查找就绪设备。这一步因为两次遍历所有设备,时间复杂度也是O(n),这里面不包括等待时间。相关代码在do_poll函数中。
   3,将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向用户空间拷贝数据与剥离等待队列等操作的的时间复杂度同样是O(n),具体代码包括do_sys_poll函数中调用do_poll后到结束的部分。
           

poll函数

头文件包含:#include <poll.h>

函数原型:int poll(struct pollfd fds, nfds_t nfds, int timeout);

struct pollfd 定义为:

struct pollfd {

int fd; / file descriptor /

short events; / requested events /

short revents; / returned events */

};

1.fd 为文件描述符,events 告诉poll 监听fd 上哪些事件,它是一系列事件按位或。revents 由内核修改,来通知应用程序fd 上实际上发生了哪些事件。

在《unix环境高级编程》中有一张events取值的表,如下:

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

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

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

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

POLLOUT :可写普数据,不阻塞

POLLWRNORM:与POLLOUT相同

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

其次revents还有下面取值

POLLERR :已出错

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

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

2.nfds 为要监视的描述符的数目。

3.timeout 为poll的超时时间,单位毫秒。timeout 为-1时,poll永远阻塞,直到有事件发生。timeout为0时,poll立即返回。

返回值:

返回值>0,表示已侦听到events指定的事件或者出错返回给revents;

返回值=0,表示timeout时间到或者出错返回给revents;

返回值<0,也就是-1,表示出错。

编程使用poll1

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <stropts.h>
#include <sys/poll.h>
#include <sys/stropts.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <poll.h>

#define BUFSIZE 1024

int main(int argc, char *argv[])
{
char buf[BUFSIZE];
int bytes;
struct pollfd *pollfd;
int i=0;
int nummonitor=0;
int numready;
int errno;
char *str;
if(argc != 3)
{
fprintf(stderr,"Usage:the argc num error\n");
exit(1);
}


if((pollfd = (struct pollfd*)calloc(2, sizeof(struct pollfd))) == NULL) //为struct pollfd分配空间
exit(1);
for(i; i<2; i++) //初始化化struct pollfd结构

{
str = (char*)malloc(14*sizeof(char));
memcpy(str,"./",3); 
strcat(str,argv[i+1]);//注意,需要把路劲信息放到str中,否则opne("./argv[i]",O_RDONLY)会出错
printf("str=%s\n",str);//原因在于,在” “之中的argv[i]是字符串,不会用变量代替argv[i].
(pollfd+i)->fd = open(str,O_RDONLY);
if((pollfd+i)->fd >= 0)
fprintf(stderr, "open (pollfd+%d)->fd:%s\n", i, argv[i+1]); 
nummonitor++;
(pollfd+i)->events = POLLIN;
}
printf("nummonitor=%d\n",nummonitor);

while(nummonitor > 0)
{
numready = poll(pollfd, 2, -1);
if ((numready == -1) && (errno == EINTR))
continue;//被信号中断,继续等待
else if (numready == -1)
break; //poll真正错误,推出
printf("numready=%d\n",numready);
for (i=0;nummonitor>0 && numready>0; i++)
{
if((pollfd+i)->revents & POLLIN)
{

bytes = read(pollfd[i].fd, buf, BUFSIZE);
numready--;
printf("pollfd[%d]->fd read buf:\n%s \n", i, buf);
nummonitor--;
}
}
}
for(i=0; i<nummonitor; i++)
close(pollfd[i].fd);
free(pollfd);
return 0;
}
           

运行结果1

test1 和test2为当前路径下的文件,里面均有内容

test1:

IO多路复用--poll详细介绍与编程使用

test2:

IO多路复用--poll详细介绍与编程使用
IO多路复用--poll详细介绍与编程使用

编程2–TCP回射服务器程序

服务器端:poll_server.c

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


#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.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   3
#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);
        }
    }
}


           

客户端:poll_client.c

#include <netinet/in.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>

#define MAXLINE     1024
#define IPADDRESS   "127.0.0.1"
#define SERV_PORT   8787

#define max(a,b) (a > b) ? a : b

static void handle_connection(int sockfd);

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);
    connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    //处理连接描述符
    handle_connection(sockfd);
    return 0;
}

static void handle_connection(int sockfd)
{
    char    sendline[MAXLINE],recvline[MAXLINE];
    int     maxfdp,stdineof;
    struct pollfd pfds[2];
    int n;
    //添加连接描述符
    pfds[0].fd = sockfd;
    pfds[0].events = POLLIN;
    //添加标准输入描述符
    pfds[1].fd = STDIN_FILENO;
    pfds[1].events = POLLIN;
    for (; ;)
    {
        poll(pfds,2,-1);
        if (pfds[0].revents & POLLIN)
        {
            n = read(sockfd,recvline,MAXLINE);
            if (n == 0)
            {
                    fprintf(stderr,"client: server is closed.\n");
                    close(sockfd);
            }
            write(STDOUT_FILENO,recvline,n);
        }
        //测试标准输入是否准备好
        if (pfds[1].revents & POLLIN)
        {
            n = read(STDIN_FILENO,sendline,MAXLINE);
            if (n  == 0)
            {
                shutdown(sockfd,SHUT_WR);
        continue;
            }
            write(sockfd,sendline,n);
        }
    }
}



           

运行结果2

因为在程序中设置了最大连接数为3,所以当第三个连接请求到来的时候,服务器端关闭,客户端收到拒绝连接的回应,然后每个客户端均可收到消息,客户端closed.

IO多路复用--poll详细介绍与编程使用
IO多路复用--poll详细介绍与编程使用

继续阅读