poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
系统调用成功返回就绪文件描述符的总数,超时返回0,失败返回-1
参数 | 含义 |
---|---|
fds | struct polled结构类型的数组,它指定所有用户感兴趣的文件描述符上发生的可读、可写和异常等事件 |
nfds | 指定被监听事件集合fds的大小 |
timeout | 指定poll的超时值,单位是毫秒, timeout为-1时,poll调用将永久阻塞,直到某个事件发生;为0时,poll调用将立即返回 |
pollfd结构
struct pollfd
{
int fd; // 用户设置关注的文件描述符
short events; // 用户关注的事件类型
short revents; // 由内核填充就绪的事件类型
};
poll支持的事件类型:
poll和select的异同
- poll将用户关注的事件类型和就绪的事件类型分隔开
- poll可以关注多个事件类型,并且观测的文件描述符没有数量的限制,文件描述符的值也不再限制在1024以内
- 对于两者,用户探测就绪事件的时间复杂度仍为O(n)
使用poll方法实现一个简单的TCP服务器端
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <poll.h>
#define FDS_LENGTH 100 // 数组长度
// 创建并初始化连接套接字sockfd
int InitSocket()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
{
return -1;
}
struct sockaddr_in ser;
memset(&ser, 0, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(6000);
ser.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd, (struct sockaddr*)&ser, sizeof(ser));
if (res == -1)
{
return -1;
}
res = listen(sockfd, 5);
if (res == -1)
{
return -1;
}
return sockfd;
}
// 初始化fds数组
void InitFds(struct pollfd *fds)
{
int i = 0;
for (; i < FDS_LENGTH; ++i)
{
fds[i].fd = -1;
fds[i].events = 0;
}
}
// 向数组中添加新的文件描述符
void InsertFd(struct pollfd *fds, int fd, short events)
{
int i = 0;
for (; i < FDS_LENGTH; ++i)
{
if (fds[i].fd == -1)
{
fds[i].fd = fd;
fds[i].events = events;
break;
}
}
}
// 处理就绪事件
void DealFinishEvent(struct pollfd *fds, int sockfd)
{
int i = 0;
for (; i < FDS_LENGTH; ++i)
{
if (fds[i].fd == -1) // 无效的文件描述符
{
continue;
}
else if (fds[i].fd == sockfd) // 接受新的客户端连接
{
if (fds[i].revents & POLLIN)
{
struct sockaddr_in cli;
socklen_t len = sizeof(cli);
int c = accept(sockfd, (struct sockaddr*)&cli, &len);
if (c < 0)
{
printf("accept error\n");
continue;
}
printf("one client link success\n");
InsertFd(fds, c, POLLIN | POLLRDHUP);
}
}
else // 客户端连接文件描述符
{
// 必须先判断POLLRDHUP,若不为断开则接收数据,提高效率
if (fds[i].revents & POLLRDHUP) // 客户端断开连接
{
printf("one client unlink\n");
close(fds[i].fd);
// 防止下次连接关闭的fd
fds[i].fd = -1;
fds[i].events = 0;
}
else if (fds[i].revents & POLLIN) // 有数据传递
{
char buff[128] = { 0 };
int n = recv(fds[i].fd, buff, 127, 0);
if (n <= 0)
{
printf("recv error\n");
continue;
}
printf("%d: %s\n", fds[i].fd, buff);
send(fds[i].fd, "OK", 2, 0);
}
}
}
}
int main()
{
int sockfd = InitSocket();
assert(sockfd != -1);
struct pollfd fds[FDS_LENGTH];
InitFds(fds); // 初始化fds
InsertFd(fds, sockfd, POLLIN); // 将连接套接字sockfd插入
while (1)
{
int n = poll(fds, FDS_LENGTH, -1); // n>0返回就绪文件描述符的个数
if (n <= 0)
{
printf("poll error\n");
continue;
}
DealFinishEvent(fds, sockfd);
}
close(sockfd);
exit(0);
}
TCP客户端
代码参见TCP客户端