epoll概念

epoll对文件描述符的操作方式有两种工作模式:LT模式(Level Trigger,水平触发) 和ET模式(Edge Trigger,边缘触发)。

  • LT模式:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件,这样,当应用程序下一次调用epoll_wait时,epoll_wait还会向应用程序通告此事件,直到该事件被处理。
  • ET模式:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不在向应用程序通告此事件。

1. 水平触发
 对于读操作

  • 只要缓冲内容不为空,LT模式返回读就绪。

对于写操作

  • 只要缓冲区还不满,LT模式会返回写就绪。

2. 边缘触发
 对于读操作

  • 当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。
  • 当有新数据到达时,即缓冲区中的待读数据变多的时候。
  • 当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。

对于写操作

  • 当缓冲区由不可写变为可写时。
  • 当有旧数据被发送走,即缓冲区中的内容变少的时候。
  • 当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。

实验一

1. 测试代码:

 #include <unistd.h>
#include <stdio.h>
#include <sys/epoll.h> int main()
{
int epfd, nfds;
struct epoll_event event, events[];
epfd = epoll_create();
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
while ()
{
nfds = epoll_wait(epfd, events, , -);
int i;
for (i = ; i < nfds; ++i)
{
if (events[i].data.fd == STDIN_FILENO)
printf("hello world\n");
}
}
}

输出结果:

分析:当用户输入一组字符,这组字符被送入缓冲区,因为缓冲区由空变成不空,所以ET返回读就绪,输出”hello world”。之后再次执行epoll_wait,但ET模式下只会通知应用进程一次,故导致epoll_wait阻塞。 如果用户再次输入一组字符,导致缓冲区内容增多,ET会再返回就绪,应用进程再次输出”hello world”。 如果将上面的代码中的event.events = EPOLLIN | EPOLLET;改成event.events = EPOLLIN;,即使用LT模式,则运行程序后,会一直输出hello world。

2. 测试代码:

 #include <unistd.h>
#include <stdio.h>
#include <sys/epoll.h> int main()
{
int epfd, nfds;
char buf[];
struct epoll_event event, events[];
epfd = epoll_create();
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN; // LT是默认模式
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
while () {
nfds = epoll_wait(epfd, events, , -);
int i;
for (i = ; i < nfds; ++i)
{
if (events[i].data.fd == STDIN_FILENO)
{
read(STDIN_FILENO, buf, sizeof(buf));
printf("hello world\n");
}
}
}
}

输出:

分析:

实验2中使用的是LT模式,则每次epoll_wait返回时我们都将缓冲区的数据读完,下次再调用epoll_wait时就会阻塞,直到下次再输入字符。 
如果将上面的程序改为每次只读一个字符,那么每次输入多少个字符,则会在屏幕中输出多少个“hello world”。

3. 测试代码:

 #include <unistd.h>
#include <stdio.h>
#include <sys/epoll.h> int main()
{
int epfd, nfds;
struct epoll_event event, events[];
epfd = epoll_create();
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
while ()
{
nfds = epoll_wait(epfd, events, , -);
int i;
for (i = ; i < nfds; ++i)
{
if (events[i].data.fd == STDIN_FILENO)
{
printf("hello world\n");
event.data.fd = STDIN_FILENO;
event.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &event);
}
}
}
}

输出结果:

对标准输入文件描述符使用ET模式进行监听。当我们输入任何输入并接下回车时,屏幕中会死循环输出”hello world”。

分析:

实验3使用ET模式,但是每次读就绪后都主动对描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件,由上面的描述我们可以知道,会再次触发读就绪,这样就导致程序出现死循环,不断地在屏幕中输出”hello world”。但是,如果我们将EPOLL_CTL_MOD 改为EPOLL_CTL_ADD,则程序的运行将不会出现死循环的情况。

实验二

问题:

1.阻塞读数据(不用epoll),你说读到一半有新消息又来了怎么办? 
2.非阻塞读数据(不用epoll),你说读到一半有新消息又来了怎么办? 
3.epoll的ET模式时,如果数据只读了一半,也就是缓冲区的数据只读了一点,然后又来新事件了怎么办?

答案:

1:来了就来了呗,读就是了啊。可能我们一次读到两次发过来的消息。 
2:来了就来了呗,读就是了啊。可能我们一次读到两次发过来的消息。 
3:单线程/进程不会有任何问题,多进程/多线程我们只需要设置EPOLLONESHOT这个参数就好了

客户端代码:(下面四个示例都是同一个客户端)

 #include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h> int main()
{
int sock, n;
sock= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sock < )
return ; struct sockaddr_in servaddr;
memset(&servaddr,,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < )
return ;
char *buf1 = "hello ";
n = write(sock, buf1, strlen(buf1) + );
printf("%d : buf = %s\n", n, buf1); sleep(); char *buf2 = "world ";
n = write(sock,buf2,strlen(buf2) + );
printf("%d : buf = %s\n", n, buf2); sleep(); char *buf3 = "陈明东";
n = write(sock,buf3,strlen(buf3) + );
printf("%d : buf = %s\n", n, buf3); sleep();
close(sock);
}

输出:

