IO复用之poll

poll()函数

         poll在本质上和select没有什么区别,它将用户传入的数组复制到内核空间,然后查询每个描述符对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有描述符后发现没有设备就绪,则挂起当前进程,直到设备就绪或者等待超时,被唤醒后它又要再次遍历描述符。

         poll()函数用来等待某个文件描述符上的某个时间的发生,其原型为:

#include <poll.h>

int poll(struct pollfd* fds,nfds_t nfds,int timeout);

         参数说明:

         1.fds是一个poll()函数监听的struct pollfd结构体类型的数组。每一个元素中,包含了三部分内容:文件的描述符、监听的事件集合、返回的事件集合。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd的条件。存放需要监测其状态的socket描述符,每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便,特别是对于socket连接比较多的情况下,在一定程度上可以提高处理的效率。这正是poll()函数与select()的不同之处。select()函数调用之后会清空它所监测的socket描述符集合,导致每次调用select()函数之前都必须把socket描述符重新加载到待监测的集合中。

         pollfd结构体定义如下:

struct pollfd
{
         int fd;                        //文件描述符
         short events;   //等待的事件集合
         short revents; //实际发生的事件集合
};

         每一个pollfd结构体指定了一个被监测的文件的描述符,可以传递多个结构体,监测多个文件描述符。每个结构体的events域是监测该文件描述符的事件的掩码,由用户来手动设置。revents域是文件描述符中的操作结果事件掩码,内核在调用返回时设置这个域值。events域中请求的任何事件都可能在revents域中返回。合法的事件有:

POLLIN                              有数据(包括普通数据和优先数据)可读

POLLRDNORM                          有普通数据可读

POLLRDBAND                   有优先数据可读

POLLPRI                             有紧迫数据可读

POLLOUT                                   写数据不会造成阻塞

POLLWRNORM                写普通数据不会造成阻塞

POLLWRBAND                          写优先数据不会造成阻塞

POLLMSGSIGPOLL           有消息可用

POLLERR                           文件描述符发生错误

POLLHUP                           文件描述符挂起

POLLNVAL                         文件描述符非法

         其中POLLERR、POLLHUP、POLLNVAL只能作为描述符的返回结果存储在revents中,而不能作为测试条件用于events中。

         2.nfds与select()函数中nfds的含义一样,表示最大的描述符加1。

         3.timeout表示poll()函数的等待时间,单位是毫秒,用来指定poll()函数在返回前没有接收到事件时应该等待的时间。如果设置timeout=0,则表示poll()函数不等待,立即返回,不阻塞进程;如果设置timeout=-1,则表示poll()函数永远等待;若设置timeout>0,则表示poll()函数等待指定的毫秒数。

         返回值:若poll()函数成功执行,则返回结构体中revents域不为0的文件描述符的个数;如果超时,则返回0;如果失败,则返回-1,并且设置errno值为下列值之一:

EBADF                                                 一个或多个结构体中指定的文件描述符无效

EFAULTfds                                          指针指向的地址超出进程的地址空间

EINTR                                                  请求的事件之前产生一个信号,调用可以重新发起

EINVALnfds                                        参数超出PLIMIT_NOFIFE值

ENOMEM                                                    可用内存不足,无法完成请求

         如果是对一个描述符上的多个事件感兴趣,可以把这些常量标记之间进行按位或运算。比如,对socket描述符上的读、写、异常事件感兴趣,则可记为:

struct pollfd fd;

fd[i].events = POLLIN | POLLOUT | POLLERR;

         当poll()函数返回时,要判断所监测的socket描述符上发生的事件,则可记为:

struct pollfd fd;

//监测可读TCP连接请求

if((fd[i].revents & POLLIN) == POLLIN)

{

         //调用accept()函数接收连接请求

}

//监测可写

if((fd[i].revents & POLLOUT) == POLLOUT)

{

         //发送数据

}

//监测异常

if((fd[i].revents & POLLOUT) == POLLOUT)

{

         //异常处理

}

 

         poll的优点:poll不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。pollfd结构包含了要监测的 events和发生的revents,不再使用select”参数-值”传递的方式,接口使用比select方便。而且poll没有最大描述符数量的限制,但是socket描述符数量过大后性能也是会下降。

         poll的缺点:poll的缺点主要是当连接的socket描述符数目增多时才显现。和select()函数一样,poll()返回后,需要轮询pollfd来获取就绪的描述符。每次调用poll()都需要把大量的pollfd结构从用户态复制到内核中。同时连接的大量客户端在同一时刻可能只有很少的客户端处于就绪状态,因此随着监测的描述符数量的增长,其效率也会线性下降。

 

案例:poll监测标准输入

checkstdin.c

#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
int main()
{
    struct pollfd poll_fd;
    //监测的socket描述符
    poll_fd.fd = 0;
    //监测的事件
    poll_fd.events = POLLIN;
    while(1)
    {
        //char buffer[1024] = {0};
        int npoll = poll(&poll_fd,1,3000);
        switch(npoll)
        {
            case 0:
                perror("timeout");
                break;
            case -1:
                perror("poll");
                break;
            default:
            {
                if(poll_fd.revents == POLLIN)
                {
                    char buffer[1024] = {0};

                    int nread = read(poll_fd.fd,buffer,sizeof(buffer) - 1);
                    if(nread > 0)
                    {
                        printf("stdin input:%s",buffer);
                    }
                }
            }
            break;
        }
    }
    return 0;
}

 

