[c++] 学习epoll的历程(附调试用demo)

2 篇文章 0 订阅

最近在看epoll查阅了很多文章,,,之所以不好理解是因为涉及到的东西太多组合方式也各不一样,在此处罗列一下。。。

两个fd

一个是 listenfd=socket(…) 得到的,用来监听socket的fd
另一个是 connfd=accpt(…) 得到的,用来交互数据的fd,是在有cli连接到socket的时候生成的
两者不同都要分别进行各自的设置

两种状态

阻塞与非阻塞
这个是针对fd进行的设置,默认阻塞,可设置成非阻塞。这两种读写的方法都有所区别,尤其是用buffer间断读写。
非阻塞较难实现但是响应快。

两种触发模式

LT(水平触发),可以类比硬件理解为“电平触发”,有响应的事件就一直响一直响一直响一直响。。。
ET(边沿触发),可以类比硬件理解为“跳变触发”,当有事件变成响应态的时候触发一次,取出所有响应的事件。
(???个人看法,,,这里可以理解为所有事件共享一个bool值,平时是0有响应就置1,多个响应覆盖1,所以et才是可能一次跳变对应多个事件)

三种操作

accept 就是上面生成connfd的操作
recv 接收数据的操作
send 返回数据的操作
后两种可能会由于buffer满会停止接收或发送,所以有特殊处理

大体流程是:
1.生成socket相关绑定监听
2.生成epoll实例,注册监听socket的事件
3.死循环并阻塞在epoll_wait
有事件响应则唤醒wait,针对每个事件判断事件类型:
listenfd:接收一个connfd,注册一个读数据事件
recv:读到不能读(如果oneshot则原事件重新注册),读完了的话do_something(){… 最后要修改注册事件为写数据}
send:写到不能写(如果oneshot则原事件重新注册),写完了的话删除注册事件,close(fd)

epoll的讲解:

通俗的原理讲解:
https://blog.csdn.net/darmao/article/details/78306200
https://blog.csdn.net/u011671986/article/details/79449853
较为详细的讲解+api+demo:
https://www.jianshu.com/p/ee381d365a29
https://blog.csdn.net/qq_19923217/article/details/81943705
https://www.cnblogs.com/zl-graduate/articles/6724446.html
https://blog.csdn.net/xn6666/article/details/80352057

epoll事件的介绍

各种事件的介绍:
https://blog.csdn.net/heluan123132/article/details/77891720
https://blog.csdn.net/gettogetto/article/details/77979412

非阻塞相关

原理:
https://blog.csdn.net/nosix/article/details/77484562
https://blog.csdn.net/u012511518/article/details/77867709
实现:
https://www.cnblogs.com/shichuan/p/4496319.html

两种触发模式+三种操作

https://blog.csdn.net/qq_24571549/article/details/79011369 (原理较多)
https://blog.csdn.net/peng314899581/article/details/78066374
https://blog.csdn.net/weiyuefei/article/details/52242880

完整例子:

https://www.cnblogs.com/yanwei-wang/p/5351771.html
https://blog.csdn.net/swj9099/article/details/83378284

问题及解决方法

EAGAIN : Resource temporarily unavailable
https://blog.csdn.net/hanyingzhong/article/details/17243059

PS

one shot的简单例子
https://www.2cto.com/kf/201310/248466.html
优化
https://blog.csdn.net/houjixin/article/details/46413583
socket相关
https://www.cnblogs.com/kefeiGame/p/7246942.html
源码详解(很不错的一篇)
https://blog.csdn.net/RUN32875094/article/details/79371818

代码demo

// MyEpoll/MyEpoll.h
#ifndef __MY_EPOLL__
#define __MY_EPOLL__
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>

#include<string>
#include<vector>

#include"MyThreadPool/MyThreadPool.h"

#define MAX_EVENT 20
#define READ_BUF_LEN 10
using namespace std;
class MyEvent
{
    public:
        std::string req;        // 请求数据
        std::string resp;       // 返回数据

        MyEvent(int param_fd):req(""), resp(""), _recv_offset(0), _send_offset(0), _fd(param_fd),_ori_url(""){};
        virtual ~MyEvent(){
            close(_fd);
        };
        //是否接收完(自己定义请求的头部数据来判断是否结束)
        bool isRecvFinish(){return true;};

        int _recv_offset;       //记录已接收位置
        int _send_offset;       //记录已发送位置
        int _fd;                //该连接的conn_fd描述符
        std::string _ori_url;   //请求原始完整串
};
class MySocketListener
{
    public:
        MySocketListener(){};
        ~MySocketListener(){};

        // 初始化,指定ip端口和同时处理并发的数量(httpworker)
        bool init(const char* ip, const int& port, const int& num);
        // 开始监听
        bool start();

        bool _handle_accept(int fd);    //处理链接事件
        bool _handle_send(int i);       //处理发送事件
        bool _handle_recv(int i);       //处理接收事件
        bool _dosth(int i);             //调用处理函数入口
        int _setnonblock(int fd);       //设置fd为非阻塞

        MyThreadPool* _pool;    //http_worker线程池
        int _epfd;              //epoll实例
        int _listenfd;          //监听socket的fd
        struct epoll_event _listenev;   //监听socket的事件
        struct epoll_event* _event_list;//处理事件的list
        struct sockaddr_in _server_addr;//监听的地址
};

class MyHttpWorker : public Worker
{
    public:
        MyHttpWorker(){};
        ~MyHttpWorker(){};
        void run();
        MyEvent *ev_ptr;
        MySocketListener* listener_ptr;
};

#endif
// MyEpoll/MyEpoll.cpp
#include "MyEpoll.h"

int MySocketListener::_setnonblock(int fd)
{
    // 获取当前flag
    int flags;
    flags = fcntl(fd, F_GETFL, 0);//获取fd当前的状态
    if (-1 == flags)
    {
        printf("[ERROR] get fd flag error");
        return -1;
    }
    // 设置flag
    if( fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)//设置增加非阻塞
    {
        printf("[ERROR] set fd flag error");
        return -1;
    }
    return 0;
}
bool MySocketListener::init(const char* ip, const int& port, const int& num)
{
    // 设置ip和端口
    _server_addr = { 0 };
    _server_addr.sin_family = AF_INET;  //指定协议
    inet_aton (ip, &(_server_addr.sin_addr));   //字符串ip转换为网络格式
    _server_addr.sin_port = htons(port);        //数字端口号转换为网络格式

    // socket相关设置
    _listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == _listenfd) {
        //perror("Open listen socket");
        return -1;
    } // 打开 socket 端口复用, 防止测试的时候出现 Address already in use
    int on = 1;
    if(-1 == setsockopt( _listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) )){
        printf("[ERROR]set socket opt error\n");
        return 0;
    } // 设置不阻塞
    if(-1 == _setnonblock(_listenfd)){
        printf("[ERROR]set socket nonblock error\n");
        return 0;
    } // 绑定
    if( -1 == bind(_listenfd, (const struct sockaddr *)&_server_addr, sizeof (_server_addr))){
        printf("[ERROR]bind error\n");
        return 0;
    } // 监听
    if( -1 == listen(_listenfd, 200)){
        printf("[ERROR]listen error\n");
        return 0;
    }

    // 开启httpworker的线程池
    _pool = new MyThreadPool();
    _pool->open(num);

    // 创建epoll实例
    _epfd = epoll_create(1);
    if (1 == _epfd) {
        //perror("Create epoll instance");
        return 0;
    }
    //创建事件
    _event_list = new epoll_event[MAX_EVENT];
    MyEvent* p_ = new MyEvent(_listenfd);

    _listenev.data.ptr = p_;
