Linux网络编程之“epoll”

223 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 27 天,点击查看活动详情

一、read 函数返回值

> 0: 实际读到的字节数

= 0: socket中,表示对端关闭。close()

- 1: 如果 errno == EINTR 被异常终端。 需要重启。

如果 errno == EAGIN 或 EWOULDBLOCK 以非阻塞方式读数据,但是没有数据。 需要,再次读。

如果 errno == ECONNRESET 说明连接被 重置。 需要 close(),移除监听队列。 错误。

二、突破 1024 文件描述符限制

cat /proc/sys/fs/file-max --> 当前计算机所能打开的最大文件个数。 受硬件影响。

ulimit -a ——> 当前用户下的进程,默认打开文件描述符个数。 缺省为 1024

修改:

打开 sudo vi /etc/security/limits.conf, 写入:

  • soft nofile 65536 --> 设置默认值, 可以直接借助命令修改。 【注销用户,使其生效】

  • hard nofile 100000 --> 命令修改上限。

三、epoll

int epoll_create(int size);						创建一棵监听红黑树

	size:创建的红黑树的监听节点数量。(仅供内核参考。)

	返回值:指向新创建的红黑树的根节点的 fd。 

		失败: -1 errno

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);	操作监听红黑树

	epfd:epoll_create 函数的返回值。 epfd

	op:对该监听红黑数所做的操作。

		EPOLL_CTL_ADD 添加fd到 监听红黑树

		EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件。

		EPOLL_CTL_DEL 将一个fd 从监听红黑树上摘下(取消监听)

	fd:
		待监听的fd

	event:	本质 struct epoll_event 结构体 地址

		成员 events:

			EPOLLIN / EPOLLOUT / EPOLLERR

		成员 data: 联合体(共用体):

			int fd;	  对应监听事件的 fd

			void *ptr; 

			uint32_t u32;

			uint64_t u64;		

	返回值:成功 0; 失败: -1 errno


int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 	 阻塞监听。

	epfd:epoll_create 函数的返回值。 epfd

	events:传出参数,【数组】, 满足监听条件的 哪些 fd 结构体。

	maxevents:数组 元素的总个数。 1024
			
		struct epoll_event evnets[1024]
	timeout:

		-1: 阻塞

		0: 不阻塞

		>0: 超时时间 (毫秒)

	返回值:

		> 0: 满足监听的 总个数。 可以用作循环上限。

		0: 没有fd满足监听事件

		-1:失败。 errno

epoll image.png

四、epoll实现多路IO转接思路

lfd = socket(); 监听连接事件lfd

bind();

listen();

int epfd = epoll_create(1024); epfd, 监听红黑树的树根。

struct epoll_event tep, ep[1024]; tep, 用来设置单个fd属性, ep 是 epoll_wait() 传出的满足监听事件的数组。

tep.events = EPOLLIN; 初始化 lfd的监听属性。 tep.data.fd = lfd

epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep); 将 lfd 添加到监听红黑树上。


while (1) {

	ret = epoll_wait(epfd, ep,1024, -1);			实施监听

	for (i = 0; i < ret; i++) {
		
		if (ep[i].data.fd == lfd) {				// lfd 满足读事件,有新的客户端发起连接请求

			cfd = Accept();

			tep.events = EPOLLIN;				初始化  cfd的监听属性。
			tep.data.fd = cfd;

			epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);

		} else {						cfd 们 满足读事件, 有客户端写数据来。

			n = read(ep[i].data.fd, buf, sizeof(buf));

			if ( n == 0) {

				close(ep[i].data.fd);

				epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL);	// 将关闭的cfd,从监听树上摘下。

			} else if (n > 0) {

				小--大
				write(ep[i].data.fd, buf, n);
			}
		}
	}
}

五、epoll 事件模型

ET模式:

  • 边沿触发:

    缓冲区剩余未读尽的数据不会导致 epoll_wait 返回。 新的事件满足,才会触发。

    struct epoll_event event;

    event.events = EPOLLIN | EPOLLET;

  • LT模式:

    水平触发 -- 默认采用模式。

    缓冲区剩余未读尽的数据会导致 epoll_wait 返回。

  • 结论:

    epoll 的 ET模式, 高效模式,但是只支持 非阻塞模式。 --- 忙轮询。

      struct epoll_event event;
    
      event.events = EPOLLIN | EPOLLET;
    
      epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event);	
    
      int flg = fcntl(cfd, F_GETFL);	
    
      flg |= O_NONBLOCK;
    
      fcntl(cfd, F_SETFL, flg);
    
  • 优点:

    高效。突破1024文件描述符。

  • 缺点:

    不能跨平台。 Linux。

六epoll 反应堆模型

epoll ET模式 + 非阻塞、轮询 + void *ptr。

  • 原来:

    socket、bind、listen -- epoll_create 创建监听 红黑树 -- 返回 epfd -- epoll_ctl() 向树上添加一个监听fd -- while(1)---- epoll_wait 监听 -- 对应监听fd有事件产生 -- 返回 监听满足数组。 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd 满足 -- read() --- 小->大 -- write回去。

  • 反应堆:不但要监听 cfd 的读事件、还要监听cfd的写事件。

    socket、bind、listen -- epoll_create 创建监听 红黑树 -- 返回 epfd -- epoll_ctl() 向树上添加一个监听fd -- while(1)---- epoll_wait 监听 -- 对应监听fd有事件产生 -- 返回 监听满足数组。 -- 判断返回数组元素 -- lfd满足 -- Accept -- cfd 满足 -- read() --- 小->大 -- cfd从监听红黑树上摘下 -- EPOLLOUT -- 回调函数 -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到红黑上监听写事件-- 等待 epoll_wait 返回 -- 说明 cfd 可写 -- write回去 -- cfd从监听红黑树上摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到红黑上监听读事件 -- epoll_wait 监听

  • eventset函数:

    设置回调函数。
    lfd --> acceptconn()

    cfd --> recvdata();

    cfd --> senddata();

  • eventadd函数:

    将一个fd, 添加到 监听红黑树。 设置监听 read事件,还是监听写事件。

  • 网络编程中:

    read --- recv()
    write --- send();