epoll初识整理

第一次仔细看epoll的一些资料,加个笔记慢慢更 有不对的求指正哈,抄录各种网上资料 加一些自己的笔记. 仅供参考~
主要参考来源:https://www.cnblogs.com/fnlingnzb-learner/p/5835573.html

epoll大体解决什么问题

epoll用于解决socket编程的一些问题,就是那一套创建套接字,bind,listen ,accept客户端链接过来的fd,然后send&recv这一套东东。然后为什么要有epoll呢,是因为你socket里调accept然后线程就阻塞了,你需要并行处理很多socket连接的时候就只能开多线程去处理这个问题,这样增加了很多的多线程切换的性能开销。于是epoll就来了,epoll用下面的三个api以一种注册&回调的方式处理了,裸的socket编程需要开多线程阻塞等待来作并发的问题。

先来看只用socket的方案(待补充)

epoll只有三个api

  1. int epoll_create(int size);
    创建一个epoll专用的句柄,需要保存后面会用 ,这个size是你要默认监听的fd数量+1 ,因为这个方法创建好以后本身就占用一个fd,/proc/进程id/fd/ 下可以查到这个fd, 该epoll句柄被用完以后需要close 去释放这个fd,否则会导致fd用尽。(不过貌似一般的服务端epoll用完进程也就退出了,粗一点让操作系统自行回收也可不管)
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    注册epoll事件用的,用epoll模型的处理中主要就是通过这个来改变fd监听的事件的状态来完成的
  • 第一个参数是epfd 这个就是上面创建的epoll句柄
  • 第二个参数op表示动作 包括下面几个
    EPOLL_CTL_ADD:注册新的fd到epfd中;
    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
    EPOLL_CTL_DEL:从epfd中删除一个fd;
  • 第三个参数是需要监听的fd,就是服务端自己的socketfd 或者 从客户端那里accept()来的fd。
  • 第四个参数就是事件的结构体了
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

2个部分组成 一个是uint32 events

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

先简单了解后面几个都不管,第一个in 就是fd里有数据了可以读, 第二个out就是这个东东可以写了
然后就是data字段,一般就是对应的fd 用来你之后收到event 事件的时候判断这个是那个fd的

  1. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。(创建了epoll,注册了监听事件,就是用死循环epoll wait来等待事件发生了)

epoll的demo做笔记

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <unistd.h>

#define SERV_PORT  8802


int main()
{
    int i,flag;
    int sockfd,clntfd,newfd;
    int epfd,nfds;
    ssize_t n;
    char buffer[1024];
    int s = sizeof(struct sockaddr);

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    //定义epoll数据结构
    struct epoll_event ev,events[20];

    epfd = epoll_create(256);

    //创建socket,并初始化事件ev
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket error!\n");
        return -1;
    }
    ev.data.fd = sockfd;
    ev.events = EPOLLIN|EPOLLET;

    //注册epoll事件 这个fd是server端生成的fd 一开始添加的事件是读
    //accept的时候会触发
    flag = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    if (flag < 0) {
        perror("epoll_ctl error!\n");
        return -1;
    }

    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERV_PORT);
    serv_addr.sin_addr.s_addr = htonl( INADDR_ANY );

    // socket的bind 和epoll无关
    flag = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr));
    if (flag < 0) {
        perror("bind error!\n");
        return -1;
    }
    printf("bind\n");

    // socket的listen 后面的20是请求队列长度,超出这个数就不给建立新连接了
    // 每个fd只注册一个事件的话epoll wait 出来最多貌似就只有20个,复杂情况触发的事件太菜了还没研究。。
    flag = listen(sockfd, 20);
    if (flag < 0) {
        perror("listen error!\n");
        return -1;
    }
    printf("listen\n");

    //开始循环
    while (1) {
        //等待事件发生,返回请求数目 20是你传进去events的长度, 500是timeout
        nfds = epoll_wait(epfd, events, 20, 500);
        //一次处理请求
        for (i = 0; i < nfds; ++i) {
            if (events[i].data.fd == sockfd){
                // 收到了事件通知 且是server那个fd的通知

                clntfd = accept(sockfd, (struct sockaddr*)&clnt_addr,(unsigned int*)&s);
                if (clntfd < 0) {
                    perror("accept error");
                    continue;
                }
                printf("accept\n");

                char *str = inet_ntoa(clnt_addr.sin_addr);
                printf("accepnt the client ip : %s\n",str);

                //设置文件标识符,设置操作属性:写操作
                ev.data.fd = clntfd;
                ev.events = EPOLLOUT | EPOLLET;
                //向创建的的epoll进行注册写操作 
                //给accept到的客户端socket 加事件 out监听这个fd可以写入的时候通知,就是对面已经读完了缓冲区的时候
                // 后续这个fd可以写入的时候这里会收到event(后续的epoll wait)
                epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &ev);
            } else if (events[i].events & EPOLLOUT) {
                printf("EPOLLOUT\n");

                if ((newfd = events[i].data.fd) < 0)
                    continue;
                bzero(buffer,sizeof(buffer));
                strcpy(buffer,"welcome to myserver!\n");
                flag = send(newfd, buffer, 1024, 0);
                if (flag < 0) {
                    perror("send error");
                    continue;
                }
                //修改操作为读操作
                // 因为你这个fd已经写过了fd里是有数据的,这里的流程是accept之后服务端发一条 client回一条
                // 所以你这里要把这个fd的监听事件修改成in 就是这个缓冲区被对面写入了通知你 那时候你就可以处理回包
                ev.data.fd = clntfd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev);
            } else if (events[i].events & EPOLLIN) {
                printf("EPOLLIN\n");
                //一样的 这里对面写入了通知你读 你这个fd就可以读出来 然后类似上面一样绑定对应的事件做你想做的事情
                //也可以什么都不做就打印出来 

                bzero(buffer,sizeof(buffer));
                if ((newfd = events[i].data.fd) < 0)
                    continue;
                if ((n = read(newfd, buffer, 1024)) < 0) {
                    if (errno == ECONNRESET){
                        close(newfd);
                        events[i].data.fd = -1;
                        printf("errno ECONRESET!\n");
                    } else {
                        perror("readbuffer error!\n");
                    }
                } else if (n == 0) {//表示客户端已经关闭
                    close(newfd);
                    events[i].data.fd = -1;
                    printf("n为0\n");
                }
                if (buffer[0] != '0')
                    printf("have read: %s\n", buffer);
            }
        }
    }
    close(sockfd);
    return 0;
}

总结

用了epoll这套api以后,原本你需要accept 然后开线程去处理和客户端的交互(read&send 好多好多次),然后开多线程处理你的具体请求业务, 现在你只需要一个线程就可以处理所有fd的read&send了, 开多线程或者别的去处理你的业务就好。
对比一下
1 纯socket
1个主线程accept + n个处理socket的线程(在双端通信的过程中要一直保持,处理双端的交互和具体的业务任务a,b,c)
2 epoll
1个主线程accept + n个工作线程(一次读取/返回+处理任务即可不用一直保持,一次只需处理一个任务a/b/c,剩下的等回调来了再继续处理)

总结中的总结

总之就是对于某个socket 两边来回send&recv 会有很多io 然后这个线程就只能阻塞等io,这样就会有好多线程在干这事,线程一多切换就多,性能就差了。epoll这种异步回调的处理就使得你不需要有任何一个线程在等待io(来回收发包)的过程中阻塞着(你自己的业务线程里写的代码除外)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容