「面试准备」Redis 网络 - 1

99 阅读1分钟

「这是我参与2022首次更文挑战的第 23 天,活动详情查看:2022首次更文挑战」。


reactor

先说说基本的 epoll 编程:

epoll 编程

  1. epoll_create ⇒ 创建一个fd的池子,负责监管fd
  2. epoll_ctl ⇒ 负责管理这个池子的fd的增删查改
  3. epoll_wait ⇒ 负责让出CPU调度,有事件发生才会从这里被唤醒

高效的原因:

  1. 内部管理fd使用了高效的红黑树,增删改的性能优秀
  2. epoll_ctl 添加fd时,会执行回调函数;会将对应的**fd结构体(epitem)**放入就绪队列(双向链表);epoll池只需要遍历这个队列,就可以返回给上层已经就绪的fd数组

注意几个要点:

int sock_fd,conn_fd; // 监听套接字和已连接套接字的变量
sock_fd = socket() // 创建套接字
bind(sock_fd)   // 绑定套接字
listen(sock_fd) // 在套接字上进行监听,将套接字转为监听套接字
    
epfd = epoll_create(EPOLL_SIZE); //创建epoll实例,
// 创建epoll_event结构体数组,保存套接字对应文件描述符和监听事件类型    
ep_events = (epoll_event*)malloc(sizeof(epoll_event) * EPOLL_SIZE);

// 创建epoll_event变量
struct epoll_event ee
// 监听读事件
ee.events = EPOLLIN;
// 监听的文件描述符是刚创建的监听套接字
ee.data.fd = sock_fd;

// 将监听套接字加入到监听列表中    
epoll_ctl(epfd, EPOLL_CTL_ADD, sock_fd, &ee); 
    
while (1) {
   // 等待返回已经就绪的描述符 
   n = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); 
   // 遍历所有就绪的描述符     
   for (int i = 0; i < n; i++) {
       // 如果是监听套接字描述符就绪,表明有一个新客户端连接到来 
       if (ep_events[i].data.fd == sock_fd) { 
          conn_fd = accept(sock_fd); //调用accept()建立连接
          ee.events = EPOLLIN;  
          ee.data.fd = conn_fd;
          // 添加对新创建的已连接套接字描述符的监听,监听后续在已连接套接字上的读事件      
          epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ee); 
                
       } else { // 如果是已连接套接字描述符就绪,则可以读数据
           ...// 读取数据并处理
       }
   }
}
  1. 针对的socket:是服务端所在的socket,监听也是这个上的读写事件
  2. 可读事件:有客户端发送数据到服务端所在的socket → 客户端三次握手成功,开始

支撑整个框架运行的主要函数:

  1. 框架主循环的 aeMain()
  2. 实践捕获和分发的 aeProcessEvents()
  3. 负责事件和 handler注册的 aeCreateFileEvent()

主循环

就是不断判断事件循环的结束标志:如果被标记为 true,整个就结束了

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
		**// 判断是否结束**
    while (!eventLoop->stop) {
        …
        aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
    }
}