Linux网络编程 --- IO复用之poll系统调用详解

poll系统调用和select系统调用相似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。

poll系统调用原型:

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

1、fds参数是一个pollfd结构类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件

      pollfd结构体定义如下:

struct pollfd
{
    int fd;//文件描述符
    short events;//注册的事件
    short revents;//实际发生的事件,由内核填充
};

其中,fd成员指定文件描述符;events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或;revents成员则由内核修改,以通知应用程序fd上实际发生了哪些事件。

              事         件                       描                                    述
POLLIN 数据(包括普通数据和优先数据)可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读(Linux不支持)
POLLPRI 高优先级数据可读,比如TCP带外数据
POLLOUT 数据(包括普通数据和优先数据)可写
  POLLWRNORM 普通数据可写
  POLLWRBAND 优先级带数据可写
POLLRDHUP TCP连接被对方关闭,或者对方关闭了写操作
POLLERR 错误
  POLLHUP 挂起。比如管道的写端被关闭后,读端描述符上将收到POLLHUP事件 POLLNVAL 文件描述符没有打开
POLLNVAL 文件描述符没有打开

       

上表中,POLLRDNORM、POLLRDBAND、POLLWRNORM、POLLWRBAND由XOPEN规范定义。它们实际上是将POLLIN和POLLOUT事件分得更细致,以区分对待普通数据和优先数据,但Linux不完全支持。

通常,应用程序需要根据recv调用的返回值来区分socket上接收到的是有效数据还是对方关闭连接的请求,并做相应的处理。不过,自LInux内核2.6.17开始,GNU为poll系统调用增加了一个POLLRDHUP时间,它在socket上接收到对方关闭连接的请求之后触发。但使用POLLRDHUP时间时,我们需要在代码最开始处定义_GNU_SOURCE。

2、nfds参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下:

typedef unsigned long int nfds_t;

3、timeout参数指定poll的超时值,单位是毫秒(1s = 1000ms)。当timeout为-1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。

4、poll系统调用的返回值与select相同:

  • poll成功时返回就绪(可读、可写和异常)文件描述符的总数。
  • 如果在超时时间内没有任何文件描述符就绪。poll将返回0。
  • 失败时返回-1并设置errno。如果在poll等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR

5、poll 和 select的相同点和不同点

不同点:

  • poll       long(可描述的文件描述符多)     POLLIN、POLLOUT等,类型多
  • select   1024bit                                    读/写/异常

相同点:

  • select/poll 描述符和事件传给内核
  • 内核实现:轮询检测,时间效率相当O(n)
  • 找到就绪|检索就绪描述符O(n)
  • 内核返回之后仍会遍历一遍

6、使用poll编写实现服务器

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

#define MAXFD 10
int create_sockfd();//函数声明

//向fds数组中添加文件描述符fd和事件信息
void fds_add(struct pollfd fds[],int fd)
{
    int i = 0;
    for(;i<MAXFD;i++)
    {
        if(fds[i].fd == -1)
        {
            fds[i].fd = fd;
            fds[i].events = POLLIN;
            fds[i].revents = 0;
            break;
        }
    }
}
//删除fds数组中文件描述符fd和事件信息
void fds_del(struct pollfd fds[],int fd)
{
    int i = 0;
    for(;i<MAXFD;i++)
    {
        if(fds[i].fd == fd)
        {
            fds[i].fd = -1;
            fds[i].events = 0;
            fds[i].revents = 0;
        }
    }
}
//初始化fds数组
void fds_init(struct pollfd fds[])
{
    int i = 0;
    for(;i<MAXFD;i++)
    {
        fds[i].fd = -1;
        fds[i].events = 0;
        fds[i].revents = 0;
    }
}

int main()
{
    int sockfd = create_sockfd();
    assert(sockfd != -1);

    struct pollfd fds[MAXFD];//定义pollfd类型的结构体数组fds
    
    fds_init(fds);//初始化fds数组
    fds_add(fds,sockfd);//添加信息

    while(1)
    {
        int n = poll(fds,MAXFD,5000);//poll系统调用
        if(n == -1)//失败
        {
            perror("poll error");
        }
        else if(n == 0)//超时
        {
            printf("time out\n");
        }
        else//当前数组有文件描述符就绪
        {
            int i = 0;
            for(;i<MAXFD;i++)//循环遍历数组
            {
                if(fds[i].fd == -1)
                {
                    continue;
                }
                
                if(fds[i].revents & POLLIN)
                {
                    /*
						此时有两种情况,若fds[i].fd == sockfd
						说明监听队列中有连接待处理,使用accept建立一个连接
						否则,说明没有新连接产生,是有客户端发来了数据,直接使
					    用recv接收客端数据,并打印,就ok
					*/
                    if(fds[i].fd == sockfd)
                    {
                        //accept
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);

                        //接收一个套接字已建立的连接,得到连接套接字connfd 
                        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
                        if(c<0)
                        {
                            continue;
                        }
                        printf("accept c = %d\n",c);

                        fds_add(fds,c);//将新的连接套接字加入fds数组                   
                    }
                    else
                    {
                        //recv
                        char buff[128] = {0};
                        int num = recv(fds[i].fd,buff,127,0);//接受客户端发来的数据

                        if (num <= 0)//说明客户端已经关闭
                        {
                            //close
                            close(fds[i].fd);//先关闭文件描述符
                            fds_del(fds,fds[i].fd);//将此文件描述符在fds数组里删除
                            printf("one client over\n");
                        }
                        else
                        {
                            printf("recv(%d) = %s\n",fds[i].fd,buff);
                            send(fds[i].fd,"ok",2,0);
                        }
                    }
                }
            }
        }
    }

}

int create_sockfd()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建监听套接字
    if(sockfd == -1)
    {
        return -1;
    }

    struct sockaddr_in saddr;//socket专用地址信息设置
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //命名套接字,将socket专用地址绑定到socket文件描述符上
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res == -1)
    {
        return -1;
    }

    listen(sockfd,5);//创建监听队列

    return sockfd;
}

猜你喜欢

转载自blog.csdn.net/Disremembrance/article/details/89454862