//  _listenev.data.fd = _listenfd; //data为unoin不能同时设置两个
    _listenev.events = EPOLLIN | EPOLLET /* 边缘触发选项。 */;
    // 添加epoll的事件
    if(-1 == epoll_ctl(_epfd, EPOLL_CTL_ADD, _listenfd, &_listenev)) {
        //perror("Set epoll_ctl");
        return 0;
    }
    return true;
}
bool MySocketListener::start()
{
    printf("Start Listen ...\n");
    for (;;) {
        int event_count;
        // 等待事件
        event_count = epoll_wait(_epfd, _event_list, MAX_EVENT, -1);
        for (int i = 0 ; i < event_count; i++) {
            uint32_t events = _event_list[i].events;
            MyEvent* ev_ptr = (MyEvent*)_event_list[i].data.ptr;
            // 判断epoll是否发生错误
            if ( events & EPOLLERR || events & EPOLLHUP || (! events & EPOLLIN)) {
                printf("[ERROR]Epoll has error\n");
                delete _event_list[i].data.ptr;
                continue;
            }
            // 分类处理事件
            if (_listenfd == ev_ptr->_fd && !(events&EPOLLONESHOT)) {//链接事件
                printf("FOR accept things\n");
                int accp_fd = 0;
                while( (accp_fd=accept(_listenfd, NULL, NULL)) > 0 )
                    _handle_accept(accp_fd);
            } else if (events & EPOLLOUT){  //发送数据事件
                printf("FOR send things\n");
                _handle_send(i);
            } else if (events & EPOLLIN){   //接收数据事件
                printf("FOR recv things\n");
                _handle_recv(i);
            }
        }
    }
    close (_epfd);
    return true;
}
bool MySocketListener::_handle_accept(int fd)
{
    if(-1 == _setnonblock(fd)){//设置非阻塞
        printf("[ERROR]set socket nonblock error\n");
        return false;
    }
    // 设置读数据事件
    struct epoll_event ev;
    //ev.data.fd = fd; //这里设置自定义指针就不能设置fd,fd保存在自定类型里
    MyEvent *ev_ptr = new MyEvent(fd); //为当前链接描述符创建新的交互数据的事件
    ev.data.ptr = ev_ptr;
    ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    // 为新accept的 file describe 设置epoll事件
    if (-1 == epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev)) {
        printf("[ERROR]epoll_ctl error\n");
    }
    return true;
}
bool MySocketListener::_handle_recv(int i)
{
    struct epoll_event& enevt = _event_list[i];
    MyEvent *ev_ptr = (MyEvent*)enevt.data.ptr;
    ssize_t result_len = 0;
    char buf[READ_BUF_LEN + 1] = { 0  };
    while( (result_len = read(ev_ptr->_fd, buf, READ_BUF_LEN)) > 0 ) //要循环读到没有
    {
        buf[result_len] = '\0';
        ev_ptr->_recv_offset += result_len;
        ev_ptr->_ori_url += buf;
    }
    if (-1 == result_len) { //读取出错误
        if (EAGAIN != errno) {
            printf("[ERROR] read error\n");
            perror("read error");
        }
        //读完,更新event (oneshot)
        epoll_ctl(_epfd, EPOLL_CTL_ADD, ev_ptr->_fd, &enevt);
        if(ev_ptr->isRecvFinish()) _dosth(i);
    }
    return true;
}
bool MySocketListener::_handle_send(int i)
{
    struct epoll_event& enevt = _event_list[i];
    MyEvent *ev_ptr = (MyEvent*)enevt.data.ptr;
    int result_len = 0;
    if(ev_ptr->_send_offset == 0)
    {
        size_t len = ev_ptr->resp.length();
        char* buff = new char[len+200];
        sprintf(buff,"HTTP/1.1 200 OK\r\nContent-Type: application/json;charset=utf-8\r\nContent-Length: %zd\r\nConnection: close\r\n\r\n%s",len,ev_ptr->resp.c_str());
        ev_ptr->resp = buff;
    }
    while(ev_ptr->resp.length() > ev_ptr->_send_offset)
    {
        const char* buf = ev_ptr->resp.c_str() + ev_ptr->_send_offset;
        result_len = write(ev_ptr->_fd, buf, READ_BUF_LEN);
        ev_ptr->_send_offset += result_len;
        if(result_len <= 0) break;
    }
    if (-1 == result_len) { //读取出错误
        if (EAGAIN != errno) { //读完,更新event (oneshot)
            epoll_ctl(_epfd, EPOLL_CTL_ADD, ev_ptr->_fd, &enevt);
        }
    }
    if (ev_ptr->resp.length() == ev_ptr->_send_offset)
    {
        epoll_ctl(_epfd, EPOLL_CTL_DEL, ev_ptr->_fd, NULL);
        close(ev_ptr->_fd);
        delete ev_ptr;
    }
    return true;
}

bool MySocketListener::_dosth(int i)
{
    //接收完后交给worker处理
    struct epoll_event& event = _event_list[i];
    MyHttpWorker* worker = new MyHttpWorker();
    worker->ev_ptr = (MyEvent*)event.data.ptr;
    worker->listener_ptr = this;
    _pool->addTask(worker);
}
void MyHttpWorker::run()
{
    // 添加处理逻辑
    ev_ptr->resp = ev_ptr->_ori_url;
    ev_ptr->resp += "11111111111111";
    printf("do le songthing!\n");

    //处理逻辑完成后,事件改为返回数据事件
    struct epoll_event ev;
    ev.data.ptr = ev_ptr;
    ev.events = EPOLLOUT | EPOLLET | EPOLLONESHOT;
    if (-1 == epoll_ctl(listener_ptr->_epfd, EPOLL_CTL_MOD, ev_ptr->_fd, &ev)) {
        printf("[ERROR]epoll_ctl error\n");
    }
}
// main/main.cpp
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>

#include<string>
#include<vector>

#include"MyEpoll/MyEpoll.h"
using namespace std;

int main() {

    MySocketListener* listener = new MySocketListener();
    string ip = "127.0.0.1";
    int port = 9012;
    listener->init(ip.c_str(), port, 5);
    listener->start();

    return 0;
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值