epoll模型的探索与实践

前言

我们知道nginx的效率非常高,能处理上万级的并发,其之所以高效离不开epoll的支持,

epoll是什么呢?,epoll是IO模型中的一种,属于多路复用IO模型;

到这里你应该想到了,select,的确select也是一种多路复用的IO模型,但是其单个select最多只能同时处理1024个socket,效率实在算不上高,这时候epoll来救场了

本文从阻塞IO模型的基础上展开讨论,一步步靠近epoll的实现原理,最后以一个简单的epoll案例程序作为结束

亲手写一个epoll,然后去虐面试官吧!

在select的学习过程中我们知道了select 只能同时处理1024个客户端,

而多线程会遇到资源瓶颈,什么才是解决高并发最有效的方式呢

linux中提供了epoll 这种高效的多路复用IO模型

注意其他平台没有相应的实现所以epoll仅在linux中可用

程序阻塞过程分析

epoll代码实现并不复杂,但是要搞清楚其高效的原理还是需要花一些时间的

我们从最原始的阻塞模型开始分析

假设系统目前运行了三个进程 A B C

进程A正在运行一下socket程序

server = socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()
server.accept()

1.系统会创建文件描述符指向一个socket对象 ,其包含了读写缓冲区,已经进行等待队列

2.当执行到accept / recv 时系统会讲进程A 从工作队列中移除

3.将进程A的引用添加到 socket对象的等待队列中

进程的唤醒

1.当网卡收到数据后会现将数据写入到缓冲区

2.发送中断信号给CPU

3.CPU执行中断程序,将数据从内核copy到socket的缓冲区

4.唤醒进程,即将进程A切换到就绪态,同时从socket的等待队列中移除这个进程引用

select监控多个socket

select的实现思路比较直接

1.先将所有socket放到一个列表中,

2.遍历这个列表将进程A 添加到每个socket的等待队列中 然后阻塞进程

3.当数据到达时,cpu执行中断程序将数据copy给socket 同时唤醒处于等待队列中的进程A

为了防止重复添加等待队列 还需要移除已经存在的进程A

4.进程A唤醒后 由于不清楚那个socket有数据,所以需要遍历一遍所有socket列表

从上面的过程中不难看出

1.select,需要遍历socket列表,频繁的对等待队列进行添加移除操作,

2.数据到达后还需要给变量所有socket才能获知哪些socket有数据

两个操作消耗的时间随着要监控的socket的数量增加而大大增加,

处于效率考虑才规定了最大只能监视1024个socket

epol要解决的问题

1.避免频繁的对等待队列进行操作
2.避免遍历所有socket
对于第一个问题我们先看select的处理方式
while True:
    r_list,w_list,x_list = select.select(rlist,wlist,xlist)
  

每次处理完一次读写后,都需要将所有过冲重复一遍,包括移除进程,添加进程,默认就会将进程添加到等待队列,并阻塞住进程,然而等待队列的更新操作并不频繁,

所以对于第一个问题epoll采取的方案是,将对等待队列的维护和,阻塞进程这两个操作进行拆分,

相关代码如下

import socket,select
server = socket.socket()
server.bind(("127.0.0.1",1688))
server.listen(5)

#创建epoll事件对象,后续要监控的事件添加到其中
epoll = select.epoll()
#注册服务器监听fd到等待读事件集合
epoll.register(server.fileno(), select.EPOLLIN)

# 等待事件发生
while True:
    for sock,event in epoll.poll():
    pass

在epoll中register 与 unregister函数用于维护等待队列

epoll.poll则用于阻塞进程

这样一来就避免了 每次处理都需要重新操作等待队列的问题

第二个问题是select中进程无法获知哪些socket是有数据的所以需要遍历

epol为了解决这个问题,在内核中维护了一个就绪列表,

1.创建epoll对象,epoll也会对应一个文件,由文件系统管理

2.执行register时,将epoll对象 添加到socket的等待队列中

3.数据到达后,CPU执行中断程序,将数据copy给socket

4.在epoll中,中断程序接下来会执行epoll对象中的回调函数,传入就绪的socket对象

5.将socket,添加到就绪列表中

6.唤醒epoll等待队列中的进程,

进程唤醒后,由于存在就绪列表,所以不需要再遍历socket了,直接处理就绪列表即可

