zoukankan      html  css  js  c++  java
  • Python IO多路复用

    epoll

    IO多路复用是一个系统层面的概念,让我们先搞清楚为什么使用IO多路复用:  

      由于进程的执行过程是线性的(也就是顺序执行),当我们调用低速系统I/O(read,write,accept等等),进程可能阻塞,此时进程就阻塞在这个调用上,不能执行其他操作.阻塞很正常. 接下来考虑这么一个问题:一个服务器进程和一个客户端进程通信,服务器端read(sockfd1,bud,bufsize),此时客户端进程没有发送数据,那么read(阻塞调用)将阻塞直到客户端调用write(sockfd,but,size)发来数据. 在一个客户和服务器通信时这没什么问题,当多个客户与服务器通信时,若服务器阻塞于其中一个客户sockfd1,当另一个客户的数据到达套接sockfd2时,服务器不能处理,仍然阻塞在read(sockfd1,...)上;此时问题就出现了,不能及时处理另一个客户的服务,咋么办?I/O多路复用来解决!
    IO多路复用本质上是在同一个线程或进程中,通过拨动开关的方式来执行多个IO操作。注意实际上每个IO操作都是独立进行的。只是由原来的一对一变成了多对多。如下图:

    select, poll, epoll 都是I/O多路复用的具体的实现,之所以有这三个鬼存在,其实是他们出现是有先后顺序的。

    I/O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。

    select 被实现以后,很快就暴露出了很多问题。
    • select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
    • select 如果任何一个sock(I/O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。
    • select 只能监视1024个链接, 这个跟草榴没啥关系哦,linux 定义在头文件中的,参见FD_SETSIZE。
    • select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现,尼玛,这个sock不用,要收回。对不起,这个select 不支持的,如果你丧心病狂的竟然关掉这个sock, select的标准行为是。。呃。。不可预测的, 这个可是写在文档中的哦.
    “If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
    霸不霸气

    于是14年以后(1997年)一帮人又实现了poll, poll 修复了select的很多问题,比如
    • poll 去掉了1024个链接的限制,于是要多少链接呢, 主人你开心就好。
    • poll 从设计上来说,不再修改传入数组,不过这个要看你的平台了,所以行走江湖,还是小心为妙。
    其实拖14年那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理1千多个链接简直就是神一样的存在了,select很长段时间已经满足需求。

    但是poll仍然不是线程安全的, 这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I/O流。你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。

    于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.

    epoll 可以说是I/O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:
    • epoll 现在是线程安全的。
    • epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。 

    比较坑爹的是epoll只用Linux提供支持,默认集成到了Linux内核中。

    Windows Python:
        提供: select
    Mac Python:
        提供: select
    Linux Python:
        提供: select、poll、epoll
    

      对于select操作:

    句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)
     
    参数: 可接受四个参数(前三个必须)
    返回值:三个列表
     
    select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
    1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
    2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
    3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
    4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
       当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
    

      

    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    
    import socket
    import select
    
    sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk1.bind(('127.0.0.1',8002))
    sk1.listen(5)
    sk1.setblocking(0)
    
    inputs = [sk1,]
    
    while True:
        readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1)
        for r in readable_list:
            # 当客户端第一次连接服务端时
            if sk1 == r:
                print 'accept'
                request, address = r.accept()
                request.setblocking(0)
                inputs.append(request)
            # 当客户端连接上服务端之后,再次发送数据时
            else:
                received = r.recv(1024)
                # 当正常接收客户端发送的数据时
                if received:
                    print 'received data:', received
                # 当客户端关闭程序时
                else:
                    inputs.remove(r)
    
    sk1.close()
    利用select实现伪并发 服务端
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import socket
    
    ip_port = ('127.0.0.1',8002)
    sk = socket.socket()
    sk.connect(ip_port)
    
    while True:
        inp = raw_input('please input:')
        sk.sendall(inp)
    sk.close()
    
    利用select实现伪同时处理多个Socket客户端请求:客户端
    利用select实现伪并发 客户端
  • 相关阅读:
    IOS-- UIView中的坐标转换
    iphone练习之手势识别(双击、捏、旋转、拖动、划动、长按)UITapGestureRecognizer
    Storm与Spark Streaming比较
    Python程序的常见错误(收集篇)
    Python画图笔记
    如何在论文中画出漂亮的插图?
    别老扯什么Hadoop了,你的数据根本不够大
    保险与互联网结合拉开序幕
    关于数学
    R--基本统计分析方法(包及函数)
  • 原文地址:https://www.cnblogs.com/ernest-zhang/p/5665341.html
Copyright © 2011-2022 走看看