手写一个epoll服务器 超详细解释

首先必须谈谈epollin和epollout事件触发的条件 没看这个 整个epoll就是懵的

1、EPOLLOUT事件触发的条件?

(1)、一次write操作,写满了发送缓冲区,返回错误码为EAGAIN(11)。

(2)、对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。

简单地说:内核缓冲区由不可写变为可写时会触发,才会触发一次,所以叫边缘触发。

暴力方法:直接调用epoll_ctl()重新设置一下event就可以了, event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。

2、EPOLLIN事件触发的条件?

对端有数据写入时才会触发。

epoll还分为LT和ET 即水平触发和边缘触发

LT就是只要满足条件我就会一直触发事件 而ET是你满足一次条件我触发一次事件

LT模式会一直触发EPOLLOUT,当缓冲区有数据时会一直触发EPOLLIN
ET模式会在连接建立后触发一次EPOLLOUT,当收到数据时会触发一次EPOLLIN
LT模式触发EPOLLIN时可以按需读取数据,残留了数据还会再次通知读取
ET模式触发EPOLLIN时必须把数据读取完,否则即使来了新的数据也不会再次通知了
LT模式的EPOLLOUT会一直触发,所以发送完数据记得删除,否则会产生大量不必要的通知
ET模式的EPOLLOUT事件若数据未发送完需再次注册,否则不会再有发送的机会
通常发送网络数据时不会依赖EPOLLOUT事件,只有在缓冲区满发送失败时会注册这个事件,期待被通知后再次发送

下面就是epoll服务器的代码 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>

#include <errno.h>
#include <sys/epoll.h>


struct sockitem {
	int sockfd;
	int (*callback)(int fd, int events, void *arg);`

}
//等会epoll传入的指针需要用到的结构体 用来回调

//  ./epoll 8080

int recv_cb(int fd, int events, void *arg) {

}
//具体的回调函数

int accept_cb(int fd, int events, void *arg) {

	int clientfd = accept();

	struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
	si->sockfd = clientfd;
	si->callback = recv_cb;
	//
	epoll_ctl()
}
//具体的回调函数
int main(int argc, char *argv[]) {

	if (argc < 2) {
		return -1;
	}

	int port = atoi(argv[1]);//

	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0) {
		return -1;
	}

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));

	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;

	if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
		return -2;
	}

	if (listen(sockfd, 5) < 0) {
		return -3;
	}
	//上面都是最基本的socket流程 不想过多讲述
	// 从这里开始epoll
	//首先创建一个epoll 参数无所谓01
	int epfd = epoll_create(1);
	//接下来创建epoll需要的结构体 该结构体用来像epoll内注册io事件和获取一些信息
	struct epoll_event ev, events[512] = {0};
	//ev用来临时加入epoll events用来获取已经被写入或被读取数据的io
	ev.events = EPOLLIN;//帮我监听epollin事件
	ev.data.fd = sockfd; //文件描述符是sockfd
	//epoll_event有个最大的好处就是他允许你带一个参数进去 那么这里我们就传入我们自定义的结构体 等下就能自动调用回调函数

	struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
	si->sockfd = sockfd;
	si->callback = accept_cb;
	//上面是填写结构体的值 没啥好说的
	//注意第二个值是一个回调函数 准备回调的
	ev.data.ptr = si;
	//将指针也写入epoll_event结构体

	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
	//将最开始用来listen的结构体传入epoll中开始监测
	
	while (1) {
		//写一个循环不断的去epoll中拿被改写过的IO
		int nready = epoll_wait(epfd, events, 512, 50);
		//这里的意思就是我去epfd对应的epoll中去拿 最多拿512个 给我放到首地址为events的地方 最长等待时间是50ms
		//此时间将四舍五入为系统时钟的粒度,并且内核调度延迟意味着阻塞间隔可能会少量溢出。
		if (nready < -1) {
			break;
		}
		//没收到的话 直接break掉

		int i = 0;
		for (i = 0;i < nready;i ++) {

			if (events[i].events & EPOLLIN) {
				//如果是epollin事件
				struct sockitem *si = (struct sockitem*)events[i].data.ptr;
				si->callback(events[i].data.fd, events[i].events, si);


			}

			if (events[i].events & EPOLLOUT) {
				//如果是epollout事件
	           //代码就不写了 跟上面流程是一样的
				

			}

		}

	}

}

 但是以上代码明显有几个思考的点 比如多线程下如何同时使用epoll 文件描述符之间如何传递?

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杀神李

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值