从select引起的bug聊聊多路复用二 poll

从select引起的bug聊聊多路复用二 pollselect最多支持1024个连接,且连接的文件描述符的最大不能超过1024个,如果程序打开了很多文件,或用了2MB这种大页内存,可能会导致打开的文件超过1024,从而使unix socket 产生莫名其妙的问题,poll这套IO多路复用机

大家好,欢迎来到IT知识分享网。

一 前言

select最多支持1024个连接,且连接的文件描述符的最大不能超过1024个,如果程序打开了很多文件,或用了2MB这种大页内存,可能会导致打开的文件超过1024,从而使unix socket 产生莫名其妙的问题,poll这套IO多路复用机制和select的用法很像,采用链表而不是采用位图的方式,突破了1024个套接字的限制,本文就是用poll重新实现前篇的功能。

二 poll

2.1 poll的API说明

poll函数原型:

 int poll(struct pollfd *fds, unsigned long nfds, int timeout); 返回值:若有就绪描述符则为其数目,若超时则为0,若出错则为-1 

struct pollfd定义如下:

struct pollfd { int fd; /* file descriptor */ short events; /* events to look for */ short revents; /* events returned */ }; 

fd 文件描述符; events 要检测的事件类型; revents 指的是返回事件类型,可以设置多个; timeout 设置为负数表示永久等待; 如果是0 ,表示不阻塞立刻返回; 如果大于0的数值表示 poll 调用方等待指定的毫秒数后返回。 比select的优点是不用每次都设置文件描述符。

可读事件类型:

#define POLLIN 0x0001 /* any readable data available */ #define POLLPRI 0x0002 /* OOB/Urgent readable data */ #define POLLRDNORM 0x0040 /* non-OOB/URG data available */ #define POLLRDBAND 0x0080 /* OOB/Urgent readable data */ 

可写事件类型:

 #define POLLOUT 0x0004 /* file descriptor is writeable */ #define POLLWRNORM POLLOUT /* no write type differentiation */ #define POLLWRBAND 0x0100 /* OOB/Urgent data can be written */ 

错误事件类型定义:

 #define POLLERR 0x0008 /* 一些错误发送 */ #define POLLHUP 0x0010 /* 描述符挂起*/ #define POLLNVAL 0x0020 /* 请求的事件无效*/ 

2.2 采用poll改成服务器代码

