IO多路复用--poll

1 基本知识

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

2 poll函数

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O.

参数描述: 

fds: 需要监控的文件描述符集,它是由结构体  struct pollfd 构成;

nfds: nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;

timeout: poll函数调用阻塞的时间,单位:毫秒;

返回值和错误码:成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:

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

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

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

  EINVALnfds  参数超出PLIMIT_NOFILE值。

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

pollfd结构体定义如下:

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};

The field fd contains a file descriptor for an open file. If this field is negative, then the corresponding eventsfield is ignored and the revents field returns zero. (This provides an easy way of ignoring a file descriptor for a single poll() call: simply negate the fd field.)


The field events is an input parameter, a bit mask specifying the events the application is interested in for the file descriptor fd. If this field is specified as zero, then all events are ignored for fd and revents returns zero.


The field revents is an output parameter, filled by the kernel with the events that actually occurred. The bits returned in revents can include any of those specified in events, or one of the values POLLERRPOLLHUP, or POLLNVAL. (These three bits are meaningless in the events field, and will be set in the revents field whenever the corresponding condition is true.)

If none of the events requested (and no error) has occurred for any of the file descriptors, then poll() blocks until one of the events occurs.

The bits that may be set/returned in events and revents are defined in <poll.h>:


  POLLIN          有数据可读。

  POLLRDNORM         有普通数据可读。

  POLLRDBAND        有优先数据可读。

  POLLPRI         有紧迫数据可读。

  POLLOUT          写数据不会导致阻塞。

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

  POLLWRBAND        写优先数据不会导致阻塞。

  POLLMSGSIGPOLL     消息可用。

3 poll函数的典型应用

下面介绍一个基于poll的echo server;client直接通过telnet连接server:telnet host port,主要包括以下几个

基本功能

(1) client 发送quit,则server主动断开与客服端的连接;

(2) clinet发送其他data到server,则server会原样返回;

注意点:

(1) 由于通过telnet与server通信,telnet client发送过来的字符串的结束符为"\r\n" ,所以判断client的断开信号时,需判断是否为"quit\r\n"

(2) 同样打印客服端的数据时不需要再添加换行符,因为data form telnet client自带的就有;

(3) 由于本文中,每次读数据时都是放在一个公共的缓冲区buffer,所以每次接收数据(read)并处理完(send)后,应重置缓冲区,比如重置受影响的区域(大小size)都为‘\0‘,memset(buffer, 0, size);

 echo server source code -- poll

/* Handle multiple socket connections with select and fd_set on Linux */

#include <stdio.h>
#include <string.h>   //strlen
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h> //FD_SET, FD_ISSET, FD_ZERO macros
#include <poll.h>

#define TRUE   1
#define FALSE  0
#define PORT 8888
#define CLIENT_NUM 5
#define BLOCK_NUM 2
#define BUFFER_SIZE 1024

