I/O多路复用 epoll 示例程序

1. epoll_create

int epoll_create(int size);

size: 忽略,但需要大于0

返回值:一个 epoll 专用的文件描述符,当创建好 epoll 句柄后它就是会占用一个 fd 值,在使用完 epoll 后必须调用 close() 关闭,否则可能导致 fd 被耗尽

该函数会建立一个红黑树用于存储通过 epoll_ctl 注册的fd,一个 rdllist 双向链表用于存储准备就绪的fd信息。所有注册到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,相应事件发生时会调用回调方法 ep_poll_callback,把发生的事件信息 epitem 放到上面的 rdllist 双向链表中

2. epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epfd: epoll_create 返回的描述符

op: EPOLL_CTL_ADD:  注册新的 fd 到 epfd

      EPOLL_CTL_MOD: 修改已经注册的 fd 的监听事件

      EPOLL_CTL_DEL:   从 epfd 中删除注册的 fd

fd: 要ADD/MOD/DEL 的 fd

event: 注册的监听事件

struct epoll_event
{
    __uint32_t events;
    epoll_data_t data;
};

typedef union epoll_data
{
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

注册监听事件,不同于 select 在监听时告诉内核要监听的事件,epoll 提前注册要监听的事件,把事件的注册和监听分离,避免了多个将注册信息拷贝到内核。检查 fd 在红黑树中是否存在,已存在则返回,不存在则添加到树干上,然后向内核注册回调函数,用于当事件发生时向 rdllist 中插入数据

3. epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epfd: epoll_create 返回的描述符

events: 用户空间分配好的 epoll_event 结构体数组,用于保存事件发生的结果信息。events 不可以是空指针,内核只负责把数据复制到这个数组,不会在用户态中分配内存

maxevents: 允许同时发生事件的最大个数,通常为 events 数组的成员个数

timeout: 指定等待的毫秒数。为 0 表示非阻塞函数立即返回,为 -1 则阻塞一直等待直到至少一个事件发生

返回值:发生事件的个数

epoll_wait 检查是否有事件发生时,只是检查 rdllist 双向链表是否为空,如果不为空则将链表数据复制到用户态内存,同时将发生的事件个数返回给用户。与 select/poll 不同的是,epoll_wait 只返回发生了事件的fd到用户态,因此我们可以直接对返回的fd进行操作,不需再判断该fd是否有事件发生

4. 触发方式

a. LT (level trigger)

系统默认使用 LT 模式

只要 fd 还有数据可读,每次 epoll_wait 都会返回,即缓冲区剩余未读尽的数据会导致下次 epoll_wait 返回。采用LT模式,系统中一旦有大量不需要读写的就绪fd,它们使每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的fd的效率

b. ET

只要 fd 还有数据可读,则必须将数据全部读取,即缓冲区剩余未读尽的数据不会导致下次 epoll_wait 返回。采用ET模式,如果这次没有把数据全部读写完,那么下次调用 epoll_wait() 时它不会通知你,也就是它只会通知你一次,直到该fd下次发生事件才会通知你!!!这种模式比LT效率高,系统不会充斥大量不关心的就绪fd

工作在 ET 模式,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死

5. 优缺点

a. 优点

  1. 监视的fd数量不受限制。它所支持的 FD 上限是最大可以打开文件的数目
  2. 查询效率不会随着监听 fd 数量的增长而下降。select/poll 需要不断轮询所有 fd,而 epoll 只需要判断就绪链表是否为空
  3. select/poll 每次调用都要把 fd 信息从用户态拷贝到内核态,而 epoll 只要 epoll_ctl() 注册时一次拷贝

b. 缺点

  1. 不能跨平台
  2. 在连接数较少的时候,效率也不一定会优于 select/poll

6. epoll 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>
#include <arpa/inet.h>

#define MAXLISTEN   5
#define LISTENPORT  8888
#define MAXEVENTS   16

static void bindSocket(int fd)
{
    sockaddr_in addr;
    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    // addr.sin_addr.s_addr = inet_addr("192.168.100.100");
    addr.sin_port = htons(LISTENPORT);

    bind(fd, (sockaddr*)&addr, sizeof(sockaddr));
}

static void setNonBlock(int fd)
{
    int flags = 0;
    flags = fcntl(fd, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(fd, F_SETFL, flags);
}

int main(int argc, char *argv[])
{
    int listenFd, clientFd, eFd;
    int nFds, i;
    ssize_t nRead;
    char bufRead[128] = {0};

    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(clientAddr);

    struct epoll_event registerEvent;
    struct epoll_event resultEvents[MAXEVENTS];

    listenFd = socket(AF_INET, SOCK_STREAM, 0);
    bindSocket(listenFd);
    setNonBlock(listenFd);
    listen(listenFd, MAXLISTEN);

    eFd = epoll_create(1);
    registerEvent.data.fd = listenFd;
    registerEvent.events = EPOLLIN | EPOLLET;
    epoll_ctl(eFd, EPOLL_CTL_ADD, listenFd, &registerEvent);

    while (1)
    {
        nFds = epoll_wait(eFd, resultEvents, MAXEVENTS, -1);
        for (i=0; i<nFds; i++)
        {
            if (listenFd == resultEvents[i].data.fd)
            {
                while (1) // have notification on the listen socket, which means one or more incoming connections
                {
                    clientFd = accept(listenFd, (sockaddr*)&clientAddr, &len);
                    if ((clientFd == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) // processed all incoming connections
                    {
                        break;
                    }
                    printf("accepted connection from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));

                    setNonBlock(clientFd);
                    registerEvent.data.fd = clientFd;
                    registerEvent.events = EPOLLIN | EPOLLET;
                    epoll_ctl(eFd, EPOLL_CTL_ADD, clientFd, &registerEvent);
                }
            }
            else
            {
                while (1) // have data on the fd waiting to be read, must read whatever data is available completely
                {
                    nRead = read(resultEvents[i].data.fd, bufRead, sizeof(bufRead));
                    if ((nRead == -1) && (errno == EAGAIN)) // have read all data
                    {
                        break;
                    }
                    else if (nRead == 0) // the remote has closed the connection
                    {
                        printf("remote closed, close fd: %d\n", resultEvents[i].data.fd);
                        epoll_ctl(eFd, EPOLL_CTL_DEL, resultEvents[i].data.fd, NULL);
                        close(resultEvents[i].data.fd);
                        break;
                    }
                    else
                    {
                        printf("received data: %s\n", bufRead);
                    }
                }
            }
        }
    }
    close(listenFd);
    close(eFd);

    return 0;
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用C语言编写Web服务器可以使用I/O多路复用技术提高服务器的性能和并发能力。以下是基于Linux下的I/O多路复用的Web服务器实现步骤: 1. 创建socket并绑定IP地址和端口号 2. 将socket设置为非阻塞模式 3. 创建epoll句柄,并将socket加入epoll监听列表中 4. 进入事件循环,等待客户端连接请求 5. 当有客户端连接请求到来时,使用accept函数接受连接,并将新连接加入epoll监听列表中 6. 当有读写事件到来时,使用recv和send函数进行读写操作 7. 关闭连接,从epoll监听列表中删除连接 下面是一个简单的示例代码: ```c #include &lt;stdio.h> #include &lt;stdlib.h> #include &lt;string.h> #include &lt;unistd.h> #include &lt;errno.h> #include &lt;arpa/inet.h> #include &lt;sys/socket.h> #include &lt;sys/epoll.h> #define MAX_EVENTS 1024 #define BUFFER_SIZE 1024 int main(int argc, char *argv[]) { struct sockaddr_in address; int listenfd, connfd, epollfd, nfds, n, i; char buffer[BUFFER_SIZE]; struct epoll_event ev, events[MAX_EVENTS]; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } memset(&address, 0, sizeof(address)); address.sin_family = AF_INET; address.sin_addr.s_addr = htonl(INADDR_ANY); address.sin_port = htons(8080); if (bind(listenfd, (struct sockaddr *)&address, sizeof(address)) == -1) { perror("bind"); exit(1); } if (listen(listenfd, 5) == -1) { perror("listen"); exit(1); } if ((epollfd = epoll_create(1)) == -1) { perror("epoll_create"); exit(1); } ev.events = EPOLLIN; ev.data.fd = listenfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) { perror("epoll_ctl"); exit(1); } while (1) { nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(1); } for (i = 0; i &lt; nfds; i++) { if (events[i].data.fd == listenfd) { if ((connfd = accept(listenfd, (struct sockaddr *)NULL, NULL)) == -1) { perror("accept"); exit(1); } ev.events = EPOLLIN | EPOLLET; ev.data.fd = connfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) == -1) { perror("epoll_ctl"); exit(1); } } else { if ((n = recv(events[i].data.fd, buffer, BUFFER_SIZE, 0)) &lt;= 0) { close(events[i].data.fd); continue; } buffer[n] = '\0'; printf("Received message: %s\n", buffer); if (send(events[i].data.fd, buffer, n, 0) == -1) { perror("send"); exit(1); } } } } close(listenfd); close(epollfd); return 0; } ``` 此示例代码只是一个简单的Web服务器,可以作为你自己的Web服务器的基础框架。如果需要更完善的功能,你需要进一步完善代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值