zoukankan      html  css  js  c++  java
  • python之基础IO模型

    一、概述


     1.1 引入

      传统编程风格:按顺序执行代码块,它是线性的  开始-代码A---代码B--...--结束,为高效出现事件驱动

        def f():

          pass

         while 1:

          鼠标检测

          f()----会一直监听鼠标占用CPU,所以很占CPU,为减少CPU资源才用事件驱动

      事件驱动例如:

        <p onclick="func()">点我啊</p>-----点这个会触发某个事件函数,是js方式,js是事件驱动的方式,这步就是鼠标单击,

                        鼠标点一次前端内部就会有个队列来Put这个事件,来一个Put一个,处理就有线程用get,队列默认是先进先出

    1.2 什么是IO?

      1.内存分为用户空间与内核空间:用户空间内存与内核内存是分开的,用户空间不能用内核的空间

      2.进程上下文切换

      3.进程阻塞:进程阻塞动作是进程本身发用的阻塞,如套接字accept,这个阻塞会把CPU释放,等IO数据过来才能开始获取CPU权限

            阻塞的状态是不占CPU的,CPU会交出去

      4.文件描述符:是一个非负整数,是一个内核表里维护的一个套接字表,所以处理套接字就是处理fd(文件描述符)

                import socket
                print(socket.socket())-----socket socket fd=220 family=AddrassFamily type=....(其中fd是文件描述符)
                用户空间                         内核空间
                socket.socket()                   内核与用户空间用fd来交互
                     |                            用户的进程会问内核空间的fd数据有没有准备好
                    fd             

      5.过程描述

              客端数据------->用户内存--复制--内核内存(能使用网卡IO)   <>     (能使用网卡IO)内核内存->复制到--->用户内存--服务器 
              
              网络IO实例:一个网络IO涉及二个系统对象一个调用IO的process另一个就是系统内核,当发生read操作时,有二个过程
                           1.待数据准备(服等待客户端发数据)   2.将数据从内核拷到进程中(服拿到数据从内核态给用户态)

    二、IO的四种模型


            1.阻塞IO:---有阻塞
              sk.accept()---这个就是阻塞IO
              服务端(1.通过方法bind listen accept发系统调用 
                      2.conn.recv就开始等待数据(阻塞) 
                      3.当服端拿到数据后从内核给用户态conn.send才行(这个过程也在阻塞,所以二个过程都阻塞,把进程阻塞住了,数据不来不向下走)
          
              
            2.非阻塞IO----有阻塞
               服端:(1.进程不断的发system call,有数据就拿没数据就继续向下走,一会再来问,这个过程它有CPU权限因它要看数据用到CPU--这个过程会有延时,
                         每次来问时不一定能看到,下次再来看到的是上次的就是延时,这个频率是人为可控的
                       2.当第n次拿到数据后,需要从内核复核到用户态(这个过程是让系统把内核数据给用户态这个是阻塞的,没有CPU权限))
                       
            3.IO多路复用(事件驱动IO或同步):--有阻塞,是事件驱动IO(select与epoll都是多路复用的机制,nginx就是用epoll实现的,nginx就有很多连接过来能监听多个客端的文件描述符,进程与内核通信就是靠文件描述符)
                服端:1.select方法或epoll方法发起系统调用,kernel等待数据,当数据来了后(阻塞了等待数据进程不能用CPU了),会通知这个调用,这时就有CPU再发系统调用
                        让把内核数据给用户内存
                      2.再次发系统调用让内核把数据复制给用户态(以上二种方法只有一次系统调用)(也阻塞了等待数据进程不能用CPU了)
                      优点:解决并发问题,select或epoll可以连接多个文件描述符来实现并发,以上二种方法不能连接多个对象,一个服务器可有多个套接字同时被监听,
                            不同端口被不同客户端来连并能响应,只有客户端来连时才会有对象返回,对象返回后才会有数据传来
                            (第一步返回对象,也同conn,addr=sk.accept()做系统调用客户端连后才会有对象返回,第二步有数据返回)
            4.异步IO:--完全没阻塞,实现很难
                 服端:1.aio_read方法发起系统调用(发给内核,等待数据,内核拿到数据复核给用户,这时内核复制组用户后直接通知进程了,这二个过程中进程未阻塞)
                         整个过程没有阻塞,以三种内核不会主动通知,可自己去看

    三、IO多路复用(同步IO)创建使用


            1.select方法:如何实现事件驱动的
              监听多个文件描述符时,循环监听所有的文件描述符fd,不断的循环问哪个fd的数据准备好就会把数据复制到用户空间,这个缺点fd很多时不断的循环问费时费资源,而且它最多只能监听1024个文件描述符
                 
            2.poll方法:与select唯一的区别是,poll只解决了select的监听数据问题,能监听的更多但还是这样轮循。
            3.epoll方法:在poll与select的基础上解决了轮循问题,只要一个fd数据准备好,epoll就知道是谁直接把数据复制到用户空间就行,不需要一个个问是谁的数据好了
                         节省cpu的时间,这是多个fd,而对于每一个fd来说二段还是阻塞的
            实例非阻塞IO:
                server.py 
                   import socket 
                   sk=socket.socket()
                   sk.bind(('127.0.0.1',8000))
                   sk.listen(3)
                   sk.setblocking(False)----1.问下面的系统调用有没有数据,这个是不断的去问
                   while 1:
                     try:
                       conn,addr=sk.accept()---2.发系统调用,没有数据就报错,这个进程再发系统调用,这个是循环不断问
                       print(addr)
                       
                       data=conn.recv(1024)
                       print(data.decode('utf8'))
                       conn.sendall(data)
                       conn.close()
                    except Exception as e:
                       print('没有数据',e)--上步报错就不断打印这个提示
                       time.sleep(3)
                client.py
                   import socket 
                   sk1=socket.socket()
                   sk1.connect(('127.0.0.1',8000))
                   
                   while 1:
                       inp=input('>>>')--是unicon编码的
                       
                       sk1.sendall(hello.encode('utf8'))
                       data=sk1.recv(1024)
                       print(data.decode('utf8'))
    
            实例2:IO多路复用(通过select函数监听多个对象)----看视频
                   server.py
                   import socket
                   import select
                   sk1=socket.socket()
                   sk1.bind(('127.0.0.1',8080))
                   sk1.listen(3)
                   
                   sk2=socket.socket()
                   sk2.bind(('127.0.0.1',8081))
                   sk2.listen(3)
                   while 1:---要循环监听
                       r,w,e=select.select([sk1,sk2],[],5)----监听socket对象,返回值有三个,如果有客户端来连就会有对象返回存在r里,r就是客户端对象与IP+端口,所以r就是sk1或sk2
                
                      for obj in r:
                         conn,addr=obj.accept()----哪个客户端连就返回哪个客户端的conn,r是服端的socket对象,conn是客端的socket对象所以二个fd是不同的,r调用accept才能打印出conn对象,conn不是r分出来的而是它调用accept方法得到的
                         conn.send(hello.encode('utf8'))          
                   问题:当只有一个sk时,当客户端连上服后r=sk,当客户端第二次发数据时就进不了for了,因r没变,如何做到客户端数据变就向下走
                         select也监听conn时,当conn有变化时select就触发向下走,conn与sk都是对象,实例如下
                   
                   client.py
                   import socket
                   sk=socket.socket()
                   sk.connect(('127.0.0.1',8080))
                   while 1:
                       data = sk.recv(1024)
                       print(data.decode('utf8'))
                       inp=input('>>>')
                       sk.sendall(inp.encode('utf8'))
    
                为什么要能监听多个socket对象?
                IO多路复用的水平触发(某种状态一直触发,1或0),边缘触发(只有高低电平变化就触发,0变1或1变0),eg:select.select()---只要里面有数据就一直触发,所以select是水平触发,只有把数据用掉才不会触发
                   而epoll二种触发方式都有,数据一直在或数据变化都触发
                r,w,e=select.select([sk1,sk2],5):监听5s,超时时间 
            
            实例:
                while 1:
                    inputs,outputs,errors=selec.select([sk,conn],[],3)----监听后二种方式才能向下触发
                      当sk不变时不同客户端进来也会向下走,一个服可跟多少客户端对话
    
                对比阻塞IO与IO多路复用过程:
                    阻塞IO二个阻塞过程:1.等待数据是send与recv来等的  2.复制数据到用户也是这二个发的
                    IO多路复用:1.等待数据是select来等的  2.复制数据是accept来复制的,它做的是第二次系统调用,这里的数据是conn,只有select执行accept才能执行

      

  • 相关阅读:
    HDOJ 2689
    UVALive 3635 Pie 切糕大师 二分
    黑马程序员 Java基础<十八>---> 网路编程
    C# 数据库dataGridView刷新数据和主外键判断
    影视-纪录片:《生死洄游》
    汉语-词语:旅行
    汉语-词语:探险
    风水学:龙脉
    人物-探险家:斯文·赫定
    影视-纪录片:《河西走廊之嘉峪关》
  • 原文地址:https://www.cnblogs.com/Dana-xiong/p/14318084.html
Copyright © 2011-2022 走看看