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

  • [Ⅰ] Epoll 原理及应用 && ET模式与LT模式
  • [Ⅱ] Epoll 反应堆模型核心原理及代码讲解
    • 一、反应堆核心原理
    • 二、反应堆模型示例
      • 2.1 整体逻辑
      • 2.2 重要函数讲解
        • ① eventset()函数
        • ② eventadd()函数
        • ③ eventdel()函数
        • ④ acceptconn()函数
        • ⑤ recvdata()函数
        • ⑥ senddata()函数
      • 2.3 示例源码(Server端)

[Ⅰ] Epoll 原理及应用 && ET模式与LT模式

第一部分文章链接: Epoll 原理及应用 && ET模式与LT模式

[Ⅱ] Epoll 反应堆模型核心原理及代码讲解

一、反应堆核心原理

epoll反应堆模型的三个要素:

  • epoll ET模式

  • 非阻塞轮询处理

  • struct epoll_event结构体中epoll_data_t联合体中的void *ptr指针 – 实现回调机制

    结构体回顾:

    【重要理解】该struct epoll_event结构体是可以理解为可(通过epoll——ctl())挂载到内核的epoll监听红黑树上的结构体(类似深拷贝的机制):

    struct epoll_event {__uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
    };typedef union epoll_data {void *ptr;int fd;               // 该fd就是传入epoll_ctl()的对应监听事件的fduint32_t u32;uint64_t u64;
    } epoll_data_t;
    

    联合体又叫共用体,联合体内的变量共同使用一片地址空间。

    最基本的使用中,放入联合体中的值是fd,如下例伪代码:

    /* int connfd 是accpt()返回的socket连接句柄 */
    struct epoll_event event = {0, {0}};
    event.events = EPOLLIN;
    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);......  // 业务逻辑while (1) {/*监听红黑树efd, 将满足的事件的文件描述符加至events数组中, 阻塞wait*/int nfd = epoll_wait(efd, events, MAX_EVENTS+1, -1);for (i = 0; i < nfd; i++) {/* 使用int类型, 接收联合体data的fd成员 */int readyfd = events[i].data.fd;  ...... // 业务逻辑}
    }
    

    反应堆模型不直接放入fd,而是放入一个自定义的结构体指针(强制转换成了void *类型),这样epoll_wait()返回的时候就可以取出之前存入的自定义结构体。

    /* 用户自定义结构体 */
    /* 描述就绪文件描述符相关信息 */
    struct myevent_s {int fd;                                                 //要监听的文件描述符int events;                                             //对应的监听事件void *arg;                                              //泛型参数void (*call_back)(int fd, int events, void *arg);       //回调函数int status;                                             //是否在监听:1->在红黑树上(监听), 0->不在(不监听)char buf[BUFLEN];int len;long last_active;                                       //记录每次加入红黑树 g_efd 的时间值
    };...... // 业务逻辑/* struct myevent_s *ev 是用户自定义结构体 */
    struct epoll_event epv = {0, {0}};
    epv.events = ev->events = EPOLLIN;         //EPOLLIN 或 EPOLLOUT
    epv.data.ptr = ev;                         // 注意这里不是epv.data.fd = connfd
    epoll_ctl(efd, EPOLL_CTL_ADD, ev->fd, &epv)...... // 业务逻辑while(1) {/*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);...... //出错处理for (i = 0; i < nfd; i++) {/*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;  ...... // 业务逻辑}
    }
    

    回调机制的实现:

    在自定义结构体中存储指针函数,epoll_wait返回后取出events[i].data.ptr指向的自定义结构体,然后调用结构体中存储的回调函数

    struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
    ...... //判断
    ev->call_back(ev->fd, events[i].events, ev->arg);
    

二、反应堆模型示例

