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 POLLERR, POLLHUP, 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自带的就有;
echo server source code -- poll
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* 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; }
运行结果:
[root@localhost 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.
参考: