linux下I/O模型并发的epoll多进程池协程实现

3 篇文章 0 订阅

方法1

主要思路:

  1. 定义了一个EventData结构体,用于存储事件相关的数据,如文件描述符、epoll 文件描述符、协程 ID 等。
  2. EchoDeal函数用于处理请求消息,并生成响应消息。
  3. handlerClient函数是协程的执行函数,用于处理客户端连接。它通过循环读取数据、解析请求、执行业务处理、发送响应等步骤,实现了对客户端请求的处理。
  4. handler函数是主函数,用于创建监听套接字、初始化 epoll、设置非阻塞模式、添加读事件等操作。然后进入一个循环,通过 epoll_wait 等待事件发生,并根据事件类型进行相应的处理,如接受新连接、处理客户端请求等。
  5. 在主函数中,通过 fork 创建多个子进程,每个子进程都执行handler函数,从而实现多进程并发处理。

示例代码: 

#include <arpa/inet.h>  // 包含网络地址转换相关的头文件
#include <assert.h>  // 包含断言相关的头文件
#include <fcntl.h>  // 包含文件控制相关的头文件
#include <netinet/in.h>  // 包含网络协议相关的头文件
#include <stdio.h>  // 包含标准输入输出相关的头文件
#include <stdlib.h>  // 包含标准库相关的头文件
#include <sys/epoll.h>  // 包含 epoll 相关的头文件
#include <sys/socket.h>  // 包含套接字相关的头文件
#include <unistd.h>  // 包含 Unix 标准相关的头文件

#include <iostream>  // 包含 C++ 的输入输出流头文件

#include "../coroutine.h"  // 包含自定义的协程相关头文件
#include "../epollctl.hpp"  // 包含自定义的 epoll 控制相关头文件

// 定义 EventData 结构体,用于存储事件相关的数据
struct EventData {  
    EventData(int fd, int epoll_fd) : fd_(fd), epoll_fd_(epoll_fd){};  // 构造函数,初始化成员变量
    int fd_{0};  // 文件描述符
    int epoll_fd_{0};  // epoll 文件描述符
    int cid_{MyCoroutine::INVALID_ROUTINE_ID};  // 协程 ID
    MyCoroutine::Schedule *schedule_{nullptr};  // 协程调度器指针
};  // 结构体定义结束

// EchoDeal 函数,用于处理请求消息,并生成响应消息
void EchoDeal(const std::string reqMessage, std::string &respMessage) { respMessage = reqMessage; }  // 函数定义结束

// handlerClient 函数,协程的执行函数,用于处理客户端连接
void handlerClient(void *arg) {  
    EventData *eventData = (EventData *)arg;  // 将传入的 void* 指针转换为 EventData* 指针
    auto releaseConn = [&eventData]() {  // 定义一个匿名函数用于释放连接资源
        EchoServer::ClearEvent(eventData->epoll_fd_, eventData->fd_);  // 清除事件
        delete eventData;  // 释放内存
    };
    ssize_t ret = 0;  // 用于存储读取或写入的返回值
    EchoServer::Codec codec;  // 编解码器对象
    std::string reqMessage;  // 请求消息字符串
    std::string respMessage;  // 响应消息字符串
    while (true) {  // 读操作循环
        uint8_t data[100];  // 数据缓冲区
        ret = read(eventData->fd_, data, 100);  // 尝试从文件描述符读取数据
        if (ret == 0) {  // 对端关闭连接
            perror("peer close connection");  // 打印错误信息
            releaseConn();  // 释放连接资源
            return;  // 函数返回
        }
        if (ret < 0) {  // 读取错误
            if (EINTR == errno) continue;  // 被中断,继续尝试读取
            if (EAGAIN == errno or EWOULDBLOCK == errno) {  // 无数据可读
                MyCoroutine::CoroutineYield(*eventData->schedule_);  // 让出 CPU
                continue;  // 继续下一次循环
            }
            perror("read failed");  // 打印读取失败的错误信息
            releaseConn();  // 释放连接资源
            return;  // 函数返回
        }
        codec.DeCode(data, ret);  // 解码读取的数据
        if (codec.GetMessage(reqMessage)) {  // 从解码数据中获取完整的请求消息
            break;  // 跳出读循环
        }
    }
    // 执行到这里说明已经读取到一个完整的请求
    EchoDeal(reqMessage, respMessage);  // 调用处理函数生成响应
    EchoServer::Packet pkt;  // 数据包对象
    codec.EnCode(respMessage, pkt);  // 对响应进行编码
    EchoServer::ModToWriteEvent(eventData->epoll_fd_, eventData->fd_, eventData);  // 切换为监听可写事件
    ssize_t sendLen = 0;  // 已发送数据的长度
    while (sendLen!= pkt.Len()) {  // 写操作循环
        ret = write(eventData->fd_, pkt.Data() + sendLen, pkt.Len() - sendLen);  // 尝试写入数据
        if (ret < 0) {  // 写入错误
            if (EINTR == errno) continue;  // 被中断,继续尝试写入
            if (EAGAIN == errno or EWOULDBLOCK == errno) {  // 不可写
                MyCoroutine::CoroutineYield(*eventData->schedule_);  // 让出 CPU
                continue;  // 继续下一次循环
            }
            perror("write failed");  // 打印写入失败的错误信息
            releaseConn();  // 释放连接资源
            return;  // 函数返回
        }
        sendLen += ret;  // 更新已发送数据的长度
    }
    releaseConn();  // 释放连接资源
}

