我们继续上篇的文章继续更新我们的代码。

首先就是介绍一下epoll的三个函数。

  • epoll_create
  • epoll_ctl
  • epoll_wait

    如何去理解这3个函数,我是这样去理解这个函数,

    就像我们去取快递一样,之前的Select模型,是通过轮询的方式一直去循环遍历客户端FD的列表,而EPOLL就相当于专门了一个快递柜,会将有读写事件的FD放到快递柜里面,而快递员只需要去快递柜进行取件和放件就可以了。

    epoll_create函数就相当于我们添加了一个快递柜在楼下,

    epoll_ctl就相当于我们添加快递或者取快递在快递柜中,

    epoll_wait就相当于快递员什么时候进行取件,什么时候取送件。

这就会有一些优点什么优点那?

  1. 不需要循环遍历所有fd
  2. 每一次取就绪集合,在固定位置;
  3. 异步解耦

那么我们在下面看一下EPOLL实现的服务器的代码,我会把对应的注释标记到代码上

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include <unistd.h>
#include<sys/epoll.h>
#include <string.h> #define BUFFER_LENGTH 128
#define EVENTS_LENGTH 128 char rbuffer[BUFFER_LENGTH] = {0};
char wbuffer[BUFFER_LENGTH] = {0}; int main()
{ unsigned char buffer[BUFFER_LENGTH]={0};
int ret=0;
//socket有两个参数,第一个参数指定我们要使用IPV4,还是IPV6,第二个参数表明我们要使用套接字类型,这里我们使用的是流格式的套接字,第三个参数就是我们需要使用传输协议
//这里使用0,表示让系统自动推导我们需要使用的传输协议。
int listenfd= socket(AF_INET,SOCK_STREAM,0);
//如果返回值为-1,说明我们创建SOCKET失败,直接返回。
if (listenfd==-1)
{
return -1;
}
//我们需要绑定的信息
struct sockaddr_in serveraddr;
//使用IPV4
serveraddr.sin_family=AF_INET;
//我们需要绑定的IP地址,INADDR_ANY 就是0.0.0.0 ,就是所有网卡的所有IP段都可以连接到我们的创建的TCP服务器上。
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
//我们需要绑定的端口,这里我们绑定的端口为9999
serveraddr.sin_port=htons(9999);
//第一个参数我们创建的套接字,第二个是我们填写的绑定信息,最后是我们的绑定信息结构体的大小。
if (-1==bind(listenfd,(const sockaddr*)&serveraddr,sizeof(serveraddr)))
{
return -2;
}
//监听我们创建的套接字,请求的队列数量,这里我们填写为10个
listen(listenfd,10);
//定义客户端的socket //定义可读序列和可写序列
fd_set rfds,wfds,rset,wset;
//清空序列
FD_ZERO(&rfds);
//设置读的序列
FD_SET(listenfd,&rfds);
//清空可写的序列
FD_ZERO(&wfds); int maxfd=listenfd; //开始进行EPOLL的创建
int epfd = epoll_create(1);
struct epoll_event ev,events[EVENTS_LENGTH];
ev.events=EPOLLIN;
ev.data.fd=listenfd;
//添加我们的服务器通信的listenfd到EPFD中,
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
//接下来开始接受 我们的客户端的连接请求
while (1)
{
//我们需要详细讲解一下这个函数的里面的各个参数的意义 ,以及它什么时候是阻塞的,什么时候是非阻塞的,
//第一个参数我们的EPFD的文件描述符,第二个我们的接收事件的缓冲器,第三个是我们事件数量的多少,最后一个参数就是我们等待的时长了。
//当是-1的时候就是一直等待连接的意思,没有连接就会 一直被阻塞住,
//当是0的时候就是一直有连接直接返回的意思,
//当是大于0的数的时候,就是在轮询查看是否有事件的时长,单位是MS。
int nready = epoll_wait(epfd,events,EVENTS_LENGTH,-1);
printf("----------%d\n",nready); //开始遍历我们的事件
int i =0;
for (int i = 0; i < nready; i++)
{
int clientfd=events[i].data.fd;
if (listenfd==clientfd)
{
//如果是我们的监听的FD,说明是有客户端连入的事件
struct sockaddr_in client;
socklen_t len=sizeof(client);
//接受客户端的请求,
int connfd=accept(listenfd,(struct sockaddr*)&client,&len);
if (connfd==-1)
{
break;
}
printf("accept:%d\n",connfd);
//增加到我们的快递柜中
ev.events=EPOLLIN;
ev.data.fd=connfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
//如果是读的请求
}
else if (events[i].events & EPOLLIN)
{
//如果客户端在线可以接受到消息
int n=recv(clientfd,rbuffer,BUFFER_LENGTH,0);
if (n>0)
{
rbuffer[n]='\0';
printf("clientfd :%d recv: %s ,n:%d\n",clientfd,rbuffer,n);
memcpy(wbuffer,rbuffer,BUFFER_LENGTH); ev.events=EPOLLOUT;
ev.data.fd=clientfd; epoll_ctl(epfd,EPOLL_CTL_MOD,clientfd,&ev);
//客户端退出的时候会触发
}
else
{ ev.data.fd=clientfd;
epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd,&ev);
}
}
else if(events[i].events & EPOLLOUT)
{
int sent = send(clientfd, wbuffer, BUFFER_LENGTH, 0); //
printf("sent: %d\n", sent); ev.events = EPOLLIN;
ev.data.fd = clientfd; epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev); } } } return 0; }

