在《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》中,我们分析了它只能支持1024个连接同时处理的原因。但是在有些需要同时处理更多连接的情况下,1024个连接往往是不够的,也就是不能够高并发。那么这个时候我们就可以采用本文介绍的Poll模型。(转载请指明出于breaksoftware的csdn博客)
在使用Poll模型之前,我们需要定义一个保存连接信息的数组
struct pollfd fds[FDS_COUNT];
之后创建异步监听socket、绑定端口和监听端口等行为和《朴素、Select、Poll和Epoll网络编程模型实现和分析——Select模型》一文中一模一样,本文就不列出代码了。我们把创建的socket信息赋值给fds数组的第一个元素,并且设定我们需要关注的事件POLLIN——可读。
int timeout;
int cur_fds_count;
int rc;
int index;
int expect_events;
int error_events;
……// 创建socket
memset(fds, 0, sizeof(fds));
error_events = POLLERR | POLLNVAL;
expect_events = POLLIN;
fds[0].fd = listen_sock;
fds[0].events = expect_events;
cur_fds_count = 1;
cur_fds_count用于记录当前fds数组中有多少个被关心的文件描述符。因为一开始我们只关心监听socket,所以它的初始值是1。
然后我们就要在一个死循环中,不停的调用poll函数,监控我们关心的文件描述符是否发生了状态改变
timeout = (500);
while (1) {
rc = poll(fds, cur_fds_count, timeout);
if (rc < 0) {
perror("poll error\n");
exit(EXIT_FAILURE);
};
if (rc == 0) {
//perror("poll timeout\n");
};
poll函数的返回值和select函数类似。如果返回小于0,则说明发生了错误,我们让程序退出。如果返回了0,则说明poll函数超时。如果大于0,则说明被关心的文件描述符状态发生了改变。但是此时,我们仍然不知道是哪个文件描述符发生了改变,所以我们要遍历fds数组。
int cur_fds_count_temp = cur_fds_count;
for (index = 0; index < cur_fds_count_temp; ++index) {
这个时候我们有必要说明下pollfd结构体的定义
struct pollfd {
int fd;
short events;
short revents;
};
pollfd中的fd是用于记录我们关心的文件描述符;events表示我们关心的事件,如POLLIN、POLLOUT等。revents是实际发生的事件。于是我们一开始要判断改pollfd是否发生了事件改变
if (fds[index].revents == 0) {
continue;
}
接着我们判断下发生的事件是否是我们定义的出错事件。
if (fds[index].revents & error_events) {
perror("revents error");
如果出错的是监听socket,则我们退出程序。
if (fds[index].fd == listen_sock) {
perror("listen sock error");
exit(EXIT_FAILURE);
}
如果出错的不是监听socket,则它就是客户端接入的socket,我们将它关闭,并且将fds数组的最后一个元素覆盖当前位置,让数组长度减一。这个过程是最精简的数组缩小方式,如果使用新数组去记录,将导致效率降低。
else {
close(fds[index].fd);
cur_fds_count--;
if (index < cur_fds_count) {
memcpy(&fds[index], &fds[cur_fds_count], sizeof(fds[cur_fds_count]));
memset(&fds[cur_fds_count], 0, sizeof(fds[cur_fds_count]));
index--;
}
cur_fds_count_temp--;
continue;
}
}
我们再看发生的事件是否是我们关心的事件,如果不是,则continue掉,继续处理下一个pollfd。
if (!(fds[index].revents & fds[index].events)) {
continue;
}
经过上述筛选,剩下的就是我们要正常处理的pollfd了。和Select方式一样,我们先看看其是否是监听socket。如果是,则获取客户端接入的socket值,并记录到数组中。
if (fds[index].fd == listen_sock) {
int new_sock;
new_sock = accept(listen_sock, NULL, NULL);
if (new_sock < 0) {
//perror("accept error");
if (errno != EWOULDBLOCK) {
continue;
}
exit(EXIT_FAILURE);
}
else {
request_add(1);
//set_block_filedes_timeout(new_sock);
if (cur_fds_count + 1 < sizeof(fds)) {
fds[cur_fds_count].fd = new_sock;
fds[cur_fds_count].events = expect_events;
cur_fds_count++;
}
}
}
如果不是监听socket,则是客户端接入的socket。我们就读取这个socket中的内容,并写入我们的回包。
else {
if (0 == server_read(fds[index].fd)) {
server_write(fds[index].fd);
}
close(fds[index].fd);
cur_fds_count--;
if (index < cur_fds_count) {
memcpy(&fds[index], &fds[cur_fds_count], sizeof(fds[cur_fds_count]));
memset(&fds[cur_fds_count], 0, sizeof(fds[cur_fds_count]));
index--;
}
cur_fds_count_temp--;
}
}
}
return 0;
}
如此,我们便将简单的poll服务器给写完了。我们看下poll模型的处理能力。采用和《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》一文中相同的环境和压力,我们看下服务器的数据输出
再看下客户端的输出
可见当前环境下poll模型的处理能力大概是每秒7500次请求。