一、前言
在本篇文章中,我们将深入探究Linux内核源码中的网络编程模型epoll。
首先,我们对epoll的整体结构进行说明。其次,我们将分析epoll的实现机制,包括epoll的两个核心数据结构以及相关操作的实现。最后,我们将结合示例代码对epoll的使用进行说明。
二、整体结构
在Linux内核源码中,epoll的实现分为多个文件,并被封装在一个名为epoll.c的文件中。其中,主要的模块包括:
-
epoll_create和epoll_create1函数的实现
-
epoll_ctl函数的实现,包括增加、删除和修改文件描述符
-
epoll_wait函数的实现,等待I/O事件发生并返回就绪文件描述符
在epoll实现中,有两个核心的数据结构:
-
epoll_event,代表一个I/O事件
-
epitem,代表epoll中的一个文件描述符项
三、实现机制
- epoll_create和epoll_create1函数的实现
epoll_create和epoll_create1函数用于创建一个epoll句柄。它们的实现非常简单,可以直接参考系统调用实现文档。
- epoll_ctl函数的实现
epoll_ctl用于给epoll句柄中添加、修改或删除一个文件描述符。当添加或修改文件描述符时,我们需要将其加入到监听队列中,并设置对应的I/O事件类型。具体实现如下:
static int ep_insert(struct epitem *epi) {
struct epoll_event e;
e.events = epi->event.events;
e.data.u32 = epi->handout->id;
if (epi->event.events & (EPOLLRDHUP | EPOLLHUP)) {
e.events |= EPOLLIN | EPOLLRDNORM | EPOLLRDBAND | EPOLLINIGNEOF |
EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND;
}
return rb_insert(&epi->node, &ep.ep_events, ep_rb_cmp) != NULL;
}
在epi中,我们设置了要监听的I/O事件类型。然后,我们将其加入到epoll中的监听队列中。
- epoll_wait函数的实现
epoll_wait用于等待就绪文件描述符。在等待过程中,我们需要从epoll监听队列中找到已经就绪的文件描述符。具体实现如下:
static int ep_item_poll(struct epitem *epi, struct epoll_event *events,
int maxevents, int *timeout, bool epoll_wait) {
// ...
if (epi->nwait == epi->nready) {
ep_remove(epi);
// ...
return 0;
}
// ...
epi->nwait++;
if (ep_waitqueue(&epi->wait, &e)) {
epi->nwait--;
ep_remove(epi);
// ...
return 0;
}
// ...
}
在epi中,我们记录了当前关注的I/O事件类型和状态。当等待过程中监听到就绪文件描述符时,我们将其移除。如果没有找到就绪的文件描述符,则传递超时值以等待就绪。
四、示例说明
现在,我们来看两个示例,以更好地理解epoll的使用。
第一个示例:简单使用epoll判断TCP连接是否关闭
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAX_EVENTS 10
int main(int argc, char *argv[]) {
int sfd, efd;
struct sockaddr_in listen_addr;
struct epoll_event event;
struct epoll_event events[MAX_EVENTS];
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(8080);
listen_addr.sin_addr.s_addr = INADDR_ANY;
sfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sfd, (struct sockaddr*)&listen_addr, sizeof(listen_addr));
listen(sfd, 10);
efd = epoll_create1(0);
event.data.fd = sfd;
event.events = EPOLLIN | EPOLLRDHUP;
epoll_ctl(efd, EPOLL_CTL_ADD, sfd, &event);
while (1) {
int n = epoll_wait(efd, events, MAX_EVENTS, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == sfd) { // 有新的连接
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int cfd = accept(sfd, (struct sockaddr*)&client_addr, &client_len);
event.data.fd = cfd;
event.events = EPOLLIN | EPOLLRDHUP;
epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &event);
} else { // 接受或关闭连接
if (events[i].events & EPOLLHUP || events[i].events & EPOLLRDHUP) {
// 关闭连接
close(events[i].data.fd);
} else if (events[i].events & EPOLLIN) {
// 读取数据
char buf[1024];
int n = 0;
do {
n = read(events[i].data.fd, buf, 1024);
if (n > 0) { // 成功接受到数据
printf("Received %d bytes from %d\n", n, events[i].data.fd);
}
} while (n == 1024);
}
}
}
}
}
第二个示例:使用epoll来提高性能,支持更多连接
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#define MAX_EVENTS 10000
static int set_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
return -1;
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
return -1;
}
return 0;
}
int main(int argc, char* argv[]) {
int sfd, sfd1, efd, res;
struct sockaddr_in sa;
struct epoll_event event;
struct epoll_event events[MAX_EVENTS];
int i;
if ((sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
perror("socket()");
exit(EXIT_FAILURE);
}
set_nonblock(sfd);
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(9000);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sfd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
perror("bind()");
exit(EXIT_FAILURE);
}
if (listen(sfd, 5) == -1) {
perror("listen()");
exit(EXIT_FAILURE);
}
if ((efd = epoll_create1(0)) == -1) {
perror("epoll_create1()");
exit(EXIT_FAILURE);
}
update_epoll(efd, sfd, EPOLLIN | EPOLLET);
while (1) {
int n = epoll_wait(efd, events, MAX_EVENTS, 150000);
if (n == -1 && errno != EINTR) {
perror("epoll_pwait()");
exit(EXIT_FAILURE);
}
for (i = 0; i < n; i++) {
if ((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) ||
(!(events[i].events & (EPOLLIN | EPOLLOUT)))) {
close(events[i].data.fd);
continue;
}
if (events[i].data.fd == sfd) {
while (1) {
sfd1 = accept(sfd, NULL, NULL);
if (sfd1 == -1) {
break;
}
update_epoll(efd, sfd1, EPOLLIN | EPOLLET);
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("accept()");
exit(-1);
}
continue;
}
do_use_fd(events[i].data.fd);
}
}
return 0;
}
以上就是epoll的解析攻略的完整内容,希望能对你有所帮助!
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:解析Linux源码之epoll - Python技术站