1. 服务端阻塞读:

 nclude <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h> #define SERV_PORT 8888 int main(void)
{
int sfd, cfd;
int len, i;
char buf[BUFSIZ], clie_IP[BUFSIZ]; struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len; sfd = socket(AF_INET, SOCK_STREAM, );
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(SERV_PORT); bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)); listen(sfd, );
printf("wait for client connect ...\n"); clie_addr_len = sizeof(clie_addr_len);
cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);
printf("client IP:%s\tport:%d\n",
inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),
ntohs(clie_addr.sin_port)); while ()
{
printf("sleep\n");
sleep();
int len = read(cfd, buf, );
if ( == len)
{
printf("客户端退出\n");
close(cfd);
break;
}
/*把读到的数据打印出来*/
printf("%d: ", len);
for (int i = ; i < len; ++i)
printf("%c", buf[i]);
printf("\n");
}
close(sfd);
return ;
}

输出:

  • 客户端

  • 服务端

参考资料

epoll(二)的更多相关文章

  1. 第二十二篇、IO多路复用 一

    一.简介io多路复用 可以监听多个文件描述符(socket对象)(文件句柄),一旦文件句柄出现变化,就会感知到 Linux中的 select,poll,epoll(内核2.6以上) 都是IO多路复用的 ...

  2. epoll机制

    一.参考网址 1.epoll机制:epoll_create.epoll_ctl.epoll_wait.close 2.Linux网络编程 使用epoll实现一个高性能TCP Echo服务器 3.用C写 ...

  3. 超哥笔记 --nginx入门(6)

    一 NGINX 1 nignx是什么 nginx是一个开源的支持高性能,高并发的web服务和代理服务软件. nginx比他大哥apache性能改进许多,nginx占用的系统资源更少,支持高并发连接,有 ...

  4. go语言之行--golang操作redis、mysql大全

    一.redis 简介 redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写key-value存储系统,它由C语言编写.遵守BSD协议.支持网 ...

  5. Linux -- nginx

    一. 网络服务 web服务器和web框架的关系 web服务器(nginx):接收HTTP请求(例如www.baidu.com)并返回数据 web框架(django,flask):开发web应用程序,处 ...

  6. Redis入门到高可用(五)—— 单线程

    一.单线程为何这么快 1)绝大部分请求是纯粹的内存操作(非常快速) 2)采用单线程,避免了不必要的上下文切换和竞争条件 3)非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框 ...

  7. 15 并发编程-(IO模型)

    一.IO模型介绍 1.阻塞与非阻塞指的是程序的两种运行状态 阻塞:遇到IO就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源 非阻塞(就绪态或运行态):没有遇到IO操作,或者通过某种 ...

  8. selectors

    一.Selectors模块 它具有根据平台选出最佳的IO多路机制,比如在win的系统上他默认的是select模式而在linux上它默认的epoll,建议使用selectors. 常用共分为三种:sel ...

  9. IO多路复用,协程,

    一.单线程的并发 import socket import select client1 = socket.socket() client1.setblocking(False) # 百度创建连接: ...

  10. naginx安装入门

    一.nginx是什么 nginx是一个开源的,支持高性能,高并发的www服务和代理服务软件.它是一个俄罗斯人lgor sysoev开发的,作者将源代码开源出来供全球使用. nginx比它大哥apach ...

随机推荐

  1. Spring Security测试代码

    ⒈实体Bean package cn.coreqi.blog.entities; import org.springframework.security.core.GrantedAuthority; ...

  2. 【转】python模块分析之collections(六)

    [转]python模块分析之collections(六) collections是Python内建的一个集合模块,提供了许多有用的集合类. 系列文章 python模块分析之random(一) pyth ...

  3. camera理论基础和工作原理【转】

    转自:http://www.cnblogs.com/fjutacm/p/220631977df995512d136e4dbd411951.html 写在前面的话,本文是因为工作中需要编写摄像头程序,因 ...

  4. DES和3DES加密算法C语言实现【转】

    转自:https://blog.csdn.net/leumber/article/details/78043675 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.cs ...

  5. C语言 16进制转float

    float hex_to_float(uint8_t *data) { float num = 0.0; uint8_t dd[4] = {data[0], data[1], data[2], dat ...

  6. [转载]RabbitMQ消息可靠性分析

    有很多人问过我这么一类问题:RabbitMQ如何确保消息可靠?很多时候,笔者的回答都是:说来话长的事情何来长话短说.的确,要确保消息可靠不只是单单几句就能够叙述明白的,包括Kafka也是如此.可靠并不 ...

  7. Linux系统基础优化及常用命令

    Linux基础系统优化 引言没有,只有一张图. Linux的网络功能相当强悍,一时之间我们无法了解所有的网络命令,在配置服务器基础环境时,先了解下网络参数设定命令. ifconfig 查询.设置网卡和 ...

  8. Mysql 的安装(压缩文件)和基本管理

    MySql安装和基本管理   本节掌握内容: mysql的安装.启动 mysql破解密码 统一字符编码 MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于 Oracle ...

  9. HSSFWorkbook操作excel读写

    //exlel读操作 MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request; Ite ...

  10. 32)django-modelform

    一:mdoelform modelform是model和form结合 model+form =>验证+数据库 class A(model): user= pass= Form: class Lo ...