IT袋

当前位置:主页 > 经验教程 > 系统教程 >

Linux

Linux I/O模型详解(6)

时间:2024-01-14 17:25:08 来源:IT袋 作者:马勇
导读:Linux,链表用于存储所有的事件, 当监听的事件发生后, 内核会回调 ep_poll_callback 函数把这个事件添加到 rdllist 这个双向链表中。一旦有事件发生,epoll 就会将该

Linux

链表用于存储所有的事件, 当监听的事件发生后, 内核会回调 ep_poll_callback 函数把这个事件添加到 rdllist 这个双向链表中。一旦有事件发生,epoll 就会将该事件添加到双向链表中。那么当我们调用 epoll_wait 时,epoll_wait 只需要检查 rdlist 双向链表中是否有存在注册的事件,效率非常可观。这里也需要将发生了的事件复制到用户态内存中即可。

下面看下 epoll 的相关函数

#include <sys/epoll.h>
// 该 size 已经没有意义, epoll 早期实现中, 对于监控文件描述符的组织并不是使用红黑树,而是hash表
int epoll_create ( int size );
// epfd: epoll的描述符, epoll_create 的返回值
// fd:要操作的文件描述符
// op:指定操作类型
//     EPOLL_CTL_ADD:往事件表中注册fd上的事件
//     EPOLL_CTL_MOD:修改fd上的注册事件
//     EPOLL_CTL_DEL:删除fd上的注册事件
// event:指定事件 可读,可写及异常等事件
int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event );
// epfd: epoll的描述符, epoll_create 的返回值
// events:检测到事件,将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。
// maxevents:指定最多监听多少个事件
// timeout:指定epoll的超时时间,单位是毫秒。当timeout为-1是,epoll_wait调用将永远阻塞,直到某个时间发生。当timeout为0时,epoll_wait调用将立即返回。
// 返回:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno  
int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );

因为 epoll 是 Linux 特有的,在 Java 中,要使用 epoll 机制,通常需要通过 JNI(Java Native Interface)来调用操作系统特定的 epoll 函数。

以下为 基于 C++ 实现的 epoll 示例:

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <fcntl.h>
int create_and_bind(int port) {
  int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
  // 设置 socket 属性
  struct sockaddr_in server_addr{};
  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_port = htons(port);
  // 绑定端口
  bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr))
  return listen_fd;
}
int main() {
  // 创建服务端
  int listen_fd = create_and_bind(8080);
  listen(listen_fd, 10)
  // 创建 epoll 
  int epoll_fd = epoll_create1(0);
  // 初始化epoll结构并将服务端 fd 加入 epoll
  struct epoll_event event{};
  event.data.fd = listen_fd;
  event.events = EPOLLIN;
  if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event) == -1) {
      perror("epoll_ctl");
      exit(EXIT_FAILURE);
  }
  struct epoll_event events[64];
  while (true) {
    // 等待 epoll 返回事件
    int num_events = epoll_wait(epoll_fd, events, 64, -1);
    // 处理事件
    for (int i = 0; i < num_events; ++i) {
      // 如果是服务端监听的 fd, 那么表示新的客户端连接进来
      if (events[i].data.fd == listen_fd) {
        // 调用 accept, 接受客户端连接
        int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
        // 将新的连接也加入到 epoll 中    
        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);
      } else {
        // 如果不是服务端监听的 fd, 那么就是与客户端的连接有数据进来
        // read 方法读数据
        ssize_t n = read(client_fd, buffer, sizeof(buffer));
        if (n <= 0) {
            // 客户端断开连接
        } else {
           // 数据读取成功, 处理业务
        }
      }
    }
  }
  close(listen_fd);
  return 0;
}

相关阅读