文件描述符处理与回调函数
一、主要概念
二、处理流程
三、注意事项
四、概括
核心观点:
>>回顾ChannelMap 模块的实现和Channel 模块的实现
// 定义函数指针
typedef int(*handleFunc)(void* arg);
// 定义文件描述符的读写事件
enum FDEvent {
TimeOut = 0x01;
ReadEvent = 0x02;
WriteEvent = 0x04;
};
struct Channel {
// 文件描述符
int fd;
// 事件
int events;
// 回调函数
handleFunc readCallback;// 读回调
handleFunc writeCallback;// 写回调
// 回调函数的参数
void* arg;
};
>>在EventLoop中处理被激活的文件描述符的事件
// 处理被激活的文件描述符fd
int eventActivate(struct EventLoop* evLoop,int fd,int event);
// 处理被激活的文件描述符fd
int eventActivate(struct EventLoop* evLoop,int fd,int event) {
if(fd < 0 || evLoop == NULL) {+
return -1;
}
// 取出channel
struct Channel* channel = evLoop->channelMap->list[fd];
assert(channel->fd == fd);
if(event & ReadEvent && channel->readCallback) {
channel->readCallback(channel->arg);
}
if(event & WriteEvent && channel->writeCallback) {
channel->writeCallback(channel->arg);
}
return 0;
}
>>回顾Dispatcher模块,Dispatcher模块的实现思路和定义 ,补充代码
static int selectDispatch(struct EventLoop* evLoop,int timeout) {
struct SelectData* data = (struct SelectData*)evLoop->dispatcherData;
struct timeval val;
val.tv_sec = timeout;
val.tv_usec = 0;
fd_set rdtmp = data->readSet;
fd_set wrtmp = data->writeSet;
int count = select(Max,&rdtmp,&wrtmp,NULL,&val);
if(count == -1) {
perror("select");
exit(0);
}
for(int i=0;i
static int pollDispatch(struct EventLoop* evLoop,int timeout) {
struct PollData* data = (struct PollData*)evLoop->dispatcherData;
int count = poll(data->fds,data->maxfd + 1,timeout * 1000);
if(count == -1) {
perror("poll");
exit(0);
}
for(int i=0;i<=data->maxfd;++i) {
if(data->fds[i].fd == -1) {
continue;
}
if(data->fds[i].revents & POLLIN) {
// 已续写...
eventActivate(evLoop,data->fds[i].fd,ReadEvent);
}
if(data->fds[i].revents & POLLOUT) {
// 已续写...
eventActivate(evLoop,data->fds[i].fd,WriteEvent);
}
}
return 0;
}
static int epollDispatch(struct EventLoop* evLoop,int timeout) {
struct EpollData* data = (struct EpollData*)evLoop->dispatcherData;
int count = epoll_wait(data->epfd,data->events,Max,timeout * 1000);
for(int i=0;ievents[i].events;
int fd = data->events[i].data.fd;
if(events & EPOLLERR || events & EPOLLHUP) {
// 对方断开了连接,删除 fd
// epollRemove(&evLoop->channels[fd],evLoop);
continue;
}
if(events & EPOLLIN) {
// 已续写...
eventActivate(evLoop,fd,ReadEvent);
}
if(events & EPOLLOUT) {
// 已续写...
eventActivate(evLoop,fd,WriteEvent);
}
}
return 0;
}
五、回顾select函数、poll函数、epoll_wait函数(CodeGeex生成)
Linux 中的 select 函数主要用于等待多个文件描述符(fd)就绪,从而可以一次性处理多个文件描述符。select 函数会检查指定的文件描述符是否就绪,如果其中任何一个是就绪的,则 select 函数返回。select 函数的原型为:
#include
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,const struct timeval *timeout);
其中,nfds 表示要检查的文件描述符数量,readfds、writefds 和 exceptfds 分别表示要检查的读、写和异常文件描述符集合,timeout 表示等待的时间限制。
例如,下面代码等待两个文件描述符(fd)就绪,最多等待 5 秒钟:
int fd1 = open("file1", O_RDONLY);
int fd2 = open("file2", O_RDONLY);
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(fd1, &readfds);
FD_SET(fd2, &readfds);
struct timeval timeout;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
int ret = select(2, &readfds, NULL, NULL, &timeout);
if (ret > 0) {
if (FD_ISSET(fd1, &readfds)) {
// fd1 is ready to read
}
if (FD_ISSET(fd2, &readfds)) {
// fd2 is ready to read
}
}
Linux 中的 poll 函数与 select 函数类似,用于等待多个文件描述符就绪,但是 poll 函数返回的是一个事件列表,而不是像 select 函数那样返回就绪的文件描述符数量。poll 函数的原型为:
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
其中,fds 是一个 pollfd 结构数组,每个结构中包含一个文件描述符和一个事件掩码,timeout 表示等待的时间限制。
例如,下面代码使用 poll 函数等待两个文件描述符(FD)就绪,最多等待 5 秒钟:
int fd1 = open("file1", O_RDONLY);
int fd2 = open("file2", O_RDONLY);
struct pollfd fds[2];
fds[0].fd = fd1;
fds[0].events = POLLIN;
fds[1].fd = fd2;
fds[1].events = POLLIN;
int timeout = 5000; // 5 秒
int ret = poll(fds, 2, timeout);
if (ret > 0) {
if (fds[0].revents & POLLIN) {
// fd1 is ready to read
}
if (fds[1].revents & POLLIN) {
// fd2 is ready to read
}
}
如果 poll 函数返回 0,则表示超时,如果返回值大于 0,则表示有文件描述符就绪。与 select 函数不同的是,poll 函数返回的是一个事件列表,而不是具体的文件描述符。
Linux 中的 epoll 函数是 Linux 2.6.8 引入的一个新的 I/O 模型,它提供了高效的 I/O 事件通知机制,能够处理大量文件描述符,并且具有低延迟和高吞吐量的优点。
epoll 函数的原型为:
#include
int epoll_create(int size);
int epoll_ctl(int epoll_fd, int op, int fd, int event);
int epoll_wait(int epoll_fd, struct epoll_event *events, int maxevents, int timeout);
其中,epoll_create 函数用于创建一个 epoll 实例,size 参数指定要创建的 epoll 实例可以容纳的最大文件描述符数量。epoll_ctl 函数用于控制 epoll 实例,op 参数指定要进行的操作(例如,添加、修改或删除文件描述符),fd 参数指定要操作的文件描述符,event 参数指定要设置的事件掩码。epoll_wait 函数用于等待 epoll 实例中的文件描述符就绪,events 参数指向一个 epoll_event 结构数组,用于存储就绪的文件描述符和事件信息,maxevents 参数指定最多等待的文件描述符数量,timeout 参数指定等待的时间限制。
例如,下面代码使用 epoll 函数等待两个文件描述符(fd)就绪,最多等待 5 秒钟:
int fd1 = open("file1", O_RDONLY);
int fd2 = open("file2", O_RDONLY);
int epoll_fd = epoll_create(10);
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = fd1;
int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd1, &event);
event.data.fd = fd2;
ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd2, &event);
struct epoll_event events[2];
int timeout = 5000; // 5 秒
int num = epoll_wait(epoll_fd, events, 2, timeout);
if (num > 0) {
if (events[0].data.fd == fd1 && events[0].events & EPOLLIN) {
// fd1 is ready to read
}
if (events[1].data.fd == fd2 && events[1].events & EPOLLIN) {
// fd2 is ready to read
}
}
如果 epoll 函数返回 0,则表示超时,如果返回值大于 0,则表示有文件描述符就绪。与 select 和 poll 函数不同的是,epoll 函数能够处理大量的文件描述符,并且具有低延迟和高吞吐量的优点