解决了这两个问题后,并发量得到大幅度提升,最大可同时维护上万级别的socket

epoll相关函数

import select 导入select模块

epoll = select.epoll() 创建一个epoll对象

epoll.register(文件句柄,事件类型) 注册要监控的文件句柄和事件

事件类型:

  select.EPOLLIN    可读事件

  select.EPOLLOUT   可写事件

  select.EPOLLERR   错误事件

  select.EPOLLHUP   客户端断开事件

epoll.unregister(文件句柄)   销毁文件句柄

epoll.poll(timeout)  当文件句柄发生变化,则会以列表的形式主动报告给用户进程,timeout

                     为超时时间,默认为-1,即一直等待直到文件句柄发生变化,如果指定为1

                     那么epoll每1秒汇报一次当前文件句柄的变化情况,如果无变化则返回空

epoll.fileno() 返回epoll的控制文件描述符(Return the epoll control file descriptor)

epoll.modfiy(fineno,event) fineno为文件描述符 event为事件类型  作用是修改文件描述符所对应的事件

epoll.fromfd(fileno) 从1个指定的文件描述符创建1个epoll对象

epoll.close()   关闭epoll对象的控制文件描述符

案例:

#coding:utf-8
#客户端
#创建客户端socket对象
import socket
clientsocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#服务端IP地址和端口号元组
server_address = ('127.0.0.1',1688)
#客户端连接指定的IP地址和端口号
clientsocket.connect(server_address)

while True:
    #输入数据
    data = raw_input('please input:')
    if data == "q":
        break
    if not data:
      continue
    #客户端发送数据
    clientsocket.send(data.encode("utf-8"))
    #客户端接收数据
    server_data = clientsocket.recv(1024)
    print ('客户端收到的数据:',server_data)
#关闭客户端socket
clientsocket.close()

服务器:

# coding:utf-8
import socket, select

server = socket.socket()
server.bind(("127.0.0.1", 1688))
server.listen(5)

msgs = []

fd_socket = {server.fileno(): server}
epoll = select.epoll()
# 注册服务器的 写就绪
epoll.register(server.fileno(), select.EPOLLIN)

while True:
    for fd, event in epoll.poll():
        sock = fd_socket[fd]
        print(fd, event)
        # 返回的是文件描述符 需要获取对应socket
        if sock == server:  # 如果是服务器 就接受请求
            client, addr = server.accept()
            # 注册客户端写就绪
            epoll.register(client.fileno(), select.EPOLLIN)
            # 添加对应关系
            fd_socket[client.fileno()] = client

        # 读就绪
        elif event == select.EPOLLIN:
            data = sock.recv(2018)
            if not data:
                # 注销事件
                epoll.unregister(fd)
                # 关闭socket
                sock.close()
                # 删除socket对应关系
                del fd_socket[fd]
                print(" somebody fuck out...")
                continue

            print(data.decode("utf-8"))
            # 读完数据 需要把数据发回去所以接下来更改为写就绪=事件
            epoll.modify(fd, select.EPOLLOUT)
            #记录数据
            msgs.append((sock,data.upper()))
        elif event == select.EPOLLOUT:
            for item in msgs[:]:
                if item[0] == sock:
                    sock.send(item[1])
                    msgs.remove(item)
            # 切换关注事件为写就绪
            epoll.modify(fd,select.EPOLLIN)

注意:上述代码只能在linux下运行,因为epoll模型是linux内核提供的,上层代码无法实现!

原文地址:https://www.cnblogs.com/yangyuanhu/p/11152914.html

时间: 2024-08-14 08:12:48

epoll模型的探索与实践的相关文章

Linux Epoll模型(1) --理论与实践

引言: 相比于select,Epoll最大的好处在于它不会随着监听fd数目的增长而降低效率.因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多.并且,在linux/posix_types.h头文件有这样的声明: #define __FD_SETSIZE    1024 表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎治标不治本. 常用模型的特点 Linux 下设计并发网络程序,有典型的Apache模型(

工业自动化软件产业发展的探索与实践