// handler 函数,主函数,用于创建监听套接字、初始化 epoll、设置非阻塞模式、添加读事件等操作
void handler(char *argv[]) {  
    int sockFd = EchoServer::CreateListenSocket(argv[1], atoi(argv[2]), true);  // 创建监听套接字
    if (sockFd < 0) {  // 如果创建失败
        return;  // 函数返回
    }
    epoll_event events[2048];  // epoll 事件数组
    int epollFd = epoll_create(1024);  // 创建 epoll 实例
    if (epollFd < 0) {  // 如果创建失败
        perror("epoll_create failed");  // 打印错误信息
        return;  // 函数返回
    }
    EventData eventData(sockFd, epollFd);  // 创建事件数据对象
    EchoServer::SetNotBlock(sockFd);  // 设置套接字为非阻塞
    EchoServer::AddReadEvent(epollFd, sockFd, &eventData);  // 添加可读事件
    MyCoroutine::Schedule schedule;  // 协程调度器
    MyCoroutine::ScheduleInit(schedule, 10000);  // 初始化协程池
    int msec = -1;  // 超时时间
    while (true) {  // 无限循环
        int num = epoll_wait(epollFd, events, 2048, msec);  // 等待 epoll 事件
        if (num < 0) {  // 如果等待失败
            perror("epoll_wait failed");  // 打印错误信息
            continue;  // 继续下一次循环
        } else if (num == 0) {  // 超时无事件
            sleep(0);  // 让出 CPU
            msec = -1;  // 下次超时时间设置为 -1
            continue;  // 继续下一次循环
        }
        msec = 0;  // 下次大概率还有事件,故 msec 设置为 0
        for (int i = 0; i < num; i++) {  // 处理事件
            EventData *eventData = (EventData *)events[i].data.ptr;  // 获取事件数据指针
            if (eventData->fd_ == sockFd) {  // 是监听套接字的事件
                EchoServer::LoopAccept(sockFd, 2048, [epollFd](int clientFd) {
                    EventData *eventData = new EventData(clientFd, epollFd);  // 创建新的事件数据对象
                    EchoServer::SetNotBlock(clientFd);  // 设置套接字为非阻塞
                    EchoServer::AddReadEvent(epollFd, clientFd, eventData);  // 监听可读事件
                });
                continue;  // 继续下一次循环
            }
            if (eventData->cid_ == MyCoroutine::INVALID_ROUTINE_ID) {  // 第一次事件,则创建协程
                if (MyCoroutine::CoroutineCanCreate(schedule)) {  // 可以创建协程
                    eventData->schedule_ = &schedule;  // 设置协程调度器
                    eventData->cid_ = MyCoroutine::CoroutineCreate(schedule, handlerClient, eventData, 0);  // 创建协程
                    MyCoroutine::CoroutineResumeById(schedule, eventData->cid_);  // 唤醒刚刚创建的协程处理客户端请求
                } else {
                    std::cout << "MyCoroutine is full" << std::endl;  // 输出协程已满的信息
                }
            } else {
                MyCoroutine::CoroutineResumeById(schedule, eventData->cid_);  // 唤醒之前主动让出 cpu 的协程
            }
        }
        MyCoroutine::ScheduleTryReleaseMemory(schedule);  // 尝试释放协程内存
    }
}

// main 函数
int main(int argc, char *argv[]) {  
    if (argc!= 3) {  // 如果命令行参数数量不正确
        std::cout << "invalid input" << std::endl;  // 输出错误信息
        std::cout << "example:./EpollReactorProcessPoolCoroutine 0.0.0.0 1688" << std::endl;  // 给出正确用法示例
        return -1;  // 程序返回 -1
    }
    for (int i = 0; i < EchoServer::GetNProcs(); i++) {  // 循环创建子进程
        pid_t pid = fork();  // 创建进程
        if (pid < 0) {  // 创建失败
            perror("fork failed");  // 打印错误信息
            continue;  // 继续下一次循环
        }
        if (0 == pid) {  // 子进程
            handler(argv);  // 处理客户端请求
            exit(0);  // 子进程退出
        }
    }
    while (true) sleep(1);  // 父进程进入死循环
    return 0;  // 程序正常结束返回 0
}

方法2:

思路:

  1. main 函数首先检查命令行参数数量是否正确。
  2. 然后通过循环创建子进程。
  3. 子进程调用 handler 函数。
  4. 在 handler 函数中,创建监听套接字和 epoll 实例,设置套接字为非阻塞并添加可读事件,初始化协程调度器。
  5. 进入一个无限循环,通过 epoll_wait 等待事件发生。
  6. 根据事件的类型进行处理:
    • 如果是监听套接字的事件,处理新的连接。
    • 对于客户端连接的事件,如果是第一次事件则创建协程并唤醒,否则唤醒已有的协程。
  7. 协程中的 handlerClient 函数处理客户端的读写操作。
#include <arpa/inet.h>
#include <assert.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

#include <iostream>

#include "../coroutine.h"
#include "../epollctl.hpp"

// 定义事件数据结构体
struct EventData {
  EventData(int fd, int epoll_fd) : fd_(fd), epoll_fd_(epoll_fd){};  // 构造函数
  int fd_{0};  // 文件描述符
  int epoll_fd_{0};  // epoll 文件描述符
  int cid_{MyCoroutine::INVALID_ROUTINE_ID};  // 协程 ID
  MyCoroutine::Schedule *schedule_{nullptr};  // 协程调度器指针
};

// 处理请求并生成响应的函数
void EchoDeal(const std::string reqMessage, std::string &respMessage) { respMessage = reqMessage; }

// 处理客户端的函数
void handlerClient(void *arg) {
  EventData *eventData = (EventData *)arg;  // 获取事件数据指针
  auto releaseConn = [&eventData]() {  // 定义释放连接的匿名函数
    EchoServer::ClearEvent(eventData->epoll_fd_, eventData->fd_);
    delete eventData;  // 释放内存
  };
  ssize_t ret = 0;  // 读取结果
  EchoServer::Codec codec;  // 编解码器对象
  std::string reqMessage;  // 请求消息
  std::string respMessage;  // 响应消息
  while (true) {  // 读操作循环
    uint8_t data[100];  // 数据缓冲区
    ret = read(eventData->fd_, data, 100);  // 尝试读取数据
    if (ret == 0) {  // 对端关闭连接
      perror("peer close connection");
      releaseConn();
      return;
    }
    if (ret < 0) {  // 读取错误
      if (EINTR == errno) continue;  // 被中断,继续尝试
      if (EAGAIN == errno or EWOULDBLOCK == errno) {  // 无数据可读
        MyCoroutine::CoroutineYield(*eventData->schedule_);  // 让出 CPU
        continue;
      }
      perror("read failed");
      releaseConn();
      return;
    }
    codec.DeCode(data, ret);  // 解码数据
    if (codec.GetMessage(reqMessage)) {  // 获取完整请求
      break;
    }
  }
  // 执行到这里说明已经读取到一个完整的请求
  EchoDeal(reqMessage, respMessage);  // 处理请求生成响应
  EchoServer::Packet pkt;
  codec.EnCode(respMessage, pkt);  // 编码响应
  EchoServer::ModToWriteEvent(eventData->epoll_fd_, eventData->fd_, eventData);  // 切换为监听可写事件
  ssize_t sendLen = 0;  // 已发送长度
  while (sendLen!= pkt.Len()) {  // 写操作循环
    ret = write(eventData->fd_, pkt.Data() + sendLen, pkt.Len() - sendLen);  // 尝试写入
    if (ret < 0) {  // 写入错误
      if (EINTR == errno) continue;  // 被中断,继续尝试
      if (EAGAIN == errno or EWOULDBLOCK == errno) {  // 不可写
        MyCoroutine::CoroutineYield(*eventData->schedule_);  // 让出 CPU
        continue;
      }
      perror("write failed");
      releaseConn();
      return;
    }
    sendLen += ret;  // 更新已发送长度
  }
  releaseConn();  // 释放连接资源
}