上面我已经把对应的注释以及注意的点已经写在了代码的上面,

这里我们还要说一个问题,就是EPOLLLT和EPOLLET的问题

LT模式

对于读事件 EPOLLIN,只要socket上有未读完的数据,EPOLLIN 就会一直触发,直到我们的数据接收完毕后才会停止;对于写事件 EPOLLOUT,只要socket可写,EPOLLOUT 就会一直触发。

在这种模式下,大家会认为读数据会简单一些,因为即使数据没有读完,那么下次调用epoll_wait()时,它还会通知你在上没读完的文件描述符上继续读,也就是人们常说的这种模式不用担心会丢失数据。

而写数据时,因为使用 LT 模式会一直触发 EPOLLOUT 事件,那么如果代码实现依赖于可写事件触发去发送数据,一定要在数据发送完之后移除检测可写事件,避免没有数据发送时无意义的触发。

ET模式

对于读事件 EPOLLIN,只有socket上的数据从无到有,EPOLLIN 才会触发;对于写事件 EPOLLOUT,只有在socket写缓冲区从不可写变为可写,EPOLLOUT 才会触发(刚刚添加事件完成调用epoll_wait时或者缓冲区从满到不满)

这种模式听起来清爽了很多,只有状态变化时才会通知,通知的次数少了自然也会引发一些问题,比如触发读事件后必须把数据收取干净,因为你不一定有下一次机会再收取数据了,即使不采用一次读取干净的方式,也要把这个激活状态记下来,后续接着处理,否则如果数据残留到下一次消息来到时就会造成延迟现象。

这种模式下写事件触发后,后续就不会再触发了,如果还需要下一次的写事件触发来驱动发送数据,就需要再次注册一次检测可写事件。

其次我们代码还有一个问题,就是公用同一个缓冲区的问题,这个问题,我们后面的文章再去解决。

推荐一个零声学院免费教程,个人觉得老师讲得不错,

分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,

fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,

TCP/IP,协程,DPDK等技术内容,点击立即学习:

服务器

音视频

dpdk

Linux内核

