【C语言】epoll

您所在的位置:网站首页 c语言中select函数 【C语言】epoll

【C语言】epoll

2024-07-15 09:02| 来源: 网络整理| 查看: 265

一、epoll_wait和select对比 1. 阻塞和非阻塞

在Linux C语言中进行socket编程时,`epoll_wait` 和 select 都是用于多路I/O复用的系统调用,但是它们的行为可以设置为阻塞和非阻塞模式,这取决于调用它们时所使用的参数。

让我们分别看看 epoll_wait 和 select:

epoll_wait

   epoll_wait 函数用于等待由 epoll 文件描述符指向的事件,它可以工作在阻塞模式,也可以工作在非阻塞模式。

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

   在这里,`timeout` 参数决定 epoll_wait 的行为,其值可以是:    - -1 表示无限期阻塞,直到有事件发生。    - 0 表示非阻塞调用,即使没有事件,也立即返回。

   - 大于0的值表示等待指定毫秒数,如果在这个时间段内没有事件发生,它将返回。

select

   select 函数也是多路I/O复用的调用,它监视一组文件描述符,以查看是否有数据可读、可写或有异常。

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

   其中,`timeout` 参数同样控制 select 的行为,可以设置为:    - NULL 表示无限期阻塞,直到有文件描述符就绪。    - 其中的 tv_sec 和 tv_usec 成员均为 0 表示非阻塞调用,立即返回。

   - 时间结构设置为某个特定的时间量(秒和微秒),这意味着 select 会阻塞但最多等待这段时间。

`epoll_wait` 和 select 都可以根据需求设置为阻塞或非阻塞。在性能方面,`select` 受制于能够监视的文件描述符数量的限制(通常是FD_SETSIZE,通常为1024),而 epoll 更适合大规模文件描述符的监视,因为 epoll 内部使用了不同的机制,它不受固定大小限制,并且当活动文件描述符的数量远小于总数时,它能提供更好的性能。

选择使用 select 或 epoll(以及 poll),取决于具体的应用需求以及对性能和可扩展性的考量。在现代Linux系统上,`epoll` 通常是大规模且长时间运行的网络服务程序的首选。

2. 工作方式

在Linux C语言socket编程中,`epoll_wait`和`select`都是用来监视多个文件描述符的可读、可写、异常等状态的系统调用,但它们的工作方式有所不同。

select

select函数是POSIX标准的一部分,它允许应用程序监视一组文件描述符,等待一个或多个文件描述符成为非阻塞状态。`select`使用一个固定大小的文件描述符集合,并且在每次调用`select`时,都需要重新设置文件描述符集和超时时间。这种方法在监控大量文件描述符时效率不高,因为它需要线性地检查每一个文件描述符。

epoll

epoll系列函数是Linux特有的,提供了一种更高效的机制来处理大量文件描述符。`epoll`使用一个事件表来跟踪每个文件描述符的状态,并在文件描述符状态改变时通知应用程序。`epoll`的优势在于它不需要在每次调用时都重新设置文件描述符集合,而且它能以常数时间复杂度管理文件描述符集合,这在处理大量文件描述符时可以提供更好的性能。

当使用`epoll`时,通常是通过以下步骤操作的:

1. 使用`epoll_create`创建一个`epoll`实例。 2. 使用`epoll_ctl`添加、修改或删除要监视的文件描述符。

3. 使用`epoll_wait`等待事件的发生,并处理发生的事件。

如果选择使用`epoll`系列函数来进行socket编程,就不需要使用`select`函数了,因为`epoll`提供了更高效且功能更完备的替代方案。在处理大规模并发连接时,`epoll`通常是更好的选择。

二、epoll代码示例 1. python示例

epoll 是 Linux 上的一个高效的 IO 事件通知系统,它能够告诉你哪些文件描述符(sockets、文件、pipes等)已经准备好执行非阻塞的读取或写入操作。与传统的 select 和 poll 相比,`epoll` 在处理大量文件描述符时更加高效。下面是一个简单的 epoll 示例代码。

注意:以下示例代码仅适用于 Linux 系统。

