一.Linux下的I/O复用与epoll详解

与select/poll不同的是,epoll采用回调函数机制,epoll只关心“活跃”的连接,无需遍历全部的文件描述符

一.为什么引出epoll?

1.select的缺点

1.select所用到的FD_SET是有限的

/linux/posix_types.h:
#define __FD_SETSIZE         1024

3.select/poll都要进行不断的将fd集合在内核空间和用户空间的来回拷贝
2.内核中实现 select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即 select要检测的句柄数越多就会越费时

2.epoll高效的奥秘(实现原理)

三大关键因素:mmap/红黑树/链表
(1) epoll_create:epoll是通过内核与用户空间mmap同一块内存映射区实现的。mmap将用户空间的一块地址和内核空间的一块地址映射到物理内存地址,使得这块物理内存对内核和用户均可见,减少用户态和内核态之间的数据交换。
(2) epoll_ctl:红黑树将存储epoll所监听的套接字,当epoll_ctl添加/删除一个套接字时,实际上是在红黑树上进行节点的插入/删除。
注意:当使用epoll_ctl函数将事件添加到红黑树上后,会完成更为关键的异步(那就是该事件都会与相应的设备驱动程序建立回调关系)
(3) epoll_wait:一旦有事件发生,就会调用注册的回调函数ep_poll_callback,该回调函数的作用是这个事件添加到就绪双向链表rdlist中。调用epoll_wait时,epoll_wait只需要检查双向链表rdlist中是否有存在注册的事件
epoll_wait的工作流程:
1.epoll_wait调用ep_poll,当rdlist为空(无就绪fd)时挂起当前进程,直到rdlist不空时进程才被唤醒
2.当有就绪fd发生时,将调用ep_poll_callback,它将相应fd对应epitem加入rdlist,导致rdlist不为空,进程被唤醒,epoll_wait将返回
3.ep_events_transfer函数将双向链表rdlist中的epitem拷贝到txlist中,并将双向链表rdlist清空
4.ep_send_event函数(很关键),它扫描txlist中的每个epitem,调用其关联fd对应的poll方法。此时对poll的调用仅仅是取得fd上较新的events(防止之前events被更新),之后将取得的events和相应的fd发送到用户空间(封装在struct epoll_event,从epoll_wait返回)。==之后如果这个epitem对应的fd是LT模式监听且取得的events是用户所关心的,则将其重新加入回rdlist(图中蓝线),否则(ET模式)不在加入rdlist。


二.epoll函数API

int epoll_create(int size); //哈希表
int epoll_create1(int flags); //红黑树

int epoll_ctl ( int epfd, int op, int fd, struct epoll_event *event );

  • 参数
    epfd:epoll_create的返回值
    fd:要操作的文件描述符
    op:操作类型 EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL
    event:指定事件,它是epoll_event结构指针类型

其中,epoll_event—>每一个文件描述符都有一个对应的epoll_event结构,该结构为 :

