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

首先就是介绍一下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. Webpack基础学习(一) (未完结)

    一.Webpack介绍与基本使用 1.1.Webpack是什么? Webpack 是一个静态资源打包工具. 它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出出去. ...

  2. 突破SESSION 0隔离的远程线程注入

    与传统的 CreateRemoteThread 函数实现的远线程注入 DLL 的唯一区别在于,突破 SESSION 0 远线程注 入技术是使用比 CreateRemoteThread 函数更为底层的 ...

  3. PostgreSQL-可以通过localhost连接,无法通过IP地址连接。

    (1)如果PostgreSQL配置文件中没有允许访问该服务器的IP地址,则需要先添加允许访问的IP地址,并在防火墙中开放相应的端口.(2)在PostgreSQL配置文件postgresql.conf中 ...

  4. Hive中Lateral view用法

    1. lateral view 简介   hive函数 lateral view 主要功能是将原本汇总在一条(行)的数据拆分成多条(行)成虚拟表,再与原表进行笛卡尔积,从而得到明细表.配合UDTF函数 ...

  5. CF1348

    传送门 A: 一个组 \(2^n+2^1+\dots+2^{\frac{n}{2}-1}\),另一个组剩下的. B: 考虑不停循环. 如果不同的数字超过 \(k\),无解. 否则先把原序列去重,然后把 ...

  6. P3078题解

    P3078题解 看到题解区,我有点震惊,什么贪心.线段树.各种优化都有,在此%%%.但其实这道题一个小小的差分就可解决. 前置芝士:前缀和/差分 by OI Wiki 题意简述 在一个 $card$ ...

  7. 【Android】使用MediaExtractor获取关键帧的时间戳

    1 前言 使用MediaExtractor.MediaMuxer去掉视频文件中的音频数据 中介绍了 MediaExtractor 类的主要方法,本文主要将使用其 advance() 和 seekTo( ...

  8. ffmpeg之avformat_alloc_output_context2

    函数原型: int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat, const ...

  9. Spring Boot学生信息管理系统项目实战-2.字典管理和模板管理

    1.获取源码 源码是捐赠方式获取,详细请QQ联系我 :) 2.实现效果 3.项目源码 只挑重点讲,详细请看源码. 3.1 字典管理 字典管理这里分为字典的编码和名称和字典数据的增删改查. 前端页面: ...

  10. U盘安装win7提示缺少所需的CD/DVD驱动器设备驱动程序

    问题: 最近使用U盘启动盘安装win7,系统弹出提示框: 解决方法: U盘别插在usb3.0的口(蓝色),换成一个usb2.0的口就可以了