import socket import select import errno # 创建一个 TCP/IP 套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置套接字选项,允许我们重新绑定同一个端口 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定套接字到端口 server_address = ('localhost', 10000) print('开始在 %s 端口 %s 上监听' % server_address) server_socket.bind(server_address) # 监听入站连接 server_socket.listen(1) # 设置非阻塞模式 server_socket.setblocking(0) # 创建一个 epoll 对象 epoll = select.epoll() # 在 epoll 事件循环中注册服务器套接字,监听读事件 EPOLLIN = select.EPOLLIN epoll.register(server_socket.fileno(), EPOLLIN) try: connections = {} requests = {} responses = {} while True: # 等待事件发生,可能会使得程序进入阻塞状态 events = epoll.poll(1) for fileno, event in events: if fileno == server_socket.fileno(): # 新入站连接 connection, client_address = server_socket.accept() print('新连接来自', client_address) connection.setblocking(0) # 注册读事件 epoll.register(connection.fileno(), EPOLLIN) connections[connection.fileno()] = connection requests[connection.fileno()] = b'' responses[connection.fileno()] = b'' elif event & EPOLLIN: # 可读事件 data = connections[fileno].recv(1024) if data: print(f"接收到 {len(data)} 字节: '{data.decode()}'") requests[fileno] += data else: # 如果没有数据,意味着客户端关闭了连接 epoll.unregister(fileno) connections[fileno].close() del connections[fileno], requests[fileno], responses[fileno] elif event & select.EPOLLOUT: # TODO: 可写事件处理逻辑 # 在这里处理 responses 字典中待发送的数据 pass elif event & select.EPOLLHUP: # TODO: 处理挂起的连接 epoll.unregister(fileno) connections[fileno].close() del connections[fileno] finally: # 释放资源 epoll.unregister(server_socket.fileno()) epoll.close() server_socket.close()

这段代码创建了一个 TCP 服务器,它使用 epoll 来管理每个连接的事件。在这个简单的例子中,服务器仅仅是读取客户端数据,并打印出来。

注意代码中有几个 TODO 注释,提示在哪里添加处理可写事件和挂起连接的代码。对于一个完整的服务器,通常还需要处理客户端发送的请求,并产生响应发送回客户端。

确保在实际运行这段代码前,已经对 epoll、非阻塞套接字和事件驱动编程有所理解。通过细读并且动手实践,将能够理解在一个高性能网络程序中如何使用 epoll。

2. C语言示例

Linux C 语言中使用 epoll 的 socket 编程示例相对复杂,因为它涉及对 socket API 的理解以及对事件驱动编程模型的使用。以下是一个简单的 epoll 示例,这个示例程序的目的是创建一个 TCP echo 服务器,它使用 epoll 来处理多个客户端连接。

请注意,以下代码将仅作为示教用途,它并没有处理所有可能的错误,并且不应该在生产环境中直接使用。此外,确保在编译时链接到 -lnsl 库(如果需要)。

#include #include #include #include #include #include #include #include #include #define PORT 12345 #define MAX_EVENTS 32 void die(const char *msg) { perror(msg); exit(EXIT_FAILURE); } int main() { int listenfd, connfd, epollfd, nfds, i; struct sockaddr_in addr; struct epoll_event ev, events[MAX_EVENTS]; // 创建和绑定 socket listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) die("socket() failed"); bzero(&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(PORT); addr.sin_addr.s_addr = INADDR_ANY; if (bind(listenfd, (struct sockaddr *)&addr, sizeof(addr)) != 0) die("bind() failed"); // 监听 socket if (listen(listenfd, 10) != 0) die("listen() failed"); // 创建 epoll 实例 epollfd = epoll_create1(0); if (epollfd < 0) die("epoll_create1() failed"); ev.events = EPOLLIN; ev.data.fd = listenfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) die("epoll_ctl: listenfd"); for (;;) { nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds < 0) die("epoll_wait() failed"); for (i = 0; i < nfds; ++i) { if (events[i].data.fd == listenfd) { connfd = accept(listenfd, NULL, NULL); if (connfd < 0) die("accept() failed"); ev.events = EPOLLIN | EPOLLET; ev.data.fd = connfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, connfd, &ev) == -1) die("epoll_ctl: connfd"); } else { char buffer[256]; ssize_t bytes_read; // 处理客户端数据 bytes_read = read(events[i].data.fd, buffer, sizeof(buffer)); if (bytes_read


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3