int main(int argc , char *argv[])
{
    int opt = TRUE;
    int master_socket , addrlen , new_socket , activity, i , valread , sd;
    struct pollfd clientfds[CLIENT_NUM];
    int nfds;
    struct sockaddr_in address;
    char buffer[BUFFER_SIZE+1];  //data buffer of 1K
    int timeout = 5000;          //unit: ms
    char quit[7]="quit\r\n";     //for data from telnet client, the data end with "\r\n"

    //a message retuan to client when it connect to the server.
    char *message = "ECHO Daemon v1.0 \r\n";

    //initialise all clientfds[i].fd to -1 so not checked
    for (i = 0; i < CLIENT_NUM; i++)
    {
        clientfds[i].fd = -1;
    clientfds[i].events = 0;
    }
    printf("size of buffer:%lu\n",sizeof(buffer));
    memset(buffer,0,sizeof(buffer)*sizeof(buffer[0]));

    //create a master socket for listening
    if( (master_socket = socket(AF_INET , SOCK_STREAM , 0)) == 0)
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    /*一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/
    if( setsockopt(master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt)) < 0 )
    {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    //type of socket created
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons( PORT );

    //bind the socket to localhost port 8888
    if (bind(master_socket, (struct sockaddr *)&address, sizeof(address))<0)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    printf("Listener on port %d \n", PORT);

    clientfds[0].fd = master_socket;
    clientfds[0].events = POLLIN;
    nfds = 0;

    //try to specify maximum of BLOCK_NUM pending connections for the master socket
    if (listen(master_socket, BLOCK_NUM) < 0)
    {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    //accept the incoming connection
    addrlen = sizeof(address);
    puts("Waiting for connections ...\n");

    /********************************call poll in a infinite loop*******************************/
    while(TRUE)
    {
        printf("\nlinux poll begin, timeout: 5000ms.\n");
    timeout = 5000;
        //wait for an activity on one of the sockets, timeout is tiemout;
        activity = poll(clientfds, nfds+1, timeout);

        //return value < 0, error happend;
        if ((activity < 0) && (errno!=EINTR))
        {
            printf("poll error");
        }
    //return value = 0, nothing happend, timeout;
    else if(activity == 0){
        //no event happend in specific time, timeout;
        printf("poll return, nothing happend, timeout!\n");
        continue;
    }

        printf("poll return, activity:%d, nfds:%d\n", activity, nfds);
    //If something happened on the master socket , then its an incoming connection
        if (clientfds[0].revents & POLLIN)
        {
            if ((new_socket = accept(master_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0)
            {
                perror("accept error");
                exit(EXIT_FAILURE);
            }

            //inform user of socket number - used in send and receive commands
            printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));

            //send new connection greeting message
            if( send(new_socket, message, strlen(message), 0) != strlen(message) )
            {
                perror("send error");
            }

            puts("Welcome message sent successfully");

            //add new socket to array of sockets
            for (i = 1; i < CLIENT_NUM; i++)
            {
                //if position is empty
                if( clientfds[i].fd == -1 )
                {
                    clientfds[i].fd = new_socket;
                    clientfds[i].events = POLLIN;
                    nfds = nfds > i ? nfds : i;
                    printf("Adding to list of sockets as clientfds[%d].fd = %d\n", i, new_socket);
                    break;
                }
            }
        if (i == CLIENT_NUM) {
            perror("Too many clients");
        exit(EXIT_FAILURE);
        }
        if(--activity <= 0) {
            continue;
        }
        }

        //else there is some IO operation on some other socket :)
        for (i = 1; i <= nfds; i++)
        {
            sd = clientfds[i].fd;
        printf("check client--%d:fd--%d\n", i, clientfds[i].fd);

        if(clientfds[i].fd < 0){
            printf("continue: current clientfds[%d]-->fd:%d/\n", i, clientfds[i].fd);
        }
            if (clientfds[i].revents & POLLIN)
            {
              printf("event pollin happend, waitting to read, clientfds[%d]-->fd:%d\n", i, clientfds[i].fd);
                //Check if it was for closing , and also read the incoming message
                valread = read( sd , buffer, 1024);
        printf("read form client,size:%d\n",valread);
                if (valread <= 0)
                {
                    //Somebody disconnected , get his details and print
                    getpeername(sd , (struct sockaddr*)&address , (socklen_t*)&addrlen);
                    printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));
                    clientfds[i].fd = -1;
                    clientfds[i].events = 0;
                    //Close the socket and mark as -1 in list for reuse
                    close( sd );
                }
                //get message "quit", close the connection;
                else if(valread == strlen(quit) && memcmp(buffer,quit,strlen(quit))==0) {
            printf("client of clientfds[%d]-->fd:%d will be closed.\n", i, sd);
            clientfds[i].fd = -1;
                    clientfds[i].events = 0;
            memset(buffer, 0, valread);
            close(sd);
        }
                //Echo back the message that came in
        else
                {   

                    printf("data come from client:%s",buffer);
                    send(sd , buffer , strlen(buffer) , 0 );
                    memset(buffer,0,valread);
            //printf("sleep 5s\n");
            //sleep(5);
                }
            }
        }
    }

    return 0;
} 

运行结果:

[[email protected] LibEvent_practice]# ./echo-server-poll
size of buffer:1025
Listener on port 8888
Waiting for connections ...

linux poll begin, timeout: 5000ms.
poll return, activity:1, nfds:0
New connection , socket fd is 4 , ip is : 127.0.0.1 , port : 32871
Welcome message sent successfully
Adding to list of sockets as clientfds[1].fd = 4

