6.Linux网络编程-epoll原理

2023-05-16

一:对比select发现epoll的有点
要比较epoll相比较select高效在什么地方,就需要比较二者做相同事情的方法。
要完成对I/O流的复用需要完成如下几个事情:
1.用户态怎么将文件句柄传递到内核态?
2.内核态怎么判断I/O流可读可写?
3.内核怎么通知监控者有I/O流可读可写?
4.监控者如何找到可读可写的I/O流并传递给用户态应用程序?
5.继续循环时监控者怎样重复上述步骤?
搞清楚上述的步骤也就能解开epoll高效的原因了。

select的做法:
步骤1的解法:select创建3个文件描述符集,并将这些文件描述符拷贝到内核中,这里限制了文件句柄的最大的数量为1024(注意是全部传入—第一次拷贝);
步骤2的解法:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作和select无关;
步骤3的解法:内核在检测到文件句柄可读/可写时就产生中断通知监控者select,select被内核触发之后,就返回可读可写的文件句柄的总数;
步骤4的解法:select会将之前传递给内核的文件句柄再次从内核传到用户态(第2次拷贝),select返回给用户态的只是可读可写的文件句柄总数,但却并不知道是哪些文件句柄需要IO操作,我们只能使用FD_ISSET宏函数来轮询所有文件句柄来找出它们;
步骤5的解法:select对于事件的监控是建立在内核的修改之上的,也就是说经过一次监控之后,内核会修改位,因此再次监控时需要再次从用户态向内核态进行拷贝(第N次拷贝);

epoll的做法:
步骤1的解法:首先执行epoll_create在内核专属于epoll的高速cache区,并在该缓冲区建立红黑树和就绪链表,用户态传入的文件句柄将被放到红黑树中(第一次拷贝)。
步骤2的解法:内核针对读缓冲区和写缓冲区来判断是否可读可写,这个动作与epoll无关;
步骤3的解法:epoll_ctl执行add动作时除了将文件句柄放到红黑树上之外,还向内核注册了该文件句柄的回调函数,内核在检测到某句柄可读可写时则调用该回调函数,回调函数将文件句柄放到就绪链表。
步骤4的解法:epoll_wait只监控就绪链表就可以,如果就绪链表有文件句柄,则表示该文件句柄可读可写,并返回到用户态(少量的拷贝);
步骤5的解法:由于内核不修改文件句柄的位,因此只需要在第一次传入就可以重复监控,直到使用epoll_ctl删除,否则不需要重新传入,因此无多次拷贝。

简单说:epoll是继承了select/poll的I/O复用的思想,并在二者的基础上从监控IO流、查找I/O事件等角度来提高效率,具体地说就是内核句柄列表、红黑树、就绪list链表来实现的。

二:理解水平触发和边缘触发
当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表, 最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了,所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。

====>区别就在于epoll_wait将socket返回到用户态时是否情况就绪链表。

二:epoll相关函数
头文件#include <sys/epoll.h>

epoll_create

int epoll_create(int size);	//在早期版本中使用链表实现,size标识表的大小,现在用epoll_create1函数代替;
int epoll_create1(int flags);
epoll_create1函数
说明:创建一个epoll的句柄;
@int :调用成功时返回一个epoll句柄描述符,失败时返回-1
@flags:可取0或者EPOLL_CLOEXEC
	0:如果这个参数是0,这个函数等价于poll_create(0);
	EPOLL_CLOEXEC:这是这个参数唯一的有效值,如果这个参数设置为这个。那么当进程替换映像的时候会关闭这个文件描述符,

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl函数
说明:创建一个epoll的句柄;
@int :调用成功时返回0,失败时返回-1errno被设置
@epfd:epoll_create1的返回的文件句柄
@op:操作,EPOLL_CTL_ADD或者EPOLL_CTL_MOD或者EPOLL_CTL_DEL
	EPOLL_CTL_ADD:向多路复用实例加入一个连接socket的文件描述符
	EPOLL_CTL_MOD:改变多路复用实例中的一个socket的文件描述符的触发事件
	EPOLL_CTL_DEL:移除多路复用实例中的一个socket的文件描述符
@fd:要操作的socket的文件描述符
@event:类型为struct epoll_event,设置对象socket的预期和返回结果事件,
typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
} epoll_data_t;

struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
};   
events可以是下列命令的任意按位与
EPOLLIN: 对应的文件描述有可以读取的内容
EPOLLOUT:对应的文件描述符有可以写入
EPOLLRDHUP:写到一半的时候连接断开
EPOLLPRI:发生异常情况,比如所tcp连接中收到了带外消息
EPOLLET: 设置多路复用实例的文件描述符的事件触发机制为边沿触发,默认为水平触发

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll_wait函数
说明:等待一个epoll队列中的文件描述符的I/O事件发生;
@int :调用成功时返回>=0表示准备就绪的文件描述符个数,-1表示出错errno被设置
@epfd:epoll_create1的返回的文件句柄
@events:用于放置epoll队列中准备就绪(被触发)的事件
@maxevents:@events链表的大小
@timeout:指定函数等待的时间,这个函数阻塞这么长一段时间之后接触阻塞。

三:小例子

//利用epoll实现反射服务器
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <vector>
#include <sys/epoll.h>
#include <sys/ioctl.h>
#include <algorithm>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while (0)

#define MAXLINE 1024
#define SERV_PORT 50000
#define CLIENT_NUM 1000

int main()
{
	int i, maxi, listenfd, connfd, sockfd;
	int nready;

	ssize_t n;
	char buf[MAXLINE];
	socklen_t clilen;
	struct sockaddr_in cliaddr, servaddr;

	//1.创建套接字
	if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
		ERR_EXIT("socket error"); //调用上边的宏

	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	//2.设置套接字属性
	int on = 1;
	if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
		ERR_EXIT("setsockopt error");

	//3.绑定
	if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
		ERR_EXIT("bind error");

	//4.监听
	if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前
		ERR_EXIT("listen error");

	std::vector<int> clients;
	int epollfd = epoll_create1(EPOLL_CLOEXEC);
	if (epollfd < 0)
		ERR_EXIT("epoll_create1 error");

	struct epoll_event event;
	event.data.fd = listenfd;
	event.events = EPOLLIN | EPOLLET;

	if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &event) < 0)
		ERR_EXIT("epoll_ctl error");

	std::vector<struct epoll_event> events(16);
	struct sockaddr_in peeraddr;
	socklen_t peerlen;
	int conn;
	int count = 0;
	char recvbuf[1024] = {0};

	while (1)
	{
		nready = epoll_wait(epollfd, &*events.begin(), static_cast<int>(events.size()), -1);
		if (nready == -1)
		{
			if (errno == EINTR)
				continue;
			ERR_EXIT("epoll_wait error");
		}
		else if (nready == 0)
			continue;
		else if ((size_t)nready == events.size())
		{
			events.resize(events.size()*2);
		}

		for (int i = 0; i<nready; i++)
		{
			if (events[i].data.fd == listenfd)
			{
				peerlen = sizeof(peeraddr);
				conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);
				if (conn == -1)
					ERR_EXIT("accept error");
				printf("ip=%s, port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
				printf("count=%d\n", ++count);
				clients.push_back(conn);
				int nb;
				if (ioctl(conn, FIONBIO, &nb) == -1)
					ERR_EXIT("ioctl error");
				event.data.fd = conn;
				event.events = EPOLLIN | EPOLLET;
				epoll_ctl(epollfd, EPOLL_CTL_ADD, conn, &event);
			}
			else if (events[i].events & EPOLLIN)
			{
				conn = events[i].data.fd;
				if (conn < 0)
					continue;
				int ret = read(conn, recvbuf, 1024);
				if (ret < 0)
					ERR_EXIT("read error");
				else if (ret == 0)
				{
					printf("client close\n");
					close(conn);
					event = events[i];
					epoll_ctl(epollfd, EPOLL_CTL_DEL, conn, &event);
					clients.erase(std::remove(clients.begin(), clients.end(), conn), clients.end());
				}

				fputs(recvbuf, stdout);
				write(conn, recvbuf, strlen(recvbuf));
				memset(recvbuf, 0, sizeof(recvbuf));
			}
		}
	}

	return 0;
}

//客户端程序
//用于测试服务端支持的最大数量:1019;
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<stdlib.h>
#include<errno.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<poll.h>

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while (0)

#define MAXLINE 1024
#define SERV_PORT 50000
#define CLIENT_NUM 1000