// 主处理函数
void handler(char *argv[]) {
  int sockFd = EchoServer::CreateListenSocket(argv[1], atoi(argv[2]), true);  // 创建监听套接字
  if (sockFd < 0) {
    return;
  }
  epoll_event events[2048];  // epoll 事件数组
  int epollFd = epoll_create(1024);  // 创建 epoll 实例
  if (epollFd < 0) {
    perror("epoll_create failed");
    return;
  }
  EventData eventData(sockFd, epollFd);  // 创建事件数据对象
  EchoServer::SetNotBlock(sockFd);  // 设置套接字为非阻塞
  EchoServer::AddReadEvent(epollFd, sockFd, &eventData);  // 添加可读事件
  MyCoroutine::Schedule schedule;  // 协程调度器
  MyCoroutine::ScheduleInit(schedule, 10000);  // 初始化协程池
  int msec = -1;  // 超时时间
  while (true) {
    int num = epoll_wait(epollFd, events, 2048, msec);  // 等待 epoll 事件
    if (num < 0) {  // 等待失败
      perror("epoll_wait failed");
      continue;
    } else if (num == 0) {  // 超时无事件
      sleep(0);  // 让出 CPU
      msec = -1;  // 下次超时时间设置为 -1
      continue;
    }
    msec = 0;  // 有事件,下次超时时间设置为 0
    for (int i = 0; i < num; i++) {  // 处理事件
      EventData *eventData = (EventData *)events[i].data.ptr;
      if (eventData->fd_ == sockFd) {  // 是监听套接字的事件
        EchoServer::LoopAccept(sockFd, 2048, [epollFd](int clientFd) {
          EventData *eventData = new EventData(clientFd, epollFd);
          EchoServer::SetNotBlock(clientFd);
          EchoServer::AddReadEvent(epollFd, clientFd, eventData);  // 处理新连接
        });
        continue;
      }
      if (eventData->cid_ == MyCoroutine::INVALID_ROUTINE_ID) {  // 第一次事件
        if (MyCoroutine::CoroutineCanCreate(schedule)) {  // 可以创建协程
          eventData->schedule_ = &schedule;
          eventData->cid_ = MyCoroutine::CoroutineCreate(schedule, handlerClient, eventData, 0);  // 创建协程
          MyCoroutine::CoroutineResumeById(schedule, eventData->cid_);  // 唤醒协程
        } else {
          std::cout << "MyCoroutine is full" << std::endl;
        }
      } else {
        MyCoroutine::CoroutineResumeById(schedule, eventData->cid_);  // 唤醒已有协程
      }
    }
    MyCoroutine::ScheduleTryReleaseMemory(schedule);  // 尝试释放协程内存
  }
}

int main(int argc, char *argv[]) {
  if (argc!= 3) {  // 检查命令行参数数量
    std::cout << "invalid input" << std::endl;
    std::cout << "example:./EpollReactorProcessPoolCoroutine 0.0.0.0 1688" << std::endl;
    return -1;
  }
  for (int i = 0; i < EchoServer::GetNProcs(); i++) {  // 循环创建子进程
    pid_t pid = fork();  // 创建进程
    if (pid < 0) {  // 创建失败
      perror("fork failed");
      continue;
    }
    if (0 == pid) {  // 子进程
      handler(argv);  // 处理客户端请求
      exit(0);
    }
  }
  while (true) sleep(1);  // 父进程进入死循环
  return 0;
}

总的来说,程序通过多进程和协程的结合,实现了对客户端连接的处理和高效的 I/O 操作。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值