一 前言
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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函数中,从而如果有其他客户端来连接的会,就需要等待,从而影响性能(这个没试验出来,缓冲区够大)。