int main(void)
{
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int clients[CLIENT_NUM];
    struct pollfd p_fds[CLIENT_NUM+1];
    for (int i=0; i<CLIENT_NUM ;i++)
    {
    	int ret = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    	connect(ret, (struct sockaddr *)&servaddr, sizeof(servaddr));
    	clients[i] = ret;
    	p_fds[i+1].fd = ret;
    	p_fds[i+1].events = POLLIN;
    	p_fds[i+1].revents = 0;
    }

    int fd_stdin = fileno(stdin);
	p_fds[0].fd = STDIN_FILENO;
	p_fds[0].events = POLLIN;
	p_fds[0].revents = 0;

    int nready;
    char sendbuf[1024] = {0};
    char recvbuf[1024] = {0};

    while (true)
    {
		nready = poll(p_fds, CLIENT_NUM, -1);
        if(nready < 0)
            ERR_EXIT("poll");
        else if(nready == 0)
        {
            printf("timeout\n");
            continue;
        }
        else
        {
			if (p_fds[0].revents&POLLIN)
			{
				if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)
					break;
	        	for (int i=0; i<CLIENT_NUM ;i++)
	        	{
					write(clients[i], sendbuf, strlen(sendbuf));
	        	}
	        	memset(sendbuf, 0, sizeof(sendbuf));
			}

        	for (int i=1; i<=CLIENT_NUM ;i++)
        	{
            	if (p_fds[i].revents&POLLIN)
    			{
    				int ret = read(p_fds[i].fd, recvbuf, sizeof(recvbuf));
    				if (ret == -1)
    					ERR_EXIT("read error");
    				else if (ret  == 0 || errno == ECONNRESET)   //服务器关闭
    				{
    					p_fds[i].fd = -1;
    					break;
    				}

    				fputs(recvbuf, stdout);
    				memset(recvbuf, 0, sizeof(recvbuf));
    			}
        	}
        }
    }

    for (int i=0; i<CLIENT_NUM ;i++)
    {
    	close(clients[i]);
    }
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

