Unix环境高级编程多路复用之poll的基本实现
目录
select的多路复用实现网络socket的多并发服务器的流程图
-
poll函数简介
select()和poll()系统调用的本质一样,前者在BSD UNIX中引入的,后者在System V中引入的。poll()的机制与 select() 类 似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理。
#include <poll.h>
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
-
参数说明
-
第一个参数:用来指向一个struct pollfd类型的数组,每一个pollfd结构体指定了一个被监视的文件描述符,指示poll()监视多个文 件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果 事件掩码,内核在调用返回时设置这个域,events域中请求的任何事件都可能在revents域中返回。下表列出指定 events 标志以 及测试 revents 标志的一些常值:
常量 说明 是否能作为 events 的输入 是否能作为revents的返回结果 POLLIN 普通或者优先级带数据可读 能 能 POLLRDNORM 普通数据可读 能 能 POLLRDBAND 优先级带数据可读 能 能 POLLPRI 高优先级数据可读 能 能 POLLOUT 普通数据可写 能 能 POLLWRNORM 普通数据可写 能 能 POLLWRBAND 优先级带数据可写 能 能 POLLERR 发生错误 能 能 POLLHUP 发生挂起 否 能 POLLNVAL 描述字不是一个打开的文件 否 能 POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于 POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可 写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的 events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而 不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
- 第二个参数 :nfds 指定数组中监听的元素个数;
- 第三个参数:timeout指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll() 一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。 这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
该函数成功调用时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0; 失败时,poll()返回-1,并设置errno为下列值之一:
- EBADF 一个或多个结构体中指定的文件描述符无效。
-
EFAULTfds 指针指向的地址超出进程的地址空间。
-
EINTR 请求的事件之前产生一个信号,调用可以重新发起。
-
EINVALnfds 参数超出PLIMIT_NOFILE值。
-
ENOMEM 可用内存不足,无法完成请求。
-
poll的不足之处
poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组 被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
-
与select相比poll的优点
但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。
-
poll的多路复用实现网络socket的多并发服务器的流程图
-
服务器实现代码
-
头文件
#ifndef __SOCKET_POLL_SERVER_H__ #define __SOCKET_POLL_SERVER_H__ #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <ctype.h> #include <time.h> #include <pthread.h> #include <getopt.h> #include <libgen.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <poll.h> #define ARR_SIZE(x) (sizeof(x)/sizeof(x[0])) #define BUF_SIZE 1024 int get_opt(int argc, char * const argv[],const char *optstring); int socket_server_init(int listen_port, char *msg); void poll_start(int listenfd, char *msg); void print_usage(char *prograname) { printf("%s usage : \n", prograname); printf("-p(--port): specify sever listen port.\n"); printf("-m(--msg): specify sever write msg to client.\n"); printf("-d(--daemon): specify sever will go to run with daemon.\n"); printf("-h(--help): print this help information.\n"); return ; } #endif
-
源文件
#include "socket_poll_server.h" int main(int argc, char *argv[]) { get_opt(argc, argv,"p:dm:h"); return 0; } int get_opt(int argc, char * const argv[],const char *optstring) { int port = 0; int ch; char *msg ; struct option opts[] = { {"port", required_argument, NULL, 'p'}, {"write_msg", required_argument, NULL, 'm'}, {"daemon", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; while((ch=getopt_long(argc, argv, "p:m:dh", opts, NULL)) != -1 ) { switch(ch) { case 'p': port=atoi(optarg); break; case 'm': msg = optarg; break; case 'd': daemon(0,0); break; case 'h': print_usage(argv[0]); return 0; } } if( !port||!msg) { print_usage(argv[0]); return 0; } socket_server_init(port, msg); } int socket_server_init(int listen_port, char *msg) { int lisfd = 0; int clifd = 0; int on = 1; int rv = 0; char buf[BUF_SIZE]; struct sockaddr_in serv_addr, cli_addr; socklen_t len = sizeof(serv_addr); if ((lisfd = socket(AF_INET,SOCK_STREAM, 0))< 0) //服务器第一步socket() { printf("Socket error:%s\n", strerror(errno)); return -1; } printf("socket[%d] successfuly!\n", lisfd); setsockopt(lisfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //端口短时间可以重复使用 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(listen_port); if ((rv = bind(lisfd, (struct sockaddr *)&serv_addr, len)) < 0) //服务器第二步,bind() { printf("Bind error %s\n", strerror(errno)); goto EXIT; } if ((rv = listen(lisfd, 13)) < 0) //服务器第三步,listen() { printf("Listen error:%s\n", strerror(errno)); goto EXIT; } poll_start(lisfd, msg); //调用poll_start(); return clifd; EXIT: close(lisfd ); close(clifd ); return -1; } void poll_start(int listenfd, char *msg) { int max = 0; char buf[BUF_SIZE]; struct pollfd fds_array[1024]; int i; int rv ; int found; int connfd; for (i = 0; i < ARR_SIZE(fds_array); ++i) //将fds_array();置空,因为fd可能=0即初始化为 - 1; { fds_array[i].fd = -1; } fds_array[0].fd = listenfd; //listenfd 进入数组 fds_array[0].events = POLLIN; //设置写的事件 for ( ; ; ) { rv = poll(fds_array, max +1, -1); //poll开始,第三个参数为-1,表示永不超时; if (rv < 0) { printf("select failure: %s\n", strerror(errno)); break; } else if (rv == 0) { printf("poll get time out.\n"); } if (fds_array[0].revents & POLLIN) //判断事件. { if ( ( connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) < 0) //服务器第四步,accept { printf("Accept new client error: %s\n", strerror(errno)); continue; } found = 0; for (i = 0; i <ARR_SIZE(fds_array); i++) { if (fds_array[i].fd < 0) { printf("Accept new client[%d] and it into ayyar.\n", connfd); fds_array[i].fd = connfd; //将clifd加入array; fds_array[i].events = POLLIN; //设置事件 found = 1; break; } } if (!found) //判断数组是否已满 { printf("Accept new client[%d] sueecssful but array is full, so refuse it.\n", connfd); close(connfd); } max = i>max?i:max; if (--rv <= 0) //判断已有的事件是否处理完 { continue; } } else { for (i = 1; i<ARR_SIZE(fds_array); i++) { if (fds_array[i].fd < 0 || fds_array[i].events != POLLIN) continue; if ((rv = read(fds_array[i].fd, buf, BUF_SIZE)) <= 0) //服务器第五步,read/write { printf("Socket[%d] read failure or get disconnected.\n", fds_array[i].fd); close(fds_array[i].fd); fds_array[i].fd = -1; } else { printf("socket[%d] read get %d bytes data\n", fds_array[i].fd, rv); printf("write start[%d] \n", fds_array[i].fd); if (write(fds_array[i].fd, msg, BUF_SIZE) < 0) { printf("socket[%d] write failure: %s\n", fds_array[i].fd, strerror(errno)); close(fds_array[i].fd); fds_array[i].fd = -1; } else { printf("write to client[%d]%s\n", fds_array[i].fd, msg); } } } } } }
-
运行结果
-
单个客户端连接
-
多客户端连接
注:学识尚浅,如有不足地方敬请指出。谢谢!
上一篇: 为一台或多台ECS实例预约模拟系统事件
下一篇: epoll网路模型