Linux系统编程——水平触发和边沿触发

摘要:
LevelTriggered只要有数据,就会触发水平触发器。水平触发器:tcp_Server。只要有数据,就会触发c。这里,buf[]大小更改为5,以获得显示效果#Include //水平触发器,缓冲区中有数据时触发intmain{ARGS_CHECK;intsocketFd;socketFd=socket;ERROR_CHECK;structsockaddr_insert;bzero;server.sin_family=AF_INET;ser.sin_port=htons;ser.sin _addr.s_addr=INET_addr;//虚线十进制系统转换为32位网络字节顺序int;ret=bind;ERROR-CHECK;listen;//缓冲区大小、客户端连接信息intnew_fd;structsockaddr_client;bzer o;intaddrlen=sizeof;new_fd=接受;错误检查;输出函数charbuf[5]={0};intepfd=epoll_创建;//参数大小表示监听器ERROR_ CHECK的数量和大小;结构球事件,evs[2];event.events=EPOLLIN;//指示相应的文件描述符可读,并监视读取事件事件。数据fd=标准文件编号;ret=epoll_ ctl;//一次注册永久有效ERROR_ CHECK;event.data.fd=new_fd;epoll_ ctl;inti,readyFdNum;当{memset;readyFdNum=epoll_wait;//大小为2时,-1表示永久阻塞,返回值为{if{bzero;ret=recv;ERROR_CHECK;if{printf(“byebye!

事件模型

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
", 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!
");
					goto chatOver;
				}			
				printf("%s
", buf);
			}
			if(0 == evs[i].data.fd){
				memset(buf, 0, sizeof(buf));
				ret = read(STDIN_FILENO, buf, sizeof(buf));
				if(ret == 0){
					printf("byebye!
");
					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
");
	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!
");
				break;
			}
			printf("%s
", buf);
		}
		if(FD_ISSET(STDIN_FILENO, &rdset)){
			memset(buf, 0, sizeof(buf));
			ret = read(STDIN_FILENO, buf, sizeof(buf));
			if(ret == 0){
				printf("byebye!
");
				break;
			}
			ret = send(socketFd, buf ,strlen(buf) - 1, 0);
			ERROR_CHECK(ret, -1, "send");
		}
	}
	close(socketFd);
}

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

1556457978494

边沿触发:

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
", 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!
");
	         			goto chatOver;
	         		}
                    else if(-1 == ret){
                        printf("
");
                        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!
");
					goto chatOver;
				}
				ret = send(new_fd, buf, strlen(buf) - 1, 0);
				ERROR_CHECK(ret, -1, "send");
			}
		}
	}
chatOver:
	close(new_fd);
	close(socketFd);
	return 0;
}

1556457985915

免责声明:文章转载自《Linux系统编程——水平触发和边沿触发》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇基于 ThinkPHP5+Bootstrap 的后台开发框架 FastAdminJAVA学习总结(四)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

如何在Ubuntu中使用Eclipse + CDT开发C/C++程序

在Ubuntu中安装Eclipse和CDT步骤如下: 1. 下载资源(都下载到/home/maxw/Download/Eclipse下)    A、 下载JRE(Java Runtime Environment) 。(http://java.sun.com/javase/downloads/index.jsp )              选择下载JRE的...

04: gitlab安装与使用

1.1 gitlab安装(192.168.56.12中安装)   1、GitLab是什么?       1. GitLab实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私人项目。       2. GitLab拥有与Github类似的功能,能够浏览源代码,管理缺陷和注释。       3. 可以管理团队对仓库的访问,它非常易于浏览提交...

【 Linux 】Systemd 使用说明(1)

1. 前言   在 CentOS 7 中使用 systemd 取代了 init 的启动模式,这样的更新换代有什么好处呢?首先需要对 init 和 systemd 有个概念的认识。 2. init 概述   在 CentOS 7 之前的版本中,init 作为第一个启动进程,是所有进程的父进程。使用 init 有两个显著的缺点:   (1)启动时间长。init...

Linux 查看修改swap大小

1、查看swap 空间大小(总计): # free -m 默认单位为k, -m 单位为M 2、查看swap 空间(file(s)/partition(s)): 包括 文件 和 分区 的详细信息  # swapon -s  等价于  # cat /proc/swaps 3、添加交换空间  两种选择:添加一个交换分区或添加一个交换文件。推荐你添加一个交换分区;...

如何指定GCC的默认头文件路径

如何指定GCC的默认头文件路径 网上偶搜得之,以之为宝:)原地址:http://blog.chinaunix.net/u/28781/showart.php?id=401631===============================================================================在交叉编译的时候我们需要...

js原生封装自定义滚动条

1 /* 2 * @Author: dothin前端 3 * @Date: 2015-11-21 00:12:15 4 * @Last Modified by: dothin前端 5 * @Last Modified time: 2015-11-21 00:29:12 6 */ 7 ! function()...