2.1 整体逻辑

  • socket、bind、listen – epoll_create 创建监听 红黑树 – 返回 epfd

  • epoll_ctl() 向红黑树上添加一个listenfd(监听socket)

  • while(1) {

    • 【可选】每轮迭代监测100个连接,若存在超时连接(沉积用户)则主动关闭;

    • epoll_wait() 监听 --> 对应监听fd有事件产生 --> 返回监听满足结构集 (即struct epoll_event结构体数组);

    • 判断返回数组元素 :

      • lfd满足EPOLLIN事件(读事件) --> 回调acceptconn()函数(主要完成accept()的任务)

      acceptconn()函数:

      • 调用accept()接受新的连接,开启 cfd socket,置为非阻塞;
      • 从全局的自定义结构体数组struct myevent_s g_events[MAX_EVENTS+1]中找一个空闲元素ev ;
      • 调用eventset()函数将 cfd 和 回调函数 senddata 写入 ev 中;
      • 调用eventadd()将 全局的&ev作为struct epoll_event结构体的data联合体的void *ptr指针,设置监听EPOLLIN(读事件),挂载到epoll的监听红黑树上。
      • cfd满足EPOLLIN事件(读事件) --> 回调recvdata()函数(主要完成读操作)

      recvdata()函数:

      1. 调用epoll_ctl()和宏EPOLL_CTL_DEL 将cfd从红黑树上摘下;
      2. 处理输入,并将处理结构保存到用户自定义的结构体的char buf[BUFLEN]成员变量中;
      3. 更改监听事件为EPOLLOUT,更改cfd对应的回调函数为senddata()
      4. 调用epoll_ctl()和宏EPOLL_CTL_ADD 将cfd重新挂载到红黑树上;

      注意

      严谨的来说,write也要通过监听机制确认是否可写,因为在实际网络环境中,假如通信对端存在半关闭的情况,或者滑动窗口满的情况,就无法成功wtrite数据。


      • cfd满足EPOLLOUT事件(写事件)–> 回调senddata()函数(主要完成写操作)

      senddata()函数:

      1. 调用epoll_ctl()和宏EPOLL_CTL_DEL 将cfd从红黑树上摘下;
      2. 将保存到在用户自定义的结构体的char buf[BUFLEN]中数据拷贝到cfd的发送缓冲区(即write()/send()函数);
      3. 更改监听事件为EPOLLIN,更改cfd对应的回调函数为recvdata()
      4. 调用epoll_ctl()和宏EPOLL_CTL_ADD 将cfd重新挂载到红黑树上;

    } // while(1) end

2.2 重要函数讲解

① eventset()函数

声明void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)

功能:将自定义结构体 myevent_s 成员变量 初始化

调用示例

  1. eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);
  2. eventset(ev, fd, recvdata, ev);
  3. eventset(ev, fd, senddata, ev);
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{ev->fd = fd;               // 待监听fdev->call_back = call_back;  // 设置回调函数ev->events = 0;                // 监听事件由函数 eventadd()函数指定ev->arg = arg;             // ev的arg即ev中回调函数的参数,是ev本身(比较难理解,但这是关键)ev->status = 0;              // 结构体状态标记为"已占用"memset(ev->buf, 0, sizeof(ev->buf));    //清空结构体的char *缓冲区ev->len = 0;               // 将缓冲区数据长度置0ev->last_active = time(NULL); //调用eventset函数的时间(可选,用于断开沉积连接)return;
}
② eventadd()函数

声明void eventadd(int efd, int events, struct myevent_s *ev)

功能:向 epoll监听的红黑树添加一个监听节点

调用示例

  1. eventadd(g_efd, EPOLLIN, &g_events[i]);
  2. eventadd(g_efd, EPOLLIN, ev);
  3. eventadd(g_efd, EPOLLOUT, ev);
/* eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); */
void eventadd(int efd, int events, struct myevent_s *ev)
{/* 从自定义的结构体指针struct myevent_s *的变量ev中 提取数据到一个可以挂在到epoll监听红黑树上的struct epoll_event变量 epv上 */ struct epoll_event epv = {0, {0}};int op;epv.data.ptr = ev;epv.events = ev->events = events;  //EPOLLIN 或 EPOLLOUTif (ev->status == 0) {     //已经在红黑树 g_efd 里op = EPOLL_CTL_ADD;    //将其加入红黑树 g_efd, 并将status置1ev->status = 1;}if (epoll_ctl(efd, op, ev->fd, &epv) < 0)     //实际添加printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);elseprintf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);return ;
}
③ eventdel()函数

声明void eventadd(int efd, int events, struct myevent_s *ev)

功能:从epoll监听的红黑树上摘除一个监听节点