#include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include <poll.h> #define CLIENT_SIZE 100 #define SOCK_FILE "command.socket" #define TOO_MANY "Too many client." typedef struct unix_socket_infos_ { int socket; struct pollfd event_sets[CLIENT_SIZE]; struct sockaddr_un client_addr; } unix_socket_infos_t; static int create_unix_socket(unix_socket_infos_t *this) { struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, SOCK_FILE, sizeof(addr.sun_path)); addr.sun_path[sizeof(addr.sun_path) - 1] = 0; int len = strlen(addr.sun_path) + sizeof(addr.sun_family) + 1; int listen_socket = socket(AF_UNIX, SOCK_STREAM, 0); if (listen_socket == -1) { perror("create socket error.\n"); return -1; } int on = 1; /* set reuse option */ int ret = setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); unlink(SOCK_FILE); /* bind socket */ ret = bind(listen_socket, (struct sockaddr *)&addr, len); if (ret == -1) { perror("bind error.\n"); return -1; } printf("start to listen\n"); ret = listen(listen_socket, 1); if (ret == -1) { perror("listen error\n"); return -1; } ret = chmod(SOCK_FILE, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP); if (ret == -1) { perror("chmod error\n"); return -1; } this->socket = listen_socket; return 1; } static int close_client(unix_socket_infos_t *this, int index) { int client = this->event_sets[index].fd; close(client); this->event_sets[index].fd = -1; } static int deal_client(unix_socket_infos_t *this, int index) { char buffer[1024] = {0}; int ret = recv(this->event_sets[index].fd, buffer, sizeof(buffer) - 1, 0); if (ret <= 0) { if (ret == 0) { printf("lost connect.\n"); } else { printf("recv error:%s \n", strerror(errno)); } close_client(this, index); return -1; } if (ret < sizeof(buffer) - 2) { buffer[ret] = '\n'; buffer[ret + 1] = 0; } fprintf(stderr, "client[%d]:%s", this->event_sets[index].fd, buffer); ret = send(this->event_sets[index].fd, buffer, strlen(buffer), MSG_NOSIGNAL); if (ret < 0) { perror("send error:"); } else { fprintf(stderr, "server:%s", buffer); } return 1; } static int accept_client(unix_socket_infos_t *this ) { socklen_t len = sizeof(this->client_addr); char buffer[1024] = {0}; int client = accept(this->socket, (struct sockaddr *)&(this->client_addr), &len); printf("client to comming:%d\n", client); if (client < 0) { perror("accept error\n"); return -1; } memset(buffer, 0x0, 1024); int ret = recv(client, buffer, sizeof(buffer) - 1, 0); if (ret < 0) { perror("recv error\n"); return -1; } if (ret < sizeof(buffer) - 2) { buffer[ret] = '\n'; buffer[ret + 1] = 0; } fprintf(stderr, "client[%d][first]:%s", client, buffer); ret = send(client, buffer, strlen(buffer), MSG_NOSIGNAL); if (ret < 0) { perror("send error\n"); } else { fprintf(stderr, "server[first]:%s", buffer); } int is_set = 0; for (int i = 0; i < CLIENT_SIZE; i++) { if (this->event_sets[i].fd < 0) { this->event_sets[i].fd = client; this->event_sets[i].events = POLLRDNORM; is_set = 1; break; } } if (is_set == 0) { fputs(TOO_MANY, stdout); close(client); return -1; } return 1; } static int run_poll(unix_socket_infos_t *this) { struct timeval tv; int ret; tv.tv_sec = 0; tv.tv_usec = 200 * 1000; int ready_number; this->event_sets[0].fd = this->socket; this->event_sets[0].events = POLLRDNORM; while (1) { if ((ready_number = poll(this->event_sets, CLIENT_SIZE, -1)) < 0) { perror("poll error."); } if (this->event_sets[0].revents & POLLRDNORM) { accept_client(this); // 只有一个准备好的文件描述符 if (--ready_number <= 0) continue; } for (int i = 1; i < CLIENT_SIZE; i++) { int socket_fd; if ((socket_fd = this->event_sets[i].fd) < 0) { continue; } if (this->event_sets[i].revents & (POLLRDNORM | POLLERR)) { deal_client(this, i); if (--ready_number <= 0) break; } } } } int main(int argc, char **argv) { unix_socket_infos_t unix_socket_infos; for (int i = 0; i < CLIENT_SIZE; i++) { unix_socket_infos.event_sets[i].fd = -1; } int ret = create_unix_socket(&unix_socket_infos); printf("start to loop\n"); run_poll(&unix_socket_infos); return 0; } 

代码比select更简单,而且没有1024的限制。 poll 相比select 不用每次都重新设置监听的文件描述符,将事件和文件描述符分开,所以不用很麻烦。 内核代码里面除了将select的位图改成链表外,其他的大差不差,同样是循环调用文件描述符的对应文件的poll方法,如下:

mask = f.file->f_op->poll(f.file, pwait); 

将获取的mask设置到返回的事件中。代码和select一样,都在select.c中。

三 套接字是否要设置为非阻塞模式

在上面的代码中,我们没有对监听的socket 做特殊的设置,这就可能存在问题,举个简单的例子, 如果客户端和服务器端连接后,客户端发送RST报文给服务器端,服务器端内核里面将全连接从全连接的队列中删除,而服务器端从poll中返回,如果不能及时的调用accept,导致全连接队列的客户端连接又恰好被内核删除了,导致再调用accept不能返回。

所以一般建议将监听的socket设置为非阻塞模式:

fcntl(fd, F_SETFL, O_NONBLOCK); 

其实其他的客户端来的socket的监听,用阻塞模式仍然可能存在问题,像write,如果写的数据多,而对方读的慢导致发送窗口为0而阻塞,单线程被阻塞了,没有在poll函数中,从而如果有其他客户端来连接的会,就需要等待,从而影响性能(这个没试验出来,缓冲区够大)。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/85202.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信