事件模型

Edge Triggered (ET) 边缘触发只有数据到来,才触发,不管缓存区中是否还有数据。

Level Triggered (LT) 水平触发只要有数据都会触发。

首先介绍一下LT工作模式:

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

优点:当进行socket通信的时候,保证了数据的完整输出,进行IO操作的时候,如果还有数据,就会一直的通知你。

缺点:由于只要还有数据,内核就会不停的从内核空间转到用户空间,所有占用了大量内核资源,试想一下当有大量数据到来的时候,每次读取一个字节,这样就会不停的进行切换。内核资源的浪费严重。效率来讲也是很低的。

ET:

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).

优点:每次内核只会通知一次,大大减少了内核资源的浪费,提高效率。

缺点:不能保证数据的完整。不能及时的取出所有的数据。

应用场景: 处理大数据。使用non-block模式的socket。

水平触发:

tcp_server.c

只要有数据就触发,这里为了显示效果将buf[]大小改为5。

#include <func.h>
//水平触发,缓冲区有数据就触发
int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
int socketFd;
socketFd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(socketFd, -1, "socket");
struct sockaddr_in ser;
bzero(&ser, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(atoi(argv[2]));
ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
int ret;
ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
ERROR_CHECK(ret, -1, "bind");
listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
int new_fd;
struct sockaddr_in client;
bzero(&client, sizeof(client));
int addrlen = sizeof(client);
new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
ERROR_CHECK(new_fd, -1, "accept");
printf("client ip=%s, port=%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
char buf[5] = {0};
int epfd = epoll_create(1);//参数size表示监听的数目大小
ERROR_CHECK(epfd, -1, "epoll_create");
struct epoll_event event, evs[2];
event.events = EPOLLIN; //表示对应的文件描述符可读,监控读事件
event.data.fd = STDIN_FILENO;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);//一次注册永久生效
ERROR_CHECK(ret, -1, "epoll_ctl");
event.data.fd = new_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &event);
int i, readyFdNum;
while(1){
memset(evs, 0, sizeof(evs));
readyFdNum = epoll_wait(epfd, evs, 2, -1);//大小为2,-1表示永久阻塞,返回值是需要处理的事件数目
for(i = 0;i < readyFdNum;i++){
if(evs[i].data.fd == new_fd){
bzero(buf, sizeof(buf));
ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
ERROR_CHECK(ret, -1, "recv");
if(ret == 0){
printf("byebye!\n");
goto chatOver;
}
printf("%s\n", buf);
}
if(0 == evs[i].data.fd){
memset(buf, 0, sizeof(buf));
ret = read(STDIN_FILENO, buf, sizeof(buf));
if(ret == 0){
printf("byebye!\n");
goto chatOver;
}
ret = send(new_fd, buf, strlen(buf) - 1, 0);
ERROR_CHECK(ret, -1, "send");
}
}
}
chatOver:
close(new_fd);
close(socketFd);
return 0;
}

tcp_client.c

#include <func.h>

int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
int socketFd;
socketFd=socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(socketFd,-1,"socket");
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(atoi(argv[2]));
ser.sin_addr.s_addr=inet_addr(argv[1]);//点分十进制转为32位的网络字节序
int ret;
ret=connect(socketFd,(struct sockaddr*)&ser,sizeof(ser));
ERROR_CHECK(ret, -1, "connect");
printf("connect success\n");
char buf[128]={0};
fd_set rdset;
while(1){
FD_ZERO(&rdset);
FD_SET(STDIN_FILENO, &rdset);
FD_SET(socketFd, &rdset);
ret = select(socketFd + 1, &rdset, NULL, NULL, NULL);
if(FD_ISSET(socketFd, &rdset)){
bzero(buf, sizeof(buf));
ret = recv(socketFd, buf, sizeof(buf), 0);
ERROR_CHECK(ret, -1, "recv");
if(ret == 0){
printf("byebye!\n");
break;
}
printf("%s\n", buf);
}
if(FD_ISSET(STDIN_FILENO, &rdset)){
memset(buf, 0, sizeof(buf));
ret = read(STDIN_FILENO, buf, sizeof(buf));
if(ret == 0){
printf("byebye!\n");
break;
}
ret = send(socketFd, buf ,strlen(buf) - 1, 0);
ERROR_CHECK(ret, -1, "send");
}
}
close(socketFd);
}

当客户端输入较长的字符串时,效果如下:

边沿触发:

tcp_server.c

主要修改了注册信号EPOLLET,另外加上了changeNonblock(new_fd)非阻塞,同时在数据到来时加了while(1)循环读取,判断read的返回值跳出循环。

#include <func.h>
//水平出发,缓冲区有数据就触发
//边沿触发,缓冲区数据出现增加时触发 void changeNonblock(int fd){
int status = fcntl(fd, F_GETFL);
status = status|O_NONBLOCK;
fcntl(fd, F_SETFL, status);
} int main(int argc,char* argv[])
{
ARGS_CHECK(argc,3);
int socketFd;
socketFd = socket(AF_INET,SOCK_STREAM,0);
ERROR_CHECK(socketFd, -1, "socket");
struct sockaddr_in ser;
bzero(&ser, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(atoi(argv[2]));
ser.sin_addr.s_addr = inet_addr(argv[1]);//点分十进制转为32位的网络字节序
int ret;
ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
ERROR_CHECK(ret, -1, "bind");
listen(socketFd, 10);//缓冲区的大小,一瞬间能够放入的客户端连接信息
int new_fd;
struct sockaddr_in client;
bzero(&client, sizeof(client));
int addrlen = sizeof(client);
new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
ERROR_CHECK(new_fd, -1, "accept");
printf("client ip=%s, port=%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
char buf[5] = {0};
int epfd = epoll_create(1);//参数size表示监听的数目大小
ERROR_CHECK(epfd, -1, "epoll_create");
struct epoll_event event, evs[2];
event.events = EPOLLIN|EPOLLET; //表示对应的文件描述符可读,监控读事件
event.data.fd = STDIN_FILENO;
changeNonblock(new_fd);
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);//一次注册永久生效
ERROR_CHECK(ret, -1, "epoll_ctl");
event.data.fd = new_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &event);
int i, readyFdNum;
while(1){
memset(evs, 0, sizeof(evs));
readyFdNum = epoll_wait(epfd, evs, 2, -1);//大小为2,-1表示永久阻塞,返回值是需要处理的事件数目
for(i = 0;i < readyFdNum;i++){
if(evs[i].data.fd == new_fd){
while(1){
bzero(buf, sizeof(buf));
ret = recv(new_fd, buf, sizeof(buf) - 1, 0);
//ERROR_CHECK(ret, -1, "recv");
if(ret == 0){
printf("byebye!\n");
goto chatOver;
}
else if(-1 == ret){
printf("\n");
break;
}
else{
printf("%s", buf); //不打换行,等break后再换行刷新
}
}
}
if(0 == evs[i].data.fd){
memset(buf, 0, sizeof(buf));
ret = read(STDIN_FILENO, buf, sizeof(buf));
if(ret == 0){
printf("byebye!\n");
goto chatOver;
}
ret = send(new_fd, buf, strlen(buf) - 1, 0);
ERROR_CHECK(ret, -1, "send");
}
}
}
chatOver:
close(new_fd);
close(socketFd);
return 0;
}

Linux系统编程——水平触发和边沿触发的更多相关文章

  1. Linux系统编程温故知新系列 --- 01

    1.大端法与小端法 大端法:按照从最高有效字节到最低有效字节的顺序存储,称为大端法 小端法:按照从最低有效字节到最高有效字节的顺序存储,称为小端法 网际协议使用大端字节序来传送TCP分节中的多字节整数 ...

  2. LINUX系统编程 由REDIS的持久化机制联想到的子进程退出的相关问题

    19:22:01 2014-08-27 引言: 以前对wait waitpid 以及exit这几个函数只是大致上了解,但是看REDIS的AOF和RDB 2种持久化时 均要处理子进程运行完成退出和父进程 ...

  3. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  4. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  5. 《Linux系统编程(第2版)》

    <Linux系统编程(第2版)> 基本信息 作者: (美)Robert Love 译者: 祝洪凯 李妹芳 付途 出版社:人民邮电出版社 ISBN:9787115346353 上架时间:20 ...

  6. Linux 系统编程 学习:00-有关概念

    Linux 系统编程 学习:00-有关概念 背景 系统编程其实就是利用系统中被支持的调度API进行开发的一个过程. 从这一讲开始,我们来介绍有关Linux 系统编程的学习. 知识 在进行Linux系统 ...

  7. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

  8. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  9. Linux 系统编程 学习:10-线程:线程的属性

    Linux 系统编程 学习:10-线程:线程的属性 背景 上一讲我们介绍了线程的创建,回收与销毁:简单地提到了线程属性.这一讲我们就来具体看看,线程的属性. 概述 #include <pthre ...

随机推荐

  1. Hadoop集群启动

    1.初始化集群 要启动Hadoop集群,需要启动HDFS和YARN两个集群 注意:首次启动HDFS时,必须对其进行格式化操作.本质上是一些清理和准备工作, 因为此时的HDFS在物理上还是不存在的 命令 ...

  2. C++编程题#1:含k个3的数

    描述 输入二个正整数m 和 k,其中1 < m < 100000,1 < k <5 ,判断m 能否被19整除,且恰好含有k个3,如果满足条件,则输出YES,否则,输出NO. 例 ...

  3. Python学习第七课

    Python学习第七课 'Alex' "Alex"print('hello'*5) #重复输出字符串 print('hellowold'[2:]) #类似于切片操作:会取出 llo ...

  4. cobbler批量化安装系统

  5. ubuntu 16.04 编译安装 amule (开启GUI)

    安装依赖 $ -dev libgeoip-dev zlib1g-dev libupnp-dev libboost-all-dev libwxbase3.-dev libwxgtk3.-dev buil ...

  6. 2018上C语言程序设计(高级)作业- 第3次作业成绩

    作业地址 https://edu.cnblogs.com/campus/hljkj/CS2017-01/homework/1779 评分准则 第一次作业各项成绩包括三项: 完成PTA所有题目:13分 ...

  7. 数组之slice,splice,Concact,Reverse,Sort方法

    Slice(strart,end)用来从数组中提取元素.该方法不会改变元素数组,而是将截取到的元素封装到一个新数组中返回 参数start 截取开始的位置索引,包含开始索引 参数end 截取结束位置的索 ...

  8. PHP常用函数(一):数组常用函数

    1.list() list() 和 array() 一样,不是一个函数,而是一个语言结构,作用是为一组变量赋值.  PHP手册中的介绍 使用详情 <?php //假设现在想为$a $b $c三个 ...

  9. Mybatis集成Oracle

    首先需要导入Oracle的驱动,这部分会有一个天坑 Maven无法直接将我们所需的Oracle驱动加入项目中,手动加入依赖也是无效(原因还在分析),而且驱动无效如果不注意的话是看不出来的,他不会在编译 ...

  10. Windows下部署Apache RocketMQ

    一:环境准备: Windows.JDK1.8+.Maven.Git 二:RocketMQ准备:  1.http://rocketmq.apache.org/release_notes/release- ...