epoll(二)
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(二)的更多相关文章
- 第二十二篇、IO多路复用 一
一.简介io多路复用 可以监听多个文件描述符(socket对象)(文件句柄),一旦文件句柄出现变化,就会感知到 Linux中的 select,poll,epoll(内核2.6以上) 都是IO多路复用的 ...
- epoll机制
一.参考网址 1.epoll机制:epoll_create.epoll_ctl.epoll_wait.close 2.Linux网络编程 使用epoll实现一个高性能TCP Echo服务器 3.用C写 ...
- 超哥笔记 --nginx入门(6)
一 NGINX 1 nignx是什么 nginx是一个开源的支持高性能,高并发的web服务和代理服务软件. nginx比他大哥apache性能改进许多,nginx占用的系统资源更少,支持高并发连接,有 ...
- go语言之行--golang操作redis、mysql大全
一.redis 简介 redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写key-value存储系统,它由C语言编写.遵守BSD协议.支持网 ...
- Linux -- nginx
一. 网络服务 web服务器和web框架的关系 web服务器(nginx):接收HTTP请求(例如www.baidu.com)并返回数据 web框架(django,flask):开发web应用程序,处 ...
- Redis入门到高可用(五)—— 单线程
一.单线程为何这么快 1)绝大部分请求是纯粹的内存操作(非常快速) 2)采用单线程,避免了不必要的上下文切换和竞争条件 3)非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框 ...
- 15 并发编程-(IO模型)
一.IO模型介绍 1.阻塞与非阻塞指的是程序的两种运行状态 阻塞:遇到IO就发生阻塞,程序一旦遇到阻塞操作就会停在原地,并且立刻释放CPU资源 非阻塞(就绪态或运行态):没有遇到IO操作,或者通过某种 ...
- selectors
一.Selectors模块 它具有根据平台选出最佳的IO多路机制,比如在win的系统上他默认的是select模式而在linux上它默认的epoll,建议使用selectors. 常用共分为三种:sel ...
- IO多路复用,协程,
一.单线程的并发 import socket import select client1 = socket.socket() client1.setblocking(False) # 百度创建连接: ...
- naginx安装入门
一.nginx是什么 nginx是一个开源的,支持高性能,高并发的www服务和代理服务软件.它是一个俄罗斯人lgor sysoev开发的,作者将源代码开源出来供全球使用. nginx比它大哥apach ...
随机推荐
- Python笔记 【无序】 【二】
序列list() ——把一个可迭代对象[可以是字符串,元组]转化为列表,可不带参数——生成空列表,或者带一个迭代器作为参数tuple() ——可迭代对象转化为元组str(obj) ——把obj对象转换 ...
- 绕过PALOALTO TRAPS EDR解决方案
0x1 技术点 PaloAlto Traps(EDR解决方案)基于行为封锁和标记许多黑客工具. 0x2 绕过方法 最简单的解决方案就是禁用内置实用程序,即; Cytool.Cytool是一个集成命令行 ...
- 【转】Python数据类型之“文本序列(Text Sequence)”
[转]Python数据类型之“文本序列(Text Sequence)” Python中的文本序列类型 Python中的文本数据由str对象或字符串进行处理. 1.字符串 字符串是Unicode码值的不 ...
- Liunx之始
作者:邓聪聪 本章是为了工作之需要,所做的一个学习心得,也为自己留下相关印记以便日后留用. 1:安装服务yum install ppp -yyum install pptpd -y由于yum源没有pp ...
- Win10系统BitLocker解锁后再次快速锁定办法
原文地址:https://blog.csdn.net/ttljtw/article/details/54022241 谁都不愿意把自己电脑上资料完全公开,对资料选择性加密处理是唯一的办法. 微软Win ...
- hibernate框架学习之增删改查helloworld
插入数据删除数据修改数据查询单条数据查询多条数据 HelloWorldApp.java package cn.itcast.h3.helloworld; import org.hibernate.Se ...
- FHQ Treap摘要
原理 以随机数维护平衡,使树高期望为logn级别 不依靠旋转,只有两个核心操作merge(合并)和split(拆分) 因此可持久化 先介绍变量 ; int n; struct Node { int v ...
- (转!)Pyinstaller 打包发布经验总结
原文地址 https://blog.csdn.net/weixin_42052836/article/details/82315118 具体的实现图待本人实现后贴上 原 Pyinstaller 打包发 ...
- Mac 上卸载 Java
如何在 Mac 上卸载 Java? 本文适用于: 平台: Macintosh OS X Java 版本: 7.0, 8.0 使用终端卸载 Oracle Java 注:要卸载 Java,必须具有管理员权 ...
- 【原创】大数据基础之Spark(9)spark部署方式yarn/mesos
1 下载解压 https://spark.apache.org/downloads.html $ wget http://mirrors.shu.edu.cn/apache/spark/spark-2 ...