调用示例eventdel(g_efd, ev);

void eventdel(int efd, struct myevent_s *ev){//不在红黑树上if (ev->status != 1)    return;//修改状态ev->status = 0;       //从红黑树 efd 上将 ev->fd 摘除epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, NULL);                return ;
}
④ acceptconn()函数

声明void acceptconn(int lfd, int events, void *arg)

架构:

  • 调用accept()接受新的连接,开启 cfd socket,置为非阻塞;
  • 从全局的自定义结构体数组struct myevent_s g_events[MAX_EVENTS+1]中找一个空闲元素ev ;
  • 调用eventset()函数将 cfd 和 回调函数 senddata 写入 ev 中;
  • 调用eventadd()将 全局的&ev作为struct epoll_event结构体的data联合体的void *ptr指针,设置监听EPOLLIN(读事件),挂载到epoll的监听红黑树上。
/*  当lfd的读事件就绪, epoll返回, 调用该函数 与客户端建立链接 */
/* 在acceptconn内部去做accept */
void acceptconn(int lfd, int events, void *arg)
{struct sockaddr_in cin;socklen_t len = sizeof(cin);int cfd, i;if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {if (errno != EAGAIN && errno != EINTR) {/* 暂时不做出错处理 */}printf("%s: accept, %s\n", __func__, strerror(errno));return ;}do {for (i = 0; i < MAX_EVENTS; i++)                                //从全局数组g_events中找一个空闲元素if (g_events[i].status == 0)                                //类似于select中找值为-1的元素break;                                                  //跳出 forif (i == MAX_EVENTS) {printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);break;                                                      //跳出do while(0) 不执行后续代码}int flag = 0;if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) {             //将cfd也设置为非阻塞printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));break;}/* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */eventset(&g_events[i], cfd, recvdata, &g_events[i]);   //将cfd添加到红黑树g_efd中,监听读事件eventadd(g_efd, EPOLLIN, &g_events[i]);                      } while(0);printf("new connect [%s:%d][time:%ld], pos[%d]\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);return ;
}
⑤ recvdata()函数

声明:void recvdata(int fd, int events, void *arg)

架构:

  1. 调用epoll_ctl()和宏EPOLL_CTL_DEL 将cfd从红黑树上摘下;
  2. 处理输入,并将处理结构保存到用户自定义的结构体的char buf[BUFLEN]成员变量中;
  3. 更改监听事件为EPOLLOUT,更改cfd对应的回调函数为senddata()
  4. 调用epoll_ctl()和宏EPOLL_CTL_ADD 将cfd重新挂载到红黑树上;
void recvdata(int fd, int events, void *arg)
{struct myevent_s *ev = (struct myevent_s *)arg;int len;//将该节点从红黑树上摘除eventdel(g_efd, ev);        //读文件描述符, 数据存入myevent_s成员buf中len = recv(fd, ev->buf, sizeof(ev->buf), 0);           if (len > 0) {ev->len = len;//手动添加字符串结束标记避免缓冲区溢出ev->buf[len] = '\0';                               printf("C[%d]:%s\n", fd, ev->buf);//设置该 fd 对应的回调函数为 senddataeventset(ev, fd, senddata, ev); //将fd加入红黑树g_efd中,监听其写事件eventadd(g_efd, EPOLLOUT, ev);                     } else if (len == 0) {close(ev->fd);/* ev-g_events 地址相减得到偏移元素位置 */printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);} else {close(ev->fd);printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));}return; /* 如果配合线程池使用,期望的是线程结束任务之后返回线程池,而不是被系统回收资源,所以这部分的线程不能够设置分离属性 */
}
⑥ senddata()函数

声明:void senddata(int fd, int events, void *arg)

架构:

  1. 调用epoll_ctl()和宏EPOLL_CTL_DEL 将cfd从红黑树上摘下;
  2. 将保存到在用户自定义的结构体的char buf[BUFLEN]中数据拷贝到cfd的发送缓冲区(即write()/send()函数);
  3. 更改监听事件为EPOLLIN,更改cfd对应的回调函数为recvdata()
  4. 调用epoll_ctl()和宏EPOLL_CTL_ADD 将cfd重新挂载到红黑树上;
void senddata(int fd, int events, void *arg)
{struct myevent_s *ev = (struct myevent_s *)arg;int len;//从红黑树g_efd中移除eventdel(g_efd, ev); //直接将数据 回写给客户端。未作处理len = send(fd, ev->buf, ev->len, 0);    if (len > 0) {printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);//将该fd的 回调函数改为 recvdataeventset(ev, fd, recvdata, ev);   //从新添加到红黑树上, 设为监听读事件eventadd(g_efd, EPOLLIN, ev);                       } else {close(ev->fd);                                      //关闭链接printf("send[fd=%d] error %s\n", fd, strerror(errno));}return ;
}

2.3 示例源码(Server端)

/**epoll基于非阻塞I/O事件驱动*/
#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 BUFLEN 4096
#define SERV_PORT   8080void recvdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);/* 描述就绪文件描述符相关信息 */struct myevent_s {int fd;                                                 //要监听的文件描述符int events;                                             //对应的监听事件void *arg;                                              //泛型参数void (*call_back)(int fd, int events, void *arg);       //回调函数int status;                                             //是否在监听:1->在红黑树上(监听), 0->不在(不监听)char buf[BUFLEN];int len;long last_active;                                       //记录每次加入红黑树 g_efd 的时间值
};int g_efd;                                                  //全局变量, 保存epoll_create返回的文件描述符
struct myevent_s g_events[MAX_EVENTS+1];                    //自定义结构体类型数组. +1-->listen fd/*将结构体 myevent_s 成员变量 初始化*/
/* eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]); */
void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{ev->fd = fd;ev->call_back = call_back;ev->events = 0;ev->arg = arg;ev->status = 0;memset(ev->buf, 0, sizeof(ev->buf));ev->len = 0;ev->last_active = time(NULL);                       //调用eventset函数的时间return;
}/* 向 epoll监听的红黑树 添加一个 文件描述符 */
/* eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); */
void eventadd(int efd, int events, struct myevent_s *ev)
{/* 从自定义的结构体指针struct myevent_s *的变量ev中 提取数据到一个可以挂在到epoll监听红黑树上的struct epoll_event变量 epv上 */ struct epoll_event epv = {0, {0}};int op;epv.data.ptr = ev;epv.events = ev->events = events;       //EPOLLIN 或 EPOLLOUTif (ev->status == 0) {                                          //已经在红黑树 g_efd 里op = EPOLL_CTL_ADD;                 //将其加入红黑树 g_efd, 并将status置1ev->status = 1;}if (epoll_ctl(efd, op, ev->fd, &epv) < 0)                       //实际添加/修改printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);elseprintf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);return ;
}/* 从epoll 监听的 红黑树中删除一个 文件描述符*/void eventdel(int efd, struct myevent_s *ev)
{struct epoll_event epv = {0, {0}};if (ev->status != 1)                                        //不在红黑树上return ;//epv.data.ptr = ev;epv.data.ptr = NULL;                                      //抹去指针 ev->status = 0;                                             //修改状态epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);                //从红黑树 efd 上将 ev->fd 摘除return ;
}/*  当有文件描述符就绪, epoll返回, 调用该函数 与客户端建立链接 */
/* 在acceptconn内部去做accept */
void acceptconn(int lfd, int events, void *arg)
{struct sockaddr_in cin;socklen_t len = sizeof(cin);int cfd, i;if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {if (errno != EAGAIN && errno != EINTR) {/* 暂时不做出错处理 */}printf("%s: accept, %s\n", __func__, strerror(errno));return ;}do {for (i = 0; i < MAX_EVENTS; i++)                                //从全局数组g_events中找一个空闲元素if (g_events[i].status == 0)                                //类似于select中找值为-1的元素break;                                                  //跳出 forif (i == MAX_EVENTS) {printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);break;                                                      //跳出do while(0) 不执行后续代码}int flag = 0;if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) {             //将cfd也设置为非阻塞printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));break;}/* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */eventset(&g_events[i], cfd, recvdata, &g_events[i]);   eventadd(g_efd, EPOLLIN, &g_events[i]);                         //将cfd添加到红黑树g_efd中,监听读事件} while(0);printf("new connect [%s:%d][time:%ld], pos[%d]\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);return ;
}void recvdata(int fd, int events, void *arg)
{struct myevent_s *ev = (struct myevent_s *)arg;int len;len = recv(fd, ev->buf, sizeof(ev->buf), 0);            //读文件描述符, 数据存入myevent_s成员buf中eventdel(g_efd, ev);        //将该节点从红黑树上摘除if (len > 0) {ev->len = len;ev->buf[len] = '\0';                                //手动添加字符串结束标记printf("C[%d]:%s\n", fd, ev->buf);eventset(ev, fd, senddata, ev);                     //设置该 fd 对应的回调函数为 senddataeventadd(g_efd, EPOLLOUT, ev);                      //将fd加入红黑树g_efd中,监听其写事件} else if (len == 0) {close(ev->fd);/* ev-g_events 地址相减得到偏移元素位置 */printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);} else {close(ev->fd);printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));}return; /* 期望的是线程结束任务之后返回线程池,而不是被系统回收资源,所以这部分的线程不能够设置分离属性 */
}void senddata(int fd, int events, void *arg)
{struct myevent_s *ev = (struct myevent_s *)arg;int len;len = send(fd, ev->buf, ev->len, 0);                    //直接将数据 回写给客户端。未作处理eventdel(g_efd, ev);                                //从红黑树g_efd中移除if (len > 0) {printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);eventset(ev, fd, recvdata, ev);                     //将该fd的 回调函数改为 recvdataeventadd(g_efd, EPOLLIN, ev);                       //从新添加到红黑树上, 设为监听读事件} else {close(ev->fd);                                      //关闭链接printf("send[fd=%d] error %s\n", fd, strerror(errno));}return ;
}/*创建 socket, 初始化lfd */void initlistensocket(int efd, short port)
{struct sockaddr_in sin;int lfd = socket(AF_INET, SOCK_STREAM, 0);fcntl(lfd, F_SETFL, O_NONBLOCK);                                            //将socket设为非阻塞memset(&sin, 0, sizeof(sin));                                               //bzero(&sin, sizeof(sin))sin.sin_family = AF_INET;sin.sin_addr.s_addr = INADDR_ANY;sin.sin_port = htons(port);bind(lfd, (struct sockaddr *)&sin, sizeof(sin));listen(lfd, 20);/* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg);  */eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);/* void eventadd(int efd, int events, struct myevent_s *ev) */eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);return ;
}int main(int argc, char *argv[])
{unsigned short port = SERV_PORT;if (argc == 2)port = atoi(argv[1]);                           //使用用户指定端口.如未指定,用默认端口g_efd = epoll_create(MAX_EVENTS+1);                 //创建红黑树,返回给全局 g_efd if (g_efd <= 0)printf("create efd in %s err %s\n", __func__, strerror(errno));initlistensocket(g_efd, port);                      //初始化监听socketstruct epoll_event events[MAX_EVENTS+1];            //保存已经满足就绪事件的文件描述符数组 以供epoll_wait使用 printf("server running:port[%d]\n", port);int checkpos = 0, i;while (1) {/* 超时验证,每次测试100个链接,不测试listenfd 当客户端60秒内没有和服务器通信,则关闭此客户端链接 */long now = time(NULL);                          //当前时间for (i = 0; i < 100; i++, checkpos++) {         //一次循环检测100个。 使用checkpos控制检测对象if (checkpos == MAX_EVENTS)                 //根节点不参与检测 checkpos = 0;if (g_events[checkpos].status != 1)         //不在红黑树 g_efd 上continue;long duration = now - g_events[checkpos].last_active;       //客户端不活跃的世间if (duration >= 60) {close(g_events[checkpos].fd);                           //关闭与该客户端链接printf("[fd=%d] timeout\n", g_events[checkpos].fd);eventdel(g_efd, &g_events[checkpos]);                   //将该客户端 从红黑树 g_efd移除}}/*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);if (nfd < 0) {printf("epoll_wait error, exit\n");break;}for (i = 0; i < nfd; i++) {/*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;  if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {           //读就绪事件ev->call_back(ev->fd, events[i].events, ev->arg);//lfd  EPOLLIN  }if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {         //写就绪事件ev->call_back(ev->fd, events[i].events, ev->arg);}}}/* 退出前释放所有资源 */return 0;
}

Epoll 反应堆模型核心原理及代码讲解相关推荐

  1. mmpose关键点(四):优化关键点模型(原理与代码讲解,持续更新)

    在工程中,模型的运行速度与精度是同样重要的,本文中,我会运用不同的方法去优化比较模型的性能,希望能给大家带来一些实用的trick与经验. 有关键点检测相关经验的同学应该知道,关键点主流方法分为Heat ...

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

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

  3. epoll反应堆模型

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

  4. 【资源】Faster R-CNN原理及代码讲解电子书

    <Faster R-CNN原理及代码讲解>是首发于GiantPandaCV公众号的教程,针对陈云大佬实现的Faster R-CNN代码讲解,Github链接如下: https://gith ...

  5. 独立级联(Independent Cascade)模型的原理及代码实现

    目录 1. 原理 2. 代码实现 2.1 数据集 2.2 独立级联 1. 原理 独立级联模型在影响力最大化任务中属于比较经典的影响力传播模型. 具体来讲,针对某一具体传播的实体(谣言.绯闻.产品等), ...

  6. epoll反应堆模型代码

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

  7. 深入浅出吃透多线程、线程池核心原理及代码详解

    一.多线程详解 1.什么是线程 线程是一个操作系统概念.操作系统负责这个线程的创建.挂起.运行.阻塞和终结操作.而操作系统创建线程.切换线程状态.终结线程都要进行CPU调度--这是一个耗费时间和系统资 ...

  8. 手把手写深度学习(18):finetune微调CLIP模型的原理、代码、调参技巧

    前言:在前面的博客<手把手写深度学习(16):用CILP预训练模型搭建图文检索系统/以图搜图/关键词检索系统>中介绍了如何在图文检索.以图搜图.关键词检索等任务中使用CLIP.这篇博客重点 ...

  9. Calcite原理和代码讲解(一)

    1.Calcite介绍 (1)简介 Apache Calcite 是面向 Hadoop 新的查询引擎,它提供了标准的 SQL 语言.多种查询优化和连接各种数据源的能力. Calcite 的目标是&qu ...

最新文章

  1. 编写安全的ASP代码
  2. 祝博客园里的所有朋友 新年快乐!
  3. c语言后缀表达式构造二叉树,C ++程序为后缀表达式构造表达式树
  4. 查看WEB服务器的连接数
  5. mybatis进行CRUD操作时返回值不为影响的条数,为null
  6. Coding Interview Guide -- 翻转字符串
  7. mybatis 的 dao 接口跟 xml 文件里面的 sql 是如何建立关系的?
  8. 数据结构(C语言)-串
  9. Tool-杂项-建模:犀牛(3D造型软件)
  10. pdfobject.js和pdf.js的详解
  11. Winlogon、LSASS、Userinit
  12. 教育技术学就业方向_教育技术学专业就业方向与就业前景
  13. oracle导出dmp文件合集
  14. 一米霜降肥牛,煎饼果子,all you can eat 牛油串串
  15. 【转贴】你必须知道的20个故事
  16. mysql varchar 单引号_char、varchar数据类型值在使用时可以要用单引号或双引号括起来。...
  17. 磁共振线圈分类_核磁线圈的基本架构及各部分主要功能
  18. Odoo | Config | Odoo版本基础需求
  19. 商品详情页上拉查看详情开源库
  20. uCOSii中的互斥信号量

热门文章

  1. Flutter之声网Agora实现音频体验记录
  2. arm汇编标号globl和word解释
  3. 刘强东的代码水平到底有多牛?网友:95年一个晚上赚5万
  4. ubuntu14.04 刚安装完成后汉语拼音输入法出错问题的解决办法
  5. 在知乎逮到一个腾讯10年老测试开发,聊过之后收益良多...
  6. 迈阿密色主题学科导航 HTML5静态开源
  7. 安卓端哔哩哔哩下载文件存储处
  8. acr122_ACR的完整形式是什么?
  9. Windows10系统保留正版系统重装 与 热迁移系统
  10. 网件r6300安装mysql数据库_网件(NETGEAR)R6300 V1/V2路由器设置教程【图文】