6.Linux网络编程-epoll原理 的相关文章

  • Java GUI小程序--画板

    画板 一个可以绘画的简单绘图软件 本文用两个类来实现画板的基本功能 xff08 源代码在文章最后面 xff09 画板制作分两个部分 xff1a xff08 一 xff09 界面布局 xff08 二 xff09 实现功能 小知识 xff1a
  • Java_关于垃圾回收机制(GC)的六个知识

    1 垃圾回收机制 xff1a 垃圾回收是一种动态存储管理技术 xff0c 它自动地释放不再被程序引用的对象 xff0c 按照特定的垃圾收集算法来实现资源自动回收的功能 当一个对象不再被引用的时候 xff0c 内存回收它占领的空间 xff0c
  • 树莓派 Nginx+php7搭建网站

    1 6 安装Nginx 43 php7 并且启动 sudo apt get update sudo apt get install nginx sudo apt get install php7 0 fpm php7 0 cli php7
  • Python的GUI编程(一)Label(标签)

    常用Python GUI库有 xff1a 1 Tkinter 2 WxPython 3 PyQT 4 pyGtk 5 Jython 6 MFC 7 PythonCard 8 Dabo 9 AnyGui 10 WPY 11 IronPytho
  • 使用 Drools 规则引擎实现业务逻辑

    要求施加在当今软件产品上的大多数复杂性是行为和功能方面的 xff0c 从而导致组件实现具有复杂的业务逻辑 实现 J2EE 或 J2SE 应用程序中业务逻辑最常见的方法是编写 Java 代码来实现需求文档的规则和逻辑 在大多数情况下 xff0
  • Python的GUI编程(九)Menu(菜单)OptionMenu(为可选菜单)

    在用户界面程序中 菜单以图标和文字的方式展示可用选项 用鼠标选择一个选项 程序的某个行为既被触发 这种行为通常包括比如 打开 保存文件 退出程序 等功能 上下文菜单是一种根据用户当前所在程序位置 上下文 动态生成的菜单 简单程序 xff1a
  • Linux同时使用无线和有线网络

    有时候我们可能同时插入了无线网卡和有线网络 xff0c 但是默认是通过有线网络连接的外网 如果同时需要使用无线网络连接外网以及使用有线网络进行高速局域网连接的话需要将默认网关设置成无线网络 xff1a 使用ip route show分别查看
  • 【Python】计算两个日期相差天数

    Python 计算两个日期相差天数
  • 《Zoom to learn Learn to zoom》学习笔记

    创新点 xff1a 使用真实原始LR和HR图像作为数据进行训练网络 xff0c 区别于其他大部分网络 xff08 用HR图和经过HR图进行下采样的LR图作为网络数据进行训练 xff09 采用由CX loss 改进的CoBi loss作为网络
  • cmake安装以及更新

    直接install cmake wget https cmake org files v3 6 cmake 3 6 2 tar gz span class token function tar span zxvf cmake 3 6 2 t
  • manjaro安装以及配置、安装输入法、向日葵、anaconda、pycharm、QQ

    为什么不用Ubuntu xff0c 用manjaro就不说了 xff0c 直接上步骤吧 xff01 1 做个优盘 找个优盘 xff0c 最好是3 0接口吧 xff0c 我用的2 0有点慢 先下载系统 xff0c 找个自己喜欢的风格 Manj
  • Python编程中的常见语句

    4 1 if条件判断语句 4 1 1 if条件判断语句单分支 单分支格式 xff1a if 判断条件 xff1a 语句块1 else xff1a 语句块2 例 xff1a name 61 input 39 请输入您的用户名 39 if na
  • Linux下创建新用户及用户权限

    一 用户创建 增加用户 1 在root权限下 xff1b 命令 xff1a useradd 43 用户名 xff0c 它不会在 home目录下创建同名文件夹 xff0c 也没有创建密码 xff0c 因此利用这个用户登录系统 xff0c 是登
  • cacert.pem是怎么来的

    小弟最近在搞支付宝支付接口 xff0c 碰到个问题 xff0c help 我看demo中有下面一行代码 xff1a PHP code 1 2 3 ca证书路径地址 xff0c 用于curl中ssl校验 请保证cacert pem文件在当前文
  • mapping文件的编写

    mapping文件的编写 xff08 以及实体类与xml中类型的对应关系 xff09 2017年02月18日 11 31 36 转角人生 阅读数 xff1a 2918 标签 xff1a mapping文件 更多 个人分类 xff1a map
  • java-兔子繁殖问题

    題目 xff1a 古典问题 xff1a 有一对兔子 xff0c 从出生后第3个月起每个月都生一对兔子 xff0c 小兔子长到第三个月后每个月又生一 对兔子 xff0c 假如兔子都不死 xff0c 问每个月的兔子总数为多少 xff1f 64
  • Nginx服务器的安装部署和框架简介

    Nginx服务器的安装部署 1 如何获取Nginx服务器安装文件 Nginx服务器的软件版本包括 Windows版 和 Linux版俩种 官网下载地址为http nginx org en download html 网页上提供了Nginx服
  • error - problem conecting

    解决的办法是在树莓派里安装如下模块 xff1a sudo apt get install tightvncserver 接着reboot重启 xff0c 重新连接即可
  • SQL 日期函数应用实例!!

    sql view plain copy select convert varchar 10 getdate 120 只返回当前日期 xff0c 且为2012 12 12格式 xff08 最有用 xff09 sql view plain co
  • 使用docker-compose搭建Harbor私有仓库

    一 安装docker compose 1 下载docker compose的最新版本 curl L 34 https github com docker compose releases download 1 22 0 docker com