epoll和ractor的粗浅理解的更多相关文章

  1. paxos算法之粗浅理解

    paxos出身 paxos出身名门,它爹是没多久前获得图灵奖的在分布式领域大名鼎鼎的LeslieLamport. paxos为何而生 那么Lamport他老人家为什么要搞这个东东呢,不是吃饱了撑的,而 ...

  2. 对js闭包的粗浅理解

    只能是粗浅的,毕竟js用法太灵活. 首先抛概念:闭包(closure)是函数对象与变量作用域链在某种形式上的关联,是一种对变量的获取机制.这样写鬼能看懂. 所以要大致搞清三个东西:函数对象(funct ...

  3. C#高级编程笔记 Delegate 的粗浅理解 2016年9月 13日

    Delegate [重中之重] 委托 定义一:(参考)http://www.cnblogs.com/zhangchenliang/archive/2012/09/19/2694430.html 完全可 ...

  4. 对Java框架spring、hibernate、Struts的粗浅理解

    对 Struts 的理解:1. struts 是一个按 MVC 模式设计的 Web 层框架,其实它就是一个大大的 servlet,这个Servlet 名为 ActionServlet,或是 Actio ...

  5. UNITY 画布的粗浅理解

    画布:当画布是screen-space overlay时,这个好理解,画布可以控制如分辨率,层次等.但当画布是 world-space时,这个严格来说就不算是一个画布了,屏幕空间或相机空间的画布是先绘 ...

  6. 关于</div>的粗浅理解

    </div>作为c#中常用的一个标签,在写多个区域的内容时有着十分重要的作用.如果写简单的网页时不用div可能感受不到太大的影响,但是在写较为复杂的程序时div的分隔作用就很明显了,改动大 ...

  7. function的粗浅理解

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. 关于JavaScript闭包的粗浅理解

    在JavaScript中,使用var创建变量,会创建全局变量或局部变量. 只有在非函数内创建的变量,才是全局变量,该变量可以在任何地方被读取. 而在函数内创建变量时,只有在函数内部才可读取.在函数外部 ...

  9. CQRS粗浅理解

    CQRS(命令查询责任分离)是一种奇特的模式,表示解耦系统的输入和输出. 通常情况下,输入端将数据写到数据库,输出端从数据库查询.与读写锁的场景类似,写的过程中不能读.正常情况下没有问题,但是在大规模 ...

  10. Fragment的粗浅理解

    Fragment: 1.它是对于Activity界面实现的一种途径,相对于已经绑定的Layout,他更轻便,更灵活,更具有自由度和可设计性. 2.Fragment的功能正如他的正文意思所言,他是一个片 ...

随机推荐

  1. 699元 光威推出神武RGB系列DDR5 6400内存:海力士精选颗粒

    光威推出了神武RGB系列DDR5 6400台式机内存条,售价为699元. 据了解,新款内存条采用了海力士M-die特挑颗粒,拥有CL-32-39-39-102低时序. 散热方面,这款内存条采用显卡级散 ...

  2. 【栈和队列】栈和队列的相互实现OJ练习【力扣-232、力扣-225】超详细的保姆级别解释

    [栈和队列]栈和队列的相互实现OJ练习[力扣-232.力扣-225]超详细的保姆级别解释 先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要的动力 ...

  3. Sliver C2 实战 vulntarget-f

    ‍ 网络拓扑 host ip1 ip2 ubuntu(自用) 192.168.130.14 / centos 192.168.130.3 10.0.10.2 ubuntu1 10.0.10.3 10. ...

  4. 使用python 打包成exe文件

    python 打包exe ​ 起因:闲的蛋疼 ​ 过程:扯的蛋疼 ​ 结果:不疼了 ​ 1.起因(闲的蛋疼) ​ 突然的emo,不想干活,于是乎找遍微信好友,群发了十年八辈子不联系的一群人(此办法学习 ...

  5. JS Leetcode 33. 搜索旋转排序数组题解,图解旋转数组中的二分法

    壹 ❀ 引 本来今天(2021.4.7)的每日一题是81. 搜索旋转排序数组 II,但今天工作很忙,下班人基本累个半死,题目别说按照二分法的思路做不出来,连题解看了会都没法沉下心去看,不过得到的信息是 ...

  6. NC24858 [USACO 2009 Nov S]Job Hunt

    题目链接 题目 题目描述 Bessie is running out of money and is searching for jobs. Farmer John knows this and wa ...

  7. Java设计模式-装饰者模式Decorator

    介绍 装饰者模式的核心思想是通过创建一个装饰对象(即装饰者),动态扩展目标对象的功能,并且不会改变目标对象的结构,提供了一种比继承更灵活的替代方案.需要注意的是,装饰对象要与目标对象实现相同的接口,或 ...

  8. HTML+CSS设计一个朴实无华的登录页

    说明 之前一直偏重于后端技术研究,最近设计网站感觉前端太菜,遂集中看了下CSS的内容.后续我会发表一些前端实战的一些例子,给自己记录的同时希望也能分享给大家. 实现效果 主要知识点 DIV屏幕垂直居中 ...

  9. 自古以来,JSON序列化就是兵家必争之地

    上文讲到使用ioutil.ReadAll读取大的Response Body,出现读取Body超时的问题. 前人引路 Stackoverflow的morganbaz的看法是: 使用iotil.ReadA ...

  10. win32 - 关于GDI的RGB的数据分析

    此文章为小结,仅供参考. 第一种情况,从桌面DC获取RGBA的数据. 32位 HDC hdc, hdcTemp; RECT rect; BYTE* bitPointer; int x, y; int ...