struct epoll_event{__unit32_t events;    // epoll事件类型:EPOLLET / EPOLLONESHOTepoll_data_t data;    // 存储用户数据
};
其中,epoll_data_t定义:typedef union epoll_data{void* ptr;  //自定义的结构体(最常用)int fd;     //指定事件所从属的目标文件描述符 uint32_t u32;uint64_t u64;} epoll_data_t;

int epoll_wait ( int epfd, struct epoll_event* events, int maxevents, int timeout );

  • 返回值:成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno
  • 参数
    timeout:指定epoll的超时时间,单位是毫秒。
    maxevents:指定最多监听多少个事件
    events:传出参数,是一个数组,epoll_wait函数返回后,所有就绪的事件

三.使用epoll接口的一般操作流程为:

(1)使用epoll_create()创建一个epoll对象,该对象与epfd关联,后续操作使用epfd来使用这个epoll对象,这个epoll对象才是红黑树,epfd作为描述符只是能关联而已。
(2)调用epoll_ctl()向epoll对象中进行增加、删除等操作。
(3)调用epoll_wait()可以阻塞(或非阻塞或定时) 返回待处理的事件集合。
(3)处理事件。

/**  -[  一般epoll接口使用描述01  ]-*/
int main(void)
{/* *   此处省略网络编程常用初始化方式(从申请到最后listen)*   并且部分的错误处理省略,我会在后面放上所有的源码,这里只放重要步骤*   部分初始化也没写*/ // [1] 创建一个epoll对象ep_fd = epoll_create(OPEN_MAX);       /* 创建epoll模型,ep_fd指向红黑树根节点 */listen_ep_event.events  = EPOLLIN;    /* 指定监听读事件 注意:默认为水平触发LT */listen_ep_event.data.fd = listen_fd;  /* 注意:一般的epoll在这里放fd */ // [2] 将listen_fd和对应的结构体设置到树上epoll_ctl(ep_fd, EPOLL_CTL_ADD, listen_fd, &listen_ep_event);while(1) { // [3] 为server阻塞(默认)监听事件,ep_event是数组,装满足条件后的所有事件结构体n_ready = epoll_wait(ep_fd, ep_event, OPEN_MAX, -1); for(i=0; i<n_ready; i++) {temp_fd = ep_event[i].data.fd;if(ep_event[i].events & EPOLLIN){if(temp_fd == listen_fd) {  //说明有新连接到来connect_fd = accept(listen_fd, (struct sockaddr *)&client_socket_addr, &client_socket_len);// 给即将上树的结构体初始化temp_ep_event.events  = EPOLLIN;temp_ep_event.data.fd = connect_fd;// 上树epoll_ctl(ep_fd, EPOLL_CTL_ADD, connect_fd, &temp_ep_event);}else {                      //cfd有数据到来n_data = read(temp_fd , buf, sizeof(buf));if(n_data == 0)  {        //客户端关闭epoll_ctl(ep_fd, EPOLL_CTL_DEL, temp_fd, NULL) //下树close(temp_fd);}else if(n_data < 0) {}do {//处理数据}while( (n_data = read(temp_fd , buf, sizeof(buf))) >0 ) ;}}else if(ep_event[i].events & EPOLLOUT){//处理写事件}else if(ep_event[i].events & EPOLLERR) {//处理异常事件}}      }close(listen_fd);close(ep_fd);
}

四. 水平触发LT / 边缘触发ET

evt.events  = EPOLLIN | EPOLLET;   /*边沿触发 */
evt.events  = EPOLLIN | EPOLLIN;   /*水平触发 */

1.ET和LT在本质上的区别

[1] 水平触发LT


从图中可以看到:只要有数据,LT方式epoll_wait就会返回
1.如果用户在监听epoll事件,当内核有事件的时候,会拷贝给用户态事件,但是如果用户只处理了一次,那么剩下没有处理的会在下一次epoll_wait再次返回该事件。
2.这样如果用户永远不处理这个事件,就导致每次都会有该事件从内核到用户的拷贝,耗费性能,但是水平触发相对安全,最起码事件不会丢掉,除非用户处理完毕。

[2]边缘触发ET


从图中可以看到:尽管还有数据未被处理,但是ET方式epoll_wait也不会返回
1.边缘触发,相对跟水平触发相反,当内核有事件到达, 只会通知用户一次,至于用户处理还是不处理,以后将不会再通知。
2.这样减少了拷贝过程,增加了性能,但是相对来说,如果用户马虎忘记处理,将会产生事件丢的情况。

2.ET和LT的区别

if ET:当且仅当有新到来的数据,epoll_wait才返回
if LT:只要有数据,epoll_wait就返回

3.那么,为什么说边沿触发(ET) 的效率更高呢?*

(1) 边沿触发只在数据到来的一刻才触发,很多时候服务器在接受大量数据时会先接受数据头部(水平触发在此触发第一次,边沿触发第一次)。
(2) 接着服务器通过解析头部决定要不要接这个数据。此时,如果不接受数据,水平触发需要手动清除,而边沿触发可以将清除工作交给一个定时的清除程序去做,自己立刻返回。
(3) 如果接受,两种方式都可以用while接收完整数据。


4.边缘触发ET的使用技巧:epoll + 非阻塞fd+ET

举例说明:Client向Server一次性发送10个字节的数据;服务器一次接受5个字节的数据,下面Server使用两种方式去读取数据:
方式1:阻塞+LT触发模式
代码分析:读取10字节的数据:(1)先执行step1的epoll_wait,再执行step2的read读取5个字节;(2)再执行step1的epoll_wait,再执行step2的read读取5个字节
结论:读取10个字节,需要调用2次epoll_wait

while (1){epoll_wait(epfd, resevent, maxi+1, -1);   //step1if (resevent[0].data.fd == connfd){len = read(connfd, buf, 5);       //step2write(STDOUT_FILEND, buf, len);}
}

方式2:非阻塞+ET触发模式+while(read)
代码分析:(1)先执行step1的epoll_wait,再执行step2的read2读取5个字节(2)继续调用step2的read读取5个字节
结论:读取10个字节,只需要调用1次epoll_wait

先用fcntl将连接的套接字connfd设置为非阻塞O_NOBLOCK
while (1){epoll_wait(epfd, resevent, maxi+1, -1); if (resevent[0].data.fd == connfd){while ((len = read(connfd, buf, 5))){//非阻塞读,有数据就轮询读,直到读完缓冲区中所有的数据write(STDOUT_FILEND, buf, len);}}
}

总结:采用[非阻塞fd+边缘触发ET+while循环读]的方式,比采用[阻塞fd+水平触发LT]的方式调用epoll_wait的次数大大减少!效率更高





二. libevent核心思想:epoll反应堆模型

一.epoll的struct epoll_event结构体

自定义结构体

struct epoll_event{__unit32_t events;    // epoll事件类型:EPOLLET / EPOLLONESHOTepoll_data_t data;    // 存储用户数据
};
其中,epoll_data_t定义:typedef union epoll_data{void* ptr;  //自定义的结构体(最常用)int fd;     // 一般uint32_t u32;uint64_t u64;} epoll_data_t;

还记得每一个在红黑树上的文件描述符所对应的结构体epoll_event吗?
1.一般在epoll_event结构体中的联合体data上传入的是文件描述符fd本身
2.但是在epoll模型中,传入联合体的是一个自定义结构体指针,该结构体的基本结构至少包括:

struct my_events {  int        m_fd;                             //监听的文件描述符void       *m_arg;                           //泛型参数void       (*call_back)(void *arg);          //回调函数/**  你可以在此处封装更多的数据内容*  例如用户缓冲区、节点状态、节点上树时间等等*/
};
/** 注意:用户需要自行开辟空间存放my_events类型的数组,并在每次上树前用epoll_data_t里的  *      ptr指向一个my_events元素。*/

根据该模型,在程序中可以让所有的事件都拥有自己的回调函数,只需要使用ptr传入即可。示例代码:

while(1) {int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000); /* 监听红黑树, 1秒没事件满足则返回0 */ if (n_ready > 0) {for (i=0; i<n_ready; i++) events[i].data.ptr->call_back(/* void *arg */); //调用回调函数}else/*  * 这里可以做很多很多其他的工作,例如定时清除没读完的不要的数据*     也可以做点和数据库有关的设置*     玩大点你在这里搞搞分布式的代码也可以*/
}

二.epoll反应堆模型


1.传统的epoll服务器模型

监听可读事件(ET) ⇒ 数据到来 ⇒ 触发读事件 ⇒
epoll_wait()返回 ⇒ read消息 ⇒ write回射信息 ⇒ 继续epoll_wait()
⇒ 直到程序停止前都是这么循环


2.epoll反应堆服务器模型

监听可读事件(ET) ⇒ 数据到来 ⇒ 触发读事件 ⇒
epoll_wait()返回 ⇒
read完数据; 节点下树; 设置监听写事件和对应写回调函数; 节点上树(可读事件回调函数内)

⇒ 监听可写事件(ET) ⇒ 对方可读 ⇒ 触发事件 ⇒
epoll_wait()返回 ⇒
write数据; 节点下树; 设置监听读事件和对应可读回调函数; 节点上树(可写事件回调函数内)
⇒ 直到程序停止前一直这么交替循环


3.为什么epoll反应堆模型要这样设计

①如此频繁的在红黑树上增添/删除节点是不是浪费CPU资源?

答:epoll反应堆模型中,对于同一个socket而言,完成收发信息至少占用两个树上的位置。而传统的epoll服务器模型中,完成收发信息只需要一个树上位置。任何一种设计方式都会浪费CPU资源,关键看浪费的值不值,此处的耗费能否换来更大的收益是决定是否浪费的标准。

②为什么要可读以后设置可写?然后一直交替?

服务器向客户端write数据,并不一定能write成功,原因有二
(1) 滑动窗口机制
服务器向客户端write数据,假设刚好此时客户端的接收滑动窗口满,将导致当前服务器将阻塞在send函数处,导致服务器程序阻塞。
解决方案:设置可写事件,当客户端的接收缓冲区有空闲时,将导致该socket可写,在可写回调函数中调用send函数
(2) SIGPIPE信号
客户端send完数据后,突然由于异常停止,这将导致一个FIN发送给服务器。如果服务器不设置可写事件监听,那么服务器在read完数据后,直接向没有读端的套接字中写入数据,TCP协议栈将会给服务器发送RST分节+SIGPIPE信号,导致服务器进程终止。



三.epoll反应堆模型代码

Server

#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>#define MAX_EVENTS  1024
#define SERVER_PORT 8888struct my_events {int        m_fd;                                         //监听的文件描述符int        m_event;                                      //监听的事件  void       *m_arg;                                       //泛型参数void       (*call_back)(int fd, int event, void *arg);   //回调函数char       m_buf[BUFSIZ];int        m_buf_len;int        m_status;                                     //是否在红黑树上, 1->在, 0->不在time_t     m_lasttime;                                   //最后放入红黑树的时间
};int                    ep_fd;                                //红黑树根
struct my_events       ep_events[MAX_EVENTS];   /*初始化监听socket*/
void initlistensocket(int ep_fd, unsigned short port);
/*将结构体成员变量初始化*/
void eventset(struct my_events *my_ev, int fd, void (*call_back)(int fd, int event, void *arg), void *event_arg);
/*向红黑树添加 文件描述符和对应的结构体*/
void eventadd(int ep_fd, int event, struct my_events *my_ev);
/*从红黑树上删除 文件描述符和对应的结构体*/
void eventdel(int ep_fd, struct my_events *ev);
/*发送数据*/
void senddata(int client_fd, int event, void *arg);
/*接收数据*/
void recvdata(int client_fd, int event, void *arg);
/*回调函数: 接收连接*/
void acceptconnect(int listen_fd, int event, void *arg);int main(void)
{unsigned short port = SERVER_PORT;ep_fd = epoll_create(MAX_EVENTS);                         //创建红黑树,返回给全局变量ep_fd;if (ep_fd <= 0)printf("create ep_fd in %s error: %s \n", __func__, strerror(errno));/*初始化监听socket*/initlistensocket(ep_fd, port);int checkpos = 0;int i;struct epoll_event events[MAX_EVENTS]; //epoll_wait的传出参数(数组:保存就绪事件的文件描述符)while (1){/*超时验证,每次测试100个连接,60s内没有和服务器通信则关闭客户端连接*/long now = time(NULL); //当前时间for (i=0; i<100; i++,checkpos++) //一次循环检测100个,使用checkpos控制检测对象{if (checkpos == MAX_EVENTS-1)checkpos = 0;if (ep_events[i].m_status != 1) //不在红黑树上continue;long spell_time = now - ep_events[i].m_lasttime; //客户端不活跃的时间if (spell_time >= 60) //如果时间超过60s{printf("[fd= %d] timeout \n", ep_events[i].m_fd);  close(ep_events[i].m_fd); //关闭与客户端连接eventdel(ep_fd, &ep_events[i]); //将客户端从红黑树摘下}     }/*监听红黑树,将满足条件的文件描述符加至ep_events数组*/ int n_ready = epoll_wait(ep_fd, events, MAX_EVENTS, 1000); //1秒没事件满足则返回0if (n_ready < 0){printf("epoll_wait error, exit \n");break;}for (i=0; i<n_ready; i++){//将传出参数events[i].data的ptr赋值给"自定义结构体ev指针"struct my_events *ev = (struct my_events *)(events[i].data.ptr); if ((events[i].events & EPOLLIN) && (ev->m_event & EPOLLIN))  //读就绪事件ev->call_back(ev->m_fd, events[i].events, ev->m_arg);if ((events[i].events & EPOLLOUT) && (ev->m_event & EPOLLOUT)) //写就绪事件ev->call_back(ev->m_fd, events[i].events, ev->m_arg);}}return 0;
}     /*初始化监听socket*/
void initlistensocket(int ep_fd, unsigned short port)
{int                  listen_fd;struct sockaddr_in   listen_socket_addr;printf("\n initlistensocket() \n");  int opt = 1;setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//端口复用/*申请一个socket*/listen_fd = socket(AF_INET, SOCK_STREAM, 0);fcntl(listen_fd, F_SETFL, O_NONBLOCK); //将socket设置为非阻塞模式,好处自行百度/*绑定前初始化*/bzero(&listen_socket_addr, sizeof(listen_socket_addr));listen_socket_addr.sin_family      = AF_INET;listen_socket_addr.sin_port        = htons(port);listen_socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);/*绑定*/bind(listen_fd, (struct sockaddr *)&listen_socket_addr, sizeof(listen_socket_addr));/*设置监听上限*/listen(listen_fd, 128);/*将listen_fd初始化*/eventset(&ep_events[MAX_EVENTS-1], listen_fd, acceptconnect, &ep_events[MAX_EVENTS-1]);    /*将listen_fd挂上红黑树*/eventadd(ep_fd, EPOLLIN, &ep_events[MAX_EVENTS-1]);return ;
}/*将结构体成员变量初始化*/
void eventset(struct my_events *my_ev, int fd, void (*call_back)(int, int, void *), void *event_arg)
{my_ev->m_fd       = fd;my_ev->m_event    = 0; //开始不知道关注的是什么事件,因此设置为0my_ev->m_arg      = event_arg;my_ev->call_back  = call_back;my_ev->m_status   = 0; //0表示没有在红黑树上my_ev->m_lasttime = time(NULL);//调用eventset函数的绝对时间return ;
}/*向红黑树添加文件描述符和对应的结构体*/
void eventadd(int ep_fd, int event, struct my_events *my_ev)
{int op;struct epoll_event epv;epv.data.ptr = my_ev;epv.events   = my_ev->m_event = event; //EPOLLIN或EPOLLOUTif (my_ev->m_status == 0){op = EPOLL_CTL_ADD;}else{printf("\n add error: already on tree \n");return ;}if (epoll_ctl(ep_fd, op, my_ev->m_fd, &epv) < 0) //实际添加/修改{printf("\n event add/mod false [fd= %d] [events= %d] \n", my_ev->m_fd, my_ev->m_event);}else{my_ev->m_status = 1;printf("\n event add ok [fd= %d] [events= %d] \n", my_ev->m_fd, my_ev->m_event);}return ;
}
/*从红黑树上删除 文件描述符和对应的结构体*/
void eventdel(int ep_fd, struct my_events *ev)
{if(ev->m_status != 1)return ;epoll_ctl(ep_fd, EPOLL_CTL_DEL, ev->m_fd, NULL);ev->m_status = 0;return ;
}/*回调函数: 接收连接*/
void acceptconnect(int listen_fd, int event, void *arg)
{int                 connect_fd;int                 i;int                 flag=0;char                str[BUFSIZ];struct sockaddr_in  connect_socket_addr;socklen_t           connect_socket_len;if ( (connect_fd=accept(listen_fd, (struct sockaddr *)&connect_socket_addr, &connect_socket_len)) <0 ){if (errno != EAGAIN && errno != EINTR){/*暂时不处理*/}printf("\n %s: accept, %s \n", __func__, strerror(errno));return ;}do{for(i=0; i<MAX_EVENTS; i++) //从全局数组ep_events中找一个空闲位置i(类似于select中找值为-1的位置)if(ep_events[i].m_status == 0) break;if(i >= MAX_EVENTS){printf("\n %s : max connect [%d] \n", __func__, MAX_EVENTS);break;}      /* 设置非阻塞 */if((flag = fcntl(connect_fd, F_SETFL, O_NONBLOCK)) <0){printf("\n %s: fcntl nonblocking false, %s \n", __func__, strerror(errno));break;}eventset(&ep_events[i], connect_fd, recvdata, &ep_events[i]);eventadd(ep_fd, EPOLLIN, &ep_events[i]);}while(0);printf("\n new connection [%s:%d]  [time:%ld]  [pos:%d] \n", inet_ntop(AF_INET, &connect_socket_addr.sin_addr, str, sizeof(str)), ntohs(connect_socket_addr.sin_port), ep_events[i].m_lasttime, i);return ;
}/*接收数据*/
void recvdata(int client_fd, int event, void *arg)
{int              len;struct my_events *ev = (struct my_events *)arg;len = recv(client_fd, ev->m_buf, sizeof(ev->m_buf), 0);//1.将ep_fd从红黑树拿下eventdel(ep_fd, ev);                                      if (len >0){ev->m_buf_len      = len;ev->m_buf[len] = '\0'; //手动添加结束标记printf("\n Client[%d]: %s \n", client_fd, ev->m_buf);eventset(ev, client_fd, senddata, ev); //2.设置client_fd对应的回调函数为senddataeventadd(ep_fd, EPOLLOUT, ev); //3.将ep_fd放上红黑树,监听写事件EPOLLOUT}else if (len == 0){close(ev->m_fd);eventdel(ep_fd, ev);printf("\n [Client:%d] disconnection \n", ev->m_fd);}else{close(ev->m_fd);eventdel(ep_fd, ev);printf("\n error: [Client:%d] disconnection\n", ev->m_fd);}return ;
}/*发送数据*/
void senddata(int client_fd, int event, void *arg)
{int              len; struct my_events *ev = (struct my_events *)arg;len = send(client_fd, ev->m_buf, ev->m_buf_len, 0);   //回写if (len > 0){printf("\n send[fd=%d], [len=%d] %s \n", client_fd, len, ev->m_buf);eventdel(ep_fd, ev);  //1.将ep_fd从红黑树拿下eventset(ev, client_fd, recvdata, ev); //2.设置client_fd对应的回调函数为recvdataeventadd(ep_fd, EPOLLIN, ev); //3.将ep_fd放上红黑树,监听读事件EPOLLIN }else{close(ev->m_fd);eventdel(ep_fd, ev);printf("\n send[fd=%d] error \n", client_fd);}return ;
}

Client

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>#define MAX_LINE (1024)
#define SERVER_PORT (7778)void setnoblocking(int fd)
{int opts=0;opts=fcntl(fd,F_GETFL);opts=opts|O_NONBLOCK;fcntl(fd,F_SETFL);
}int main(int argc,char* argv[])
{int sockfd;char recvbuf[MAX_LINE+1]={0};struct sockaddr_in server_addr;/*if(argc!=2){fprintf(stderr,"usage ./cli <SERVER_IP> \n");exit(0);}*/if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0){fprintf(stderr,"socket error");exit(0);}bzero(&server_addr,sizeof(server_addr));server_addr.sin_family=AF_INET;server_addr.sin_port=htons(SERVER_PORT);server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");  /*if(inet_pton(AF_INET,argv[1],&server_addr.sin_addr)<=0){fprintf(stderr,"inet_pton error for %s",argv[1]);exit(0);}*/if(connect(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr))<0){perror("connect");fprintf(stderr,"connect error\n");exit(0);}setnoblocking(sockfd);char input[100];int n=0;int count=0;while(fgets(input,100,stdin)!=NULL){printf("[send]:%s\n",input);n=send(sockfd,input,strlen(input),0);if(n<0){perror("send");}n=0;count=0;while(1){n=read(sockfd,recvbuf+count,MAX_LINE);if(n==MAX_LINE){count+=n;continue;}else if(n<0){perror("recv");break;}else {count+=n;recvbuf[count]='\0';printf("[recv]:%s\n",recvbuf);break;}}}return 0;
}

libevent核心思想:epoll反应堆模型相关推荐

  1. Epoll 反应堆模型核心原理及代码讲解

    Epoll 反应堆模型核心原理及代码讲解 [Ⅰ] Epoll 原理及应用 && ET模式与LT模式 [Ⅱ] Epoll 反应堆模型核心原理及代码讲解 一.反应堆核心原理 二.反应堆模型 ...

  2. epoll反应堆模型

    ================================ 下面代码实现的思想:epoll反应堆模型:( libevent 网络编程开源库 核心思想) 1. 普通多路IO转接服务器: 红黑树 ― ...

  3. Linux网络编程7——epoll反应堆模型

    学习视频链接 16-epoll反应堆main逻辑_bilibili_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA?p=81& ...

  4. epoll反应堆模型代码

    libevent函数库核心思想 /*** epoll_loop.c ***/ #include<stdio.h> #include<sys/epoll.h> #include& ...

  5. linux C 基于事件回调的epoll反应堆模型

    先看一眼事件声明的结构体 #define MAX_EVENTS 1024 //监听上限数 #define BUFLEN 4096 #define SERV_PORT 8080void recvdata ...

  6. 用Linux / C实现基于自动扩/减容线程池+epoll反应堆检测沉寂用户模型的服务器框架(含源码)

    用Linux/ C实现基于自动扩/减容线程池+epoll反应堆模型的服务器框架 前言 服务器端源码 客户端源码 自定义库 helper.c 和 helper.h helper.c helper.h M ...

  7. linux网络编程 epoll水平触发、边沿触发、反应堆模型、线程池思想

    打开文件上限的设置可修改配置文件: etc/security/limits.conf 水平.边沿触发: 若客户端发送1000B数据,服务器一次只读500B.在水平触发的模式下,服务器会再调用一次epo ...

  8. 主成分分析(Principle Component Analysis)PCA的核心思想是什么?PCA算法的优缺点?增量PCA模型, 随机PCA, 模型、 核化PCA分别是什么?使用时如何选择?

    主成分分析(Principle Component Analysis)PCA的核心思想是什么?增量PCA模型, 随机PCA, 模型. 核化PCA分别是什么?使用时如何选择? 主成分分析(Princip ...

  9. epoll服务器反应堆模型

    常规的epoll处理 epoll是io多路复用的一种实现方式,最开始我们使用epoll是对多个fd进行管理,当epoll_wait从内核的rdllist就绪链表中取出一定数量的poll_event时, ...

  10. NLP之GPT-3:NLP领域没有最强,只有更强的模型—GPT-3的简介(本质、核心思想、意义、特点、优缺点、数据集、实际价值,模型强弱体现,开源探讨,GPT系列对比与总结)、安装、使用方法之详细攻略

    NLP之GPT-3:NLP领域没有最强,只有更强的模型-GPT-3的简介(本质.核心思想.意义.特点.优缺点.数据集.实际价值,模型强弱体现,开源探讨,GPT系列对比与总结).安装.使用方法之详细攻略 ...

最新文章

  1. 2019成考计算机几时出成绩,2019年成人高考成绩什么时候出来?如何查询
  2. 四川托普计算机职业学校里能拿什么快递,四川托普计算机职业学校怎么样_招生问答...
  3. springmvc集成oracle,SpringMVC整合druid
  4. cudnn.h: No such file or directory
  5. mac版smali2java_Android反编译apk并重新打包签名(Mac环境)
  6. 总线控制内部eep_CAN总线在新能源汽车中的通信网络设计及应用分析
  7. swift:Optional Type 、Swift和Objective-C混编的讲解
  8. Git 使用的问题总结
  9. 【Java数据结构】栈和队列
  10. 数据结构 3-2-2 队列的顺序存储实现
  11. iOS Xcode 调试 Unable to fix code signing issue
  12. 2021年江苏省高考成绩什么时候可以查询,2021年江苏高考成绩什么时候出来 成绩查询时间...
  13. JS判断数组是否包含某个元素
  14. App数据抓取(Appium使用)
  15. Android开发的各个领域发展前景?路线?规划未来看这篇
  16. 区块链概念股2019:躁动与尴尬 |链捕手
  17. 麒麟A1手表升级鸿蒙,麒麟A1自研芯片加持,打造年度续航旗舰手表华为WATCH GT 2...
  18. 用GCTA计算亲缘关系矩阵和遗传力
  19. ElasticSearch-2
  20. 虚拟机上网与linux kali2018和windows虚拟机ip地址修改的方法(保姆级图文)

热门文章

  1. GIS空间分析 栅格数据分析3 可达性分析
  2. 无线通信与编码_新型OFDM波形集_使用MATLAB仿真实现UFMC并与OFDM作对比_含实现代码
  3. 微软笔试题《Arithmetic Puzzles》- 题解
  4. 成都有哪些计算机科学与技术专业就业前景,成都市计算机学校计算机科学与技术专业就业去向...
  5. 软考软件设计师(知识产权)
  6. nvidia驱动缓存能清理吗?
  7. 《The industrial age of hacking》略读
  8. 端口扫描之FTP反弹扫描
  9. 黑莓9900 刷机体验(ROM:7.1.0.318_DoCoMo_Japan版)
  10. 百度地图之离线下载功能