IO多路复用--poll

Posted harvyxu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
} 
View Code

运行结果:

[[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.

 

    

  


以上是关于IO多路复用--poll的主要内容,如果未能解决你的问题,请参考以下文章

Linux IO多路复用 poll

IO多路复用——poll

IO多路复用 select() poll() epoll()

多路转接(IO复用)接口介绍

IO多路复用--poll

IO多路复用之poll