1 poll 函数原型
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 监控的事件 */
short revents; /* 监控事件中满足条件返回的事件 */
};
/*
*
* ★ POLLIN 普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND
* POLLRDNORM 数据可读
* POLLRDBAND 优先级带数据可读
* POLLPRI 高优先级可读数据
*
* ★ POLLOUT 普通或带外数据可写
* POLLWRNORM 数据可写
* POLLWRBAND 优先级带数据可写
*
* ★ POLLERR 发生错误
* POLLHUP 发生挂起
* POLLNVAL 描述字不是一个打开的文件
*
* nfds 监控数组中有多少文件描述符需要被监控
*
* timeout 毫秒级等待
* -1: 阻塞等待,#define INFTIM -1 Linux中没有定义此宏
* 0: 立即返回,不阻塞进程
* >0: 等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
*
* return value:
* 成功: 返回满足对应监听事件的文件描述符总个数
* 失败: -1,设置errno
*/
如果不再监控某个文件描述符时,可以把pollfd结构体中,fd设置为-1,poll不再监控此pollfd,下次返回时,把revents设置为0。
相较于select而言,poll的优势:
- 传入、传出事件分离。无需每次调用时,重新设定监听事件。
- 文件描述符上限,可突破1024限制。能监控的最大上限数可使用配置文件调整。
2 poll实现多路IO转接(代码)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <poll.h>
#include <errno.h>
int main()
{
int listenfd, connfd; // 监听套接字、连接套接字
int maxi = 0; // 数组的最大下标
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listenfd)
{
perror("socket error");
exit(1);
}
// 端口复用
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in saddr, caddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8000);
saddr.sin_addr.s_addr = inet_addr("192.168.71.132");
int ret = bind(listenfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (-1 == ret)
{
perror("bind error");
close(listenfd);
exit(1);
}
ret = listen(listenfd, 5);
if (-1 == ret)
{
perror("listen error");
exit(1);
}
struct pollfd client[1024];
for (int ii = 0; ii < 1024; ++ii)
{
client[ii].fd = -1;
client[ii].events = 0;
client[ii].revents = 0;
}
client[0].fd = listenfd; // 要监听的第一个文件描述符存入client[0]中
client[0].events = POLLIN; // listenfd监听普通读事件
maxi = 0;
int nReady = 0;
while (1)
{
nReady = poll(client, maxi+1, -1); // 阻塞监听
if (-1 == nReady)
{
perror("poll error");
exit(1);
}
else if (0 == nReady) // poll为阻塞,不会有这种情况
{
}
else
{
if (client[0].revents & POLLIN) // 客户端连接请求
{
client[0].revents = 0;
socklen_t caddrLen = sizeof(caddr);
connfd = accept(listenfd, (struct sockaddr*)&caddr, &caddrLen); // 与客户端连接
printf("建立连接成功:%s:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
int ii = 0;
for (ii = 0; ii < 1024; ++ii)
{
if (-1 == client[ii].fd) // 找到client[]中空闲的位置,存放accept返回的connfd
{
client[ii].fd = connfd;
client[ii].events = POLLIN;
if (ii > maxi) // 更新client[]中最大元素下标
{
maxi = ii;
}
break;
}
}
if (1024 == ii) // 达到最大客户端数
{
printf("too many clients\n");
exit(1);
}
if (--nReady <= 0) // 没有更多就绪事件,继续回到poll阻塞
continue;
}
for (int ii = 1; ii <= maxi; ++ii)
{
int sockfd = 0;
if ((sockfd = client[ii].fd) < 0)
continue;
if (client[ii].revents & POLLIN)
{
client[ii].revents = 0;
char buf[1024] = {0};
int n = read(sockfd, buf, sizeof(buf));
if (-1 == n)
{
if (errno == EINTR || errno == EAGAIN)
{
continue;
}
else if (errno == ECONNRESET) // 收到RST标识
{
close(sockfd);
client[ii].fd = -1;
}
else
{
exit(1);
}
}
else if (0 == n)
{
printf("断开连接\n");
close(sockfd);
client[ii].fd = -1;
}
else
{
printf("recv:%s\n", buf);
}
if (--nReady <= 0)
break;
}
}
}
}
return 0;
}
3 poll优缺点
-
优点
- 自带数据结构。可以将监听事件集合和返回事件集合分离
- 可以拓展监听上限。超出1024
-
缺点
- 不可跨平台。(Linux/类Unix)
4 文件描述符突破1024限制
可以使用cat命令查看当前计算机所能打开的最大文件个数。受硬件影响
cat /proc/sys/fs/file-max
当前用户下的进程,默认打开文件描述符个数。缺省为 1024
ulimit -a
修改
sudo vim /etc/security/limits.conf
在文件尾部写入以下配置,soft软限制,hard硬限制。(中间空格为<tab>)
* soft nofile 3000 -----> 设置默认值,可以直接借助命令修改。(ulimit -n 描述符个数) 【注销用户,使其生效】
* hard nofile 20000 -----> 命令修改上限