案例2:Poll()实现网络编程

Poll_Server1.c

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

typedef struct pollfd poll_fd;

//初始化结构体数组,数组中存放描述符、等待的事件、返回的事件
void Init(poll_fd* fd_list,int fd_len)
{
    //初始化时将描述符置为-1,等待的事件为0,返回的事件也为0
    int i;
    for(i = 0;i < fd_len;i++)
    {
        fd_list[i].fd = -1;
        fd_list[i].events = 0;
        fd_list[i].revents = 0;
    }
}

//将socket加入到结构体数组中
void Add(int listen_fd,poll_fd* fd_list,int fd_len)
{
    int i;
    for(i = 0;i < fd_len;i++)
    {
        if(fd_list[i].fd == -1)
        {
            fd_list[i].fd = listen_fd;
            //由于我们只监测读事件,所以设置等待的事件为POLLIN表示有数据可读
            fd_list[i].events = POLLIN;
            break;
        }
    }
}

int main()
{
    //poll监测的数组
    poll_fd fd_list[1024];
    int fd_len = sizeof(fd_list) / sizeof(fd_list[0]);
    //初始化此数组
    Init(fd_list,fd_len);

    //地址结构
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8080);

    //创建socket
    int listen_fd = socket(PF_INET,SOCK_STREAM,0);
    if(listen_fd < 0)
    {
        perror("socket");
        return -1;
    }

    //绑定 
    int rbind = bind(listen_fd,(struct sockaddr*)&addr,sizeof(addr));
    if(rbind < 0)
    {
        perror("bind");
        return -1;
    }

    //监听
    int rlisten = listen(listen_fd,5);
    printf("服务器正在监听8080端口......\n");
    if(rlisten < 0)
    {
        perror("listen");
        return -1;
    }

    //将socket加入到结构体数组中
    Add(listen_fd,fd_list,fd_len);
    
    //当有数据可读时,全部读入,并原样写出
    while(1)
    {
        //用poll()函数来监测描述符上某个事件的发生,等待时间设置为永远等待
        int rpoll = poll(fd_list,fd_len,-1);
        if(rpoll < 0)
        {
            perror("poll");
            continue;
        }
        if(rpoll == 0)
        {
            perror("timeout");
            continue;
        }
        
        //如果poll()函数正常返回,则表示结构体数组中revents域中的文件描述符个数不为0,则处理
        int i = 0;
        for(i = 0;i < fd_len;i++)
        {
            //如果无文件描述符,不处理
            if(fd_list[i].fd == -1)
            {
                continue;
            }
            //如果事件不是POLLIN事件,不处理
            if(!(fd_list[i].revents & POLLIN))
            {
                continue;
            }

            //处理监听的描述符
            if(fd_list[i].fd == listen_fd)
            {
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);
                //接收
                int client_fd = accept(listen_fd,(struct sockaddr*)&clientaddr,&len);
                if(client_fd < 0)
                {
                    perror("accept");
                    continue;
                }
                
                printf("client %s:%d has connected!\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));

                //将socket描述符加入到监测数组中
                Add(client_fd,fd_list,fd_len);
            }
            else
            {
                //处理客户端发送来的数据
                char buffer[1024] = {0};
                //读取客户端数据
                int rread = read(fd_list[i].fd,buffer,sizeof(buffer) - 1);
                switch(rread)
                {
                    case -1:
                        perror("read");
                        break;
                    case 0:
                        printf("客户端关闭了连接......\n");
                        close(fd_list[i].fd);
                        fd_list[i].fd = -1;
                        break;
                    default:
                        printf("Client:%s",buffer);
                        //将读到的数据写出
                        write(fd_list[i].fd,buffer,sizeof(buffer) - 1);
                        break;
                }
            }
        }
    }
    return 0;
}

Poll_Client1.c

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

int main()
{
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8080);

    int listen_fd = socket(AF_INET,SOCK_STREAM,0);
    if(listen_fd < 0)
    {
        perror("socket");
        return -1;
    }

    int ret = connect(listen_fd,(struct sockaddr*)&addr,sizeof(addr));
    if(ret < 0)
    {
        perror("connect");
        return -1;
    }

    while(1)
    {
        fflush(stdout);
        //从标准输入中读数据
        char buffer[1024] = {0};
        read(0,buffer,sizeof(buffer) - 1);
        int rwrite  = write(listen_fd,buffer,strlen(buffer));
        if(rwrite < 0)
        {
            perror("write");
            continue;
        }
        
        int rread = read(listen_fd,buffer,sizeof(buffer) - 1);
        if(rread < 0)
        {
            perror("read");
            continue;
        }
        if(rread == 0)
        {
            printf("服务器端关闭连接......\n");
            break;
        }
        printf("server:%s",buffer);
    }
    close(listen_fd);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值