随机推荐

  • Zabbix安装部署

    一 服务端安装配置 1 环境检查 root 64 m01 cat etc redhat release CentOS Linux release 7 4 1708 Core root 64 m01 uname r 3 10 0 693 el
  • Docker安装部署

    一 安装 devicemapper 存储驱动 使用说明 xff1a 为了在生产级别的环境中使用 docker 运行环境 xff0c 必须使用 direct lvm 模式来运行devicemapper 存储驱动 这种模式使用块设备来创建 th
  • MySql安装部署

    下载并安装MySQL官方的 Yum Repository wget i c http dev mysql com get mysql57 community release el7 10 noarch rpm 使用上面的命令就直接下载了安装
  • Nginx安装部署

    Nginx 安装配置 Nginx 34 engine x 34 是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器 xff0c 也是一个 IMAP POP3 SMTP 代理服务器 在高连接并发的情况
  • Rancher安装部署

    直接通过docker镜像来运行我们的rancher xff0c 首先 xff0c 先从镜像中心下载rancher镜像 xff0c 如果是1 x系列的 xff0c 镜像名为rancher server xff0c 而2 x是rancher r
  • form表单提交onclick和onsubmit进行表单验证

    onsubmit只能表单上使用 提交表单前会触发 onclick是按钮等控件使用 用来触发点击事件 在提交表单前 xff0c 一般都会进行数据验证 xff0c 可以选择在submit按钮上的onclick中验证 也可以在onsubmit中验
  • iperf3网络测试工具

    一 iperf能用来做什么 测量网络带宽和网络质量提供网络延迟抖动 数据包丢失率 最大传输单元等统计信息 二 iperf3主要功能介绍 TCP 测试网络带宽支持多线程 xff0c 在客户端与服务端支持多重连接报告MSS MTU值的大小支持T
  • C++多线程5-单例模式详解

    单例模式 xff1a 只允许创建一个类对象 xff0c 实现的关键是将构造函数变为私有 单例模式有几种实现方式 xff1a 懒汉模式饿汉模式线程安全模式 锁实现和call once实现 局部静态变量模式 1 懒汉模式 当需要使用类对象时 x
  • c++多线程1-多线程的创建

    什么是多线程 xff1f 我们可以理解为一个线程执行一个代码段 xff0c 所以多个线程就是执行多个代码段 xff0c 如果当一个线程结束后 xff0c 进程就退出了 xff0c 这个线程我们称之为主线程 每个进程可以有一个或一个以上的线程
  • c++多线程2-线程参数传递需要注意的几个问题

    一 线程的初始化参数需要注意以下几个问题 xff1a 1 回调函数使用引用参数接收值时 xff0c 必须声明为const xff0c 否则报错 xff1b xff08 线程基于数据安全保护的考虑 xff09 2 回调函数必须声明为指针 xf
  • c++11-智能指针

    c 43 43 智能指针 为了更安全地管理动态内存 xff0c c 43 43 11引入了智能指针 xff0c 提供了包括shared ptr unique ptr weak ptr三种不同类型的智能指针 目录结构 xff1a 一 三种指针
  • C++多线程3-共享数据操作保护

    目录 xff1a 1 多线程操作共享数据引出的问题 2 解决多线程操作共享数据的方法 xff1a 锁 3 互斥量mutex的概念和用法 4 lock普通锁的用法 5 lock guard类模板的用法 6 死锁的概念和解决 7 unique
  • C++多线程4-unique_lock详解

    unique lock和lock guard都是可以自动解锁的类 xff0c 但是lock guard更加高效体现在永远在析构函数中解锁 xff0c 而unique lock更加灵活 xff0c 但执行效率会比lock guard低一些 x
  • C++多线程6-条件变量

    1 条件变量 std condition variable是多线程中经常用到的一个类 xff0c 它的头文件为condition variable 它常用的成员函数包括 xff0c wait notify one notify all等 它
  • Centos6.5系统升级软件操作文档

    为什么要用Centos6 5系统 xff1f 答 xff1a 因为计算板官方推荐Centos6 5 为什么要升级软件 xff1f 答 xff1a 软件需支持C 43 43 11相关库 1 系统信息 系统版本 xff1a CentOS 6 5
  • c++11多线程7-异步线程

    异步线程 异步线程的引入解决了线程有依赖关系的情景 c 43 43 11提供了std async xff0c std packaged task xff0c std promise xff0c 三种方法 1 std async std as
  • HTML中meta标签如何正确使用

    HTML中 lt meta gt 标签如何正确使用 如果我们在浏览器中按下F12或者Ctrl 43 shift 43 J xff0c 便可以打开开发者工具 xff0c 在element中即可看到 lt head gt 元素中有不少 lt m
  • 4.Linux网络编程-select和poll模型

    目录 xff1a 1 补充知识 2 简易版回射服务器的实现 3 select模型实现 4 poll模型实现 1 补充知识 span class token comment 显示进程的pid xff1a span span class tok
  • 5.Linux网络编程-select实现超时API

    一 alarm函数设置超时 它的主要功能是设置信号传送闹钟 信号SIGALRM在经过seconds指定的秒数后传送给目前的进程 xff0c 如果在定时未完成的时间内再次调用了alarm函数 xff0c 则后一次定时器设置将覆盖前面的设置 x
  • 6.Linux网络编程-epoll原理

    一 xff1a 对比select发现epoll的有点 要比较epoll相比较select高效在什么地方 xff0c 就需要比较二者做相同事情的方法 要完成对I O流的复用需要完成如下几个事情 xff1a 1 用户态怎么将文件句柄传递到内核态