1. poll技术
poll函数起源于SVR3,最初局限于流设备。SVR4取消了这种限制,允许poll工作在任何描述符上。poll提供的功能与select类似,不过在处理流设备时,它能够提供额外的信息。
poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
1.1 函数原型
include <poll.h> int poll ( struct pollfd * fds, unsigned int nfds, int timeout); 返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1第一个参数:是指向一个结构数组第一个元素的指针。每个数组元素都是一个pollfd结构,用于指定测试某个给定描述符fd的条件。
struct pollfd {
intfd;/*descriptor to check 文件描述符*/
shortevents;/*events of interest on fd 等待的事件*/
shortrevents;/*events that occurred on fd 实际发生的事件*/
};
每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。
下面列出用于指定events标志以及测试revents标志的一些常值:
- POLLIN 普通或优先级带数据可读。
- POLLRDNORM 普通数据可读。
- POLLRDBAND 优先级带数据可读。
- POLLPRI 高优先级数据可读。
- POLLOUT 普通数据可写。
- POLLWRNORM 普通数据可写不会导致阻塞。
- POLLWRBAND 优先级带数据可写。
- POLLMSGSIGPOLL 消息可用。
- POLLER 指定的文件描述符发生错误。
- POLLHUP 指定的文件描述符挂起事件。
- POLLNVAL 指定的文件描述符非法。
使用poll()和select()不一样,你不需要显式地请求异常情况报告。
- POLLIN | POLLPRI等价于select()的读事件;
- POLLOUT |POLLWRBAND等价于select()的写事件;
- POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM
第二个参数:timeout参数是指定poll函数返回前等待多长时间。它是一个指定等待毫秒数的正值,可能取值为:
timeout参数值 | 说明 |
INFTIM 0 >0 |
永远等待 立即返回,不阻塞进程 等待指定数目的毫秒数 |
INFTIM常值被定义为一个负值。POSIX规范要求在头文件<poll.h>中定义,不过许多系统仍然把它定义在头文件<sys/stropts.h>中。如果两者均包含,依然出现未定义错误,则手动定义其为一个负值即可。
timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
1.2 返回值和错误代码
(1)成功时,poll()返回结构体中revents域不为0的文件描述符个数;(2)如果在超时前没有任何事件发生,poll()返回0;
(3)失败时,poll()返回-1,并设置errno为下列值之一:
- EBADF 一个或多个结构体中指定的文件描述符无效。
- EFAULTfds 指针指向的地址超出进程的地址空间。
- EINTR 请求的事件之前产生一个信号,调用可以重新发起。
- EINVALnfds 参数超出PLIMIT_NOFILE值。
- ENOMEM 可用内存不足,无法完成请求。
2. TCP回射程序实例
2.1 server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <sys/socket.h> #include <poll.h> #include <limits.h> /*for OPEN_MAX*/ #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <fcntl.h> #ifndef OPEN_MAX #define OPEN_MAX 1024 #endif #ifndef INFTIM #define INFTIM -1 #endif #define PORT 8888 #define MAX_LINE 2048 #define LISTENQ 20 int main(int argc , char **argv) { int i, maxi, listenfd, connfd, sockfd; int nready; ssize_t n, ret; struct pollfd client[OPEN_MAX]; char buf[MAX_LINE]; socklen_t clilen; struct sockaddr_in servaddr , cliaddr; /*(1) 得到监听描述符*/ listenfd = socket(AF_INET , SOCK_STREAM , 0); /*(2) 绑定套接字*/ bzero(&servaddr , sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); bind(listenfd , (struct sockaddr *)&servaddr , sizeof(servaddr)); /*(3) 监听*/ listen(listenfd , LISTENQ); /*(4) 设置poll*/ client[0].fd = listenfd; client[0].events = POLLRDNORM; for(i=1 ; i<OPEN_MAX ; ++i) { client[i].fd = -1; }//for maxi = 0; /*(5) 进入服务器接收请求死循环*/ while(1) { nready = poll(client , maxi+1 , INFTIM); if(client[0].revents & POLLRDNORM) { /*接收客户端的请求*/ clilen = sizeof(cliaddr); printf("\naccpet connection~\n"); if((connfd = accept(listenfd , (struct sockaddr *)&cliaddr , &clilen)) < 0) { perror("accept error.\n"); exit(1); }//if printf("accpet a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr) , cliaddr.sin_port); /*将客户链接套接字描述符添加到数组*/ for(i=1 ; i<OPEN_MAX ; ++i) { if(client[i].fd < 0) { client[i].fd = connfd; break; }//if }//for if(OPEN_MAX == i) { perror("too many connection.\n"); exit(1); }//if /*该描述符等待的事件*/ client[i].events = POLLRDNORM; if(i > maxi) maxi = i; if(--nready < 0) continue; }//if for(i=1; i<=maxi ; ++i) { if((sockfd = client[i].fd) < 0) continue; /*该链接描述符实际发生的事件*/ if(client[i].revents & (POLLRDNORM | POLLERR)) { /*处理客户请求*/ printf("\nreading the socket~~~ \n"); bzero(buf , MAX_LINE); if((n = read(sockfd , buf , MAX_LINE)) <= 0) { close(sockfd); client[i].fd = -1; }//if else{ printf("clint[%d] send message: %s\n", i , buf); if((ret = write(sockfd , buf , n)) != n) { printf("error writing to the sockfd!\n"); break; }//if }//else if(--nready <= 0) break; }//if }//for }//while exit(0); }
2.2 client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #include <time.h> #include <sys/socket.h> #include <sys/select.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #include <fcntl.h> #define PORT 8888 #define MAX_LINE 2048 int max(int a , int b) { return a > b ? a : b; } /*readline函数实现*/ ssize_t readline(int fd, char *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = read(fd, &c,1)) == 1) { *ptr++ = c; if (c == '\n') break; /* newline is stored, like fgets() */ } else if (rc == 0) { *ptr = 0; return(n - 1); /* EOF, n - 1 bytes were read */ } else return(-1); /* error, errno set by read() */ } *ptr = 0; /* null terminate like fgets() */ return(n); } /*普通客户端消息处理函数*/ void str_cli(int sockfd) { /*发送和接收缓冲区*/ char sendline[MAX_LINE] , recvline[MAX_LINE]; while(fgets(sendline , MAX_LINE , stdin) != NULL) { write(sockfd , sendline , strlen(sendline)); bzero(recvline , MAX_LINE); if(readline(sockfd , recvline , MAX_LINE) == 0) { perror("server terminated prematurely"); exit(1); }//if if(fputs(recvline , stdout) == EOF) { perror("fputs error"); exit(1); }//if bzero(sendline , MAX_LINE); }//while } int main(int argc , char **argv) { /*声明套接字和链接服务器地址*/ int sockfd; struct sockaddr_in servaddr; /*判断是否为合法输入*/ if(argc != 2) { perror("usage:tcpcli <IPaddress>"); exit(1); }//if /*(1) 创建套接字*/ if((sockfd = socket(AF_INET , SOCK_STREAM , 0)) == -1) { perror("socket error"); exit(1); }//if /*(2) 设置链接服务器地址结构*/ bzero(&servaddr , sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(PORT); if(inet_pton(AF_INET , argv[1] , &servaddr.sin_addr) < 0) { printf("inet_pton error for %s\n",argv[1]); exit(1); }//if /*(3) 发送链接服务器请求*/ if(connect(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)) < 0) { perror("connect error"); exit(1); }//if /*调用消息处理函数*/ str_cli(sockfd); exit(0); }
2.3 运行结果
服务器端:客户端: