socket编程——epoll模型

利用epoll模型实现多个客户端和一个服务端的CS模型——多路IO复用技术

【上一篇】:poll模型
【阅前必读】:epoll详解

epoll优点

  1. select和poll是线性处理,而epoll是红黑树处理
  2. select和poll频繁的在内核和用户区进行拷贝,而epoll使用的是共享内存
  3. 程序需要对select和poll返回的集合进行判断才能知道哪些文件描述符是就绪的,但epoll可以直接得到已就绪的文件描述符集合,无需再次检测
  • epoll
    将检测文件描述符的变化委托给内核去处理,然后内核将发生变化的描述符对应的事件返回给程序。不仅告诉程序有几个发生变化,而且精准的告诉程序哪几个发生变化
创建一棵epoll树
int epoll_create(int size);
param:
	size: 最大节点数,需要传递大于0的数,linux上该参数被忽略
return:
	成功:返回大于0的文件描述符,代表整个树的树根
	失败:返回-1


将要监听的节点在epoll树上添加、删除和修改
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
param:
	epfd:epoll_create函数的返回值
	op:	EPOLL_CTL_ADD: 往 epoll 模型中添加新的节点
			EPOLL_CTL_MOD: 修改 epoll 模型中已经存在的节点
			EPOLL_CTL_DEL: 删除 epoll 模型中的指定的节点
	fd:要操作的文件描述符
	event:检测这个文件描述符的什么事件
		event.events:委托 epoll 检测的事件
				EPOLLIN:读事件,接收数据,检测读缓冲区
				EPOLLOUT:写事件,发送数据,检测写缓冲区
				EPOLLERR:异常事件
		event.data:通常情况下使用里边的fd成员,委托内核监控的文件描述符,在调用 epoll_wait函数的时候这个值会被传出
return:
	成功:返回 0
	失败:返回 - 1


委托内核监控epoll实例中有没有就绪的文件描述符
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
param:
	epfd: epoll_create函数的返回值,通过这个参数找到 epoll 实例
	events: 传出参数,里边存储了已就绪的文件描述符的信息
	maxevents: 修饰第二个参数,结构体数组的容量(元素个数)
	timeout: 	0:函数不阻塞
				>0:函数阻塞对应的毫秒数再返回
				-1:函数一直阻塞,直到 epoll 实例中有已就绪的文件描述符之后才解除阻塞

服务端代码如下,客户端代码不变

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<netinet/in.h>
#include<errno.h>
#include<sys/epoll.h>

int main()
{
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd < 0)
	{
		perror("socket error");
		return -1;
	}

	struct sockaddr_in serv;
	bzero(&serv, sizeof(serv));
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));
	if(ret < 0)
	{
		perror("bind error");
		return -1;
	}

	listen(lfd, 128);
	
	//创建一棵epoll树
	struct epoll_event ev;
	struct epoll_event events[1024];
	int epfd = epoll_create(1024);
	if(epfd < 0)
	{
		perror("create epoll error");
		return -1;
	}
	
	//将监听文件描述符上树
	ev.data.fd = lfd;
	ev.events = EPOLLIN;
	epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
	
	int nready;
	int max=0;//表示内核监控的范围,一开始有1个
	int cfd;
	int sockfd;
	int i;
	int n;

	char buf[1024];
	while(1)
	{
		nready = epoll_wait(epfd, events, 1024, -1);
		if(nready < 0)
		{
			if(errno == EINTR)
				continue;
			perror("epoll wait error\n");
			break;
		}
		
		for(i=0; i<nready; i++)
		{
			//有客户端连接请求到来
			sockfd = events[i].data.fd;
			if(sockfd == lfd)
			{
				cfd = accept(lfd, NULL, NULL);
				//将新的cfd上树
				ev.data.fd = cfd;
				ev.events = EPOLLIN;
				epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
				continue;
			}

			//有数据发来
			memset(buf, 0, sizeof(buf));
			//n = read(sockfd, buf, sizeof(buf));
			n = recv(sockfd, buf, sizeof(buf), 0);
			if(n <= 0)
			{
				close(sockfd);
				//将sockfd对应的事件的节点从epoll树上删除
				epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
				printf("read error or client close\n");
				continue;
			}
			printf("n == [%d], buf == [%s]\n", n, buf);

			for(int j=0; j<n; j++)
			{
				buf[j] = toupper(buf[j]);
			}

			//write(sockfd, buf, n);
			send(sockfd, buf, n, 0);		}
	}
	
	close(epfd);
	close(lfd);
	return 0;
}

扩展:
epoll的ET(边沿触发)LT(水平触发) 模式

  • epoll默认情况下是LT模式,在这种模式下,若读数据一次性没有读完,缓冲区还有可读数据,则epoll_wait还会通知(比如发10个数据,一次只读两次,那么会循环读取5次)
  • 若将epoll设置为ET模式(ev.events = EPOLLIN | EPOLLET),若读数据的时候一次性没有读完,则epoll_wait不再通知,直到下次有新的数据发来(还会读取上一次缓冲区已收到但还未读走的数据)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值