I/O复用函数的使用(poll)
poll是传入一个数组,数组大小可以超过1024。比select可以支持更多的描述符。
poll的事件类型更丰富。
poll 的接口介绍
poll 系统调用和 select 类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。poll 的原型如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll 系统调用成功返回就绪文件描述符的总数,超时返回 0,失败返回-1
nfds 参数指定被监听事件集合 fds 的大小。
timeout 参数指定 poll 的超时值,单位是毫秒,timeout 为-1 时,poll 调用将永久
阻塞,直到某个事件发生,timeout 为 0 时,poll 调用将立即返回。
fds 参数是一个 struct pollfd 结构类型的数组,它指定所有用户感兴趣的文件描述
符上发生的可读、可写和异常等事件。pollfd 结构体定义如下:
struct pollfd
{
int fd; // 文件描述符
short events; // 注册的关注事件类型
short revents; // 实际发生的事件类型,由内核填充
};
其中,fd 成员指定文件描述符,events 成员告诉 poll 监听 fd 上的哪些事件类型。假如有16个位,则可以表示16种不同的事件。某个位为1代表相应的事情。
它是一系列事件的按位或,revents 成员则有内核修改,通知应用程序 fd 上实际发生了哪些事件。
返回值n>0,是这个数组上有n个描述符有数据,但是不知道是哪n个。
poll 支持的事件类型
每个位为1代表相应的事情。
赋值成2种事件的写法如下:
内核会通知我们用户实际发生了哪些事件。
revents是内核检测后返回给我们的。进行按位与判断!!!
我们先传入数组,数组的每个元素就是我们想要关注的描述符及其事件。
然后传入数组元素的个数,最后传入timeout,
如果我们定义的数组大小是100个,但是我们可能只传了10个描述符,那么我们怎么知道几个是描述符是有数据的?我们把结构体中不用的元素的fd置为-1,在内核中,发现描述符是-1,是无效描述符,就不会去检测什么事件了。只会去检测数组中真实有效的描述符。数组操作起来方便!
poll执行可能会阻塞,直到我们检测到数组描述符有事件产生了或者超时了。
poll 的示例代码
使用 poll 实现的 TCP 服务器代码如下:
封装函数如下:
主函数如下:
编译运行程序
运行服务器端和客户端
现在服务器端数组中有几个描述符?
答案是2个,一个是sockfd,一个是c。
现在服务器端数组有3个描述符。
我们把服务器端的代码修改一下,一次只读取一个字符!
这样,我们发送一个hello,它一次recv只能回复一个字符h,回复e,要等到poll第二次返回,再次提醒我们这个描述符有数据,然后recv。
多次recv,poll返回多次。
为什么只看到2个ok,就是我们上次说的,还有一些ok在接收缓冲区放着!
还有4对ok在接收缓冲区。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#define DATALEN 1024
#define MAX_FD 128
// 初始化服务器端的 sockfd 套接字
int InitSocket()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd == -1) return -1;
struct sockaddr_in saddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));
if(res == -1) return -1;
res = listen(sockfd, 5);
if(res == -1) return -1;
return sockfd;
}
void InitPollFds(struct pollfd fds[])
{
int i = 0;
for (; i < MAX_FD; ++i)
{
fds[i].fd = -1;
}
}
void InsertFdToPollfds(struct pollfd fds[], int fd, short events)
{
int i = 0;
for (; i < MAX_FD; ++i)
{
if (fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = events;
break;
}
}
}
void GetClinetLink(struct pollfd fds[], int sockfd)
{
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
socklen_t len = sizeof(caddr);
int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
if (c < 0)
{
return;
}
printf("A client connection was successful\n");
InsertFdToPollfds(fds, c, POLLIN | POLLRDHUP);
}
// 处理客户端数据
int DealClientData(int clifd)
{
char data[DATALEN] = { 0 };
int n = recv(clifd, data, DATALEN - 1, 0);
if(n <= 0)
{
return -1;
}
printf("%d: %s\n", clifd, data);
send(clifd, "OK", 2, 0);
return 0;
}
// 处理 poll 返回的就绪事件
void DealReadyEvent(struct pollfd fds[], int sockfd)
{
int i = 0;
for (; i < MAX_FD; ++i)
{
if (fds[i].fd == -1)
{
continue;
}
/* POLLRDHUP 用于判断客户端程序是否关闭
else if (fds[i].revents & POLLRDHUP)
{
close(fds[i].fd);
fds[i].fd = -1;
printf("A client disconnected\n");
continue;
}*/
else if (fds[i].revents & POLLIN)
{
if (fds[i].fd == sockfd)
{
GetClinetLink(fds, sockfd);
}
else
{
if(-1 == DealClientData(fds[i].fd))
{
close(fds[i].fd);
fds[i].fd = -1;
printf("A client disconnected\n");
}
}
}
else
{
printf("error\n");
}
}
}
int main()
{
int sockfd = InitSocket();
assert(sockfd != -1);
struct pollfd fds[MAX_FD];
InitPollFds(fds);
InsertFdToPollfds(fds, sockfd, POLLIN);
while (1)
{
int n = poll(fds, MAX_FD, 2000);
if (n < 0)
{
printf("poll error\n");
continue;
}
else if (n == 0)
{
printf("timeout\n");
continue;
}
else
{
DealReadyEvent(fds, sockfd);
}
}
exit(0);
}