linux poll begin, timeout: 5000ms.
poll return, activity:1, nfds:1
New connection , socket fd is 5 , ip is : 10.204.214.248 , port : 58983
Welcome message sent successfully
Adding to list of sockets as clientfds[2].fd = 5

linux poll begin, timeout: 5000ms.
poll return, activity:1, nfds:2
check client--1:fd--4
event pollin happend, waitting to read, clientfds[1]-->fd:4
read form client,size:9
data come from client:adfffff
check client--2:fd--5

linux poll begin, timeout: 5000ms.
poll return, nothing happend, timeout!

linux poll begin, timeout: 5000ms.
poll return, activity:1, nfds:2
check client--1:fd--4
check client--2:fd--5
event pollin happend, waitting to read, clientfds[2]-->fd:5
read form client,size:40
data come from client:dddddddddddddddddddddddddddddddddddddd

linux poll begin, timeout: 5000ms.
poll return, activity:1, nfds:2
check client--1:fd--4
check client--2:fd--5
event pollin happend, waitting to read, clientfds[2]-->fd:5
read form client,size:6
client of clientfds[2]-->fd:5 will be closed.

    

  

时间: 2024-09-29 03:50:32

IO多路复用--poll的相关文章

IO多路复用——poll

1.基本知识 poll是Linux中的字符设备驱动中的一个函数.Linux 2.5.44版本后,poll被epoll取代.和select实现的功能差不多,poll的作用是把当前的文件指针挂到等待队列. poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制.poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否

Linux IO多路复用 poll

Linux IO多路复用 poll 之前曾经提到过 select poll 跟select类似,poll改进了select的一个确定,就是poll没有监听上限 不过poll还是需要遍历以及频繁的把数组拷贝到内核空间,在监听较多文件描述符的时候性能会下降 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout); 传递的三个参数 fds:结构体数组 nfds:监听数量,(select 是最大文件描述符的值)

linux设备驱动程序中的阻塞、IO多路复用与异步通知机制

一.阻塞与非阻塞 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回. 非阻塞指不能立刻得到结果之前,该函数不会阻塞当前进程,而会立刻返回. 函数是否处于阻塞模式和驱动对应函数中的实现机制是直接相关的,但并不是一一对应的,例如我们在应用层设置为阻塞模式,如果驱动中没有实现阻塞,函数仍然没有阻塞功能. 二.等待队列 在linux设备驱动程序中,阻塞进程可以使用等待队列来实现. 在内核中,

python之IO多路复用(二)——select、poll、epoll详解

select,poll,epoll都是IO多路复用的机制.I/O多路复用就是通过一种机制使一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的 异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间.   sellect.poll.epoll三者的区别 : select通过一个sel

转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理还是有必要的.下面记录下分别基于Select/Poll/Epoll的echo server实现.Python Select Server,可监控事件数量有限制: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

IO多路复用:select、poll、epoll示例

一.IO多路复用 所谓IO多路复用,就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. Linux支持IO多路复用的系统调用有select.poll.epoll,这些调用都是内核级别的.但select.poll.epoll本质上都是同步I/O,先是block住等待就绪的socket,再是block住将数据从内核拷贝到用户内存. 当然select.poll.epoll之间也是有区别的,如下表: \ select poll e

IO多路复用(二) -- select、poll、epoll实现TCP反射程序

接着上文IO多路复用(一)-- Select.Poll.Epoll,接下来将演示一个TCP回射程序,源代码来自于该博文https://www.cnblogs.com/Anker/p/3258674.html 博主的几篇相关的文章,在这里将其进行了整合,突出select.poll和epoll不同方法之间的比较,但是代码的结构相同,为了突出方法之间的差别,可能有的代码改动的并不合理,实际中使用并非这么写. 程序逻辑 该程序的主要逻辑如下: 服务器: 1. 开启服务器套接字 2. 将服务器套接字加入要

网络通信 --&gt; IO多路复用之select、poll、epoll详解

IO多路复用之select.poll.epoll详解 目前支持I/O多路复用的系统调用有 select,pselect,poll,epoll,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作.但select,pselect,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内

IO多路复用select、poll、epoll的区别

(1)select==>时间复杂度O(n) 它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作.所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长. (2)poll==>时间复杂度O(n) poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的