承接上次select后面
函数poll也能实现io多路复用
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参p数:
struct pollfd *fds
关心的文件描述符数组struct pollfd fds[N];
nfds:个数
timeout: 超时检测
毫秒级的:如果填1000,1秒
如果-1,阻塞
struct pollfd {
int fd; /* 检测的文件描述符 */
short events; /* 检测事件 */
short revents; /* 调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员。只需要判断这个成员的值就可以确定是否产生事件 */
};
事件: POLLIN :读事件
POLLOUT : 写事件
POLLERR:异常事件
实例(简易服务器):
//io并发准备工作
struct pollfd fds[20] = {};
fds[0].fd = 0; //把关注的描述符放进表里
fds[0].events = POLLIN; //设置描述符的事件
fds[1].fd = sockfd;
fds[1].events = POLLIN;
int nfds = 2;//表中变量数量
char buf[128];
int ret, i, recvbyte, nfdstemp;
//4.阻塞等待客户端链接,链接成功返回一个通信文件描述符
while (1)
{
ret = poll(fds, nfds, 2000);
if (ret < 0)
{
perror("select err");
return -1;
}
else if (ret == 0)
{
printf("timeout ----- \n");
}
else
{
// nfdstemp = nfds; //临时用来便利的,防止后面改maxfd出错(我不知道有没有出错的可能)
for (i = 0; i < nfds; i++)
{
if (fds[i].revents == POLLIN) //只有标志变量变了才能进来
{
if (fds[i].fd == 0)
{
fgets(buf, sizeof(buf), stdin);
printf("stdin: %s\n", buf);
}
else if (fds[i].fd == sockfd)
{
acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len); //接收咱们关心的描述符
if (acceptfd < 0)
{
perror("accept err");
return -1;
}
fds[nfds].fd = acceptfd; //继续把关心的描述符放进表里,等它动弹时关心它
fds[nfds].events = POLLIN;
nfds++;
printf("用户 %d 已登录:ip:%s ,port:%d\n",
acceptfd, inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port)); //全服通告
}
else
{
recvbyte = recv(fds[i].fd, buf, sizeof(buf), 0); //
if (recvbyte < 0)
{
perror("recv err");
return -1;
}
else if (recvbyte == 0) //退出很麻烦,要多费心
{
printf("用户%d 已退出\n", fds[i].fd);
close(fds[i].fd); //再关闭描述符
fds[i] = fds[nfds - 1]; //先把它从表里踢出去
i--;
nfds--;
}
else
{
printf("用户%d: %s \n", fds[i].fd, buf); //它给的东西
}
}
}
}
}
}
poll的特点:
- 优化文件描述符个数的限制;(根据poll函数第一个函数的参数来定,如果监听的事件为1个,则结构体数组元素个数为1,如果想监听100个,那么这个结构体数组的元素个数就为100,由程序员自己来决定)
- poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低
- poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可