中国自动化产业已经走过了五十年的历程.进入21世纪以来,自动化已经成为我国制造业实现可持续发展的重要支撑与保证.在"满足用户需求,利用技术推动"的前提下,我国自动化产业正在不断出现新的可喜的变化.其主要特征是,产品实现数字化.智能化.网络化与综合集成化,并在性能上向着高精度.高可靠性.高适应性方向发展.随着自动化行业的发展,工业控制软件逐渐成为自动化行业发展的趋势和主流. 一. 工业控制软件的特点 工业控制软件除具有软件的性质外,还具有鲜明的行业特色,随着自动化产业的不断发展,通过不断

微服务探索与实践—总述

背景 软件开发是一个不断发展的过程,从当初的面向过程为主到如今的面向对象的开发,软件开发者不断探索与实践更加符合时代发展要求的开发模式与架构思想,而这,也在极大程度上提高了软件开发的效率. 微服务是一种架构模式或者说是架构风格,而架构这个词语,相信有很多人都曾试图为它做出明确的定义,可是很难下,因为软件架构也在不断发展,内涵也在不断得到丰富.只是不变的是,我们需要通过软件架构,根据族组织.业务.技术等因素划分出不同的但可以相互协作的应用系统,使得设计出来的系统具有较高的伸缩性.可维护性以及可扩展

高德在提升定位精度方面的探索和实践

2019杭州云栖大会上,高德地图技术团队向与会者分享了包括视觉与机器智能.路线规划.场景化/精细化定位时空数据应用.亿级流量架构演进等多个出行技术领域的热门话题.现场火爆,听众反响强烈.我们把其中的优秀演讲内容整理成文并陆续发布出来,本文为其中一篇. 阿里巴巴高级地图技术专家方兴在高德技术专场做了题为<向场景化.精细化演进的定位技术>的演讲,主要分享了高德在提升定位精度方面的探索和实践,本文根据现场内容整理而成(在不影响原意的情况下对文字略作编辑),更多定位技术的实现细节请关注后续系列文章.

运维管理中IT故障定位、预警与智能恢复模型建立和应用实践解密

运维管理中IT故障定位.预警与智能恢复模型建立和应用实践解密各位大伽先不要说我理的对不对,我们来说用网管软件与IT运维管理系统来做IT的监测管理,先来看下面以SITEVIEW  ITOSS为例的一张图,图最能说明模型的意图:  我们可以从左向右,从上向下来看一下,一开始是需要采集,也即监测源端,监测源包括比如关键的服务器.网络设备.网络.日志和核心的业务应用系统,IT的环境.数据中心.机房环境等等.监测的参数就看如图SITEVIEW ITOSS一体化平台包括的五大模块功能中需要的参数状态数据,这

第九章 用多线程来读取epoll模型下的客户端数据

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include

Apache select和Nginx epoll模型区别

部分内容摘自跟老男孩学Linux运维:Web集群实战(运维人员必备书籍) http://oldboy.blog.51cto.com/2561410/1752270 1.select 和epoll模型区别 1.1.网络IO模型概述 通常来说,网络IO可以抽象成用户态和内核态之间的数据交换.一次网络数据读取操作(read),可以拆分成两个步骤:1)网卡驱动等待数据准备好(内核态)2)将数据从内核空间拷贝到进程空间(用户态).根据这两个步骤处理方式不一样,我们通常把网络IO划分成阻塞IO和非阻塞IO.

0729------Linux网络编程----------使用 select 、poll 和 epoll 模型 编写客户端程序

1.select 模型 1.1 select 函数原型如下,其中 nfds 表示的描述符的最大值加1(因为这里是左闭右开区间),中间三个参数分别表示要监听的不同类型描述符的集合,timeout用来表示轮询的时间间隔,这里用NULL表示无限等待. 1.2 使用 select函数编写客户端程序的一般步骤: a)初始化参数,包括初始化监听集合read_set并添加fd,以及初始化监听的最大描述符 maxfd 和select的返回值 nready: b)将read_set 赋值给 ready_set,因

epoll模型实例

一.epoll概述 epoll是linux下的一个系统调用,用来监听大量文件描述符并对其上的触发事件进行处理.它是select/poll的增强版本,也是linux下多路复用io最常用的接口.要理解epoll是什么,首先得清楚什么是多路复用io.用户进行io操作需要经过内核,而如果所请求的io目前不满足条件(如需要从标准输入读取数据,而用户还未输入),这个时候内核就会让应用程序陷入等待,即阻塞状态.个人理解,io复用技术就是通过特定接口,将这种阻塞进行了转移,转嫁到了如select/poll/ep