网络编程之IO模型

1.1 IO模型介绍

 IO多路复用概念

  IO发生时涉及的对象和步骤。对于一个网络IO,它会涉及到两个系统对象,一个是调用IO的进程或者线程,另一个就是系统内核。

如当一个read操作发生时,会先等待数据准备,然后将数据从内核拷贝到进程中去

   阻塞IO(blocking IO)特点:在执行IO的两个阶段(等待数据和拷贝数据两个阶段)都被阻塞了

  实际上几乎所有的IO借口(包括socket端口)都是阻塞型的。如在调用recv(1024)的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

  解决方案:在服务端使用多线程(或者多进程),目的是让每个连接都拥有独立的线程(或者进程)。但是一旦客户端同一时间的连接请求过多的话,则会严重占用系统资源,降低系统对外界的响应效率。

  那么该如何解决呢?最好的办法就是使用‘线程池’或者‘连接池’。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种办法可以很好地降低系统开销,适用于大型系统。   

  上述方案只是一定程度缓解了频繁调用IO接口带来的资源占用,‘池’终究是有上限的,当响应规模过大时,必须考虑调整池的大小。

用解决该问题。可以使用非阻塞接口来解决

1.2 非阻塞IO模型(nonblocking IO)

多线程,多进程,进程池,线程池都可以实现并发,但是仍然没有解决IO问题,先看一下IO非阻塞模型(linux下)

  从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情,或者直接再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存(这一阶段仍然是阻塞的),然后返回。

    也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

    所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

相关参数

server.setblocking() # 默认是True  

server.setblocking(False)  # False的话就成非阻塞了,这只是对于socket套接字来说的
 # 所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问内核数据准备好了没有。

wait data # 等数据,这个阶段是不阻塞的 

copy data # 这个阶段还是要阻塞的

server端

import socket

sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.setblocking(False)
sk.listen()
conn_lst = []
del_lst = []
while True:
    try:
        conn,addr = sk.accept()   # 非阻塞的模型
        print(conn,addr)
        conn_lst.append(conn)
    except BlockingIOError as e:
        for conn in conn_lst:  #[conn1,conn2,conn3]
            try:
                msg= conn.recv(1024)  # 非阻塞
                if not msg:  # 判定是否为空
                    conn.close()
                    del_lst.append(conn)
                    continue
                print(msg)
                msg = msg.decode('utf-8').upper()
                conn.send(msg.encode('utf-8'))
            except BlockingIOError:pass   # 阻塞IO报错,直接pass
        for conn in del_lst:
            conn_lst.remove(conn)
        del_lst.clear()  

 client端

import time
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',9000))
print(sk)
for i in range(20):
    sk.send(b'hello')
    print(sk.recv(1024))
    time.sleep(1)
sk.close()

非阻塞IO模型优点

  能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在“”同时“”执行)。

非阻塞IO模型缺点:

  1. 循环调用recv()将大幅度推高CPU占用率

  2. 任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。

1.3 多路复用(IO multiplexing)  

  当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

操作系统中的IO多路复用的机制select:

  windows操作系统提供给你的一种监听接收数据IO的一个代理

select模块:

  python使用操作系统select机制的功能

server端:

import socket
import select
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.setblocking(False)
sk.listen()

l = [sk]                         # sk.accept
while True:                     
    r,w,x = select.select(l,[],[])   # 阻塞
    for obj in r:
        if obj is sk:
            conn,addr = obj.accept()
            l.append(conn)                   # conn.recv
        else:
            msg = obj.recv(1024)
            if not msg:
                obj.close()
                l.remove(obj)
                continue
            print(msg)
            obj.send(b'bye')

  client端:

import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
while True:
    sk.send(b'hello')
    print(sk.recv(1024))
sk.close()

 该模型优点:

  相比其他模型,使用select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

该模型缺点:

  首先select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。

对于IO多路复用(select)
select检测的是哪个套接字准备好了(检测的时候等待了,变成阻塞了)

select之所以比阻塞IO好,就是因为select可以检测多个套接字
多个链接下select才能发挥它的优势
但是你的套接字特别多,你怎么知道哪个好了呢,那么就得用循环去遍历一下
那么如果特别多的时候,效率也就不咋高了

epoll:只支持linux系统(就是为了解决select效率低的问题)
epoll比pool,select效率高

selectors 更好用,解决了上面select,epoll,pool的问题

socketserver用这个模块IO问题也解决了,实现并发也解决了(见链接文章最底部实例

1.4异步IO:

  参考链接:http://www.cnblogs.com/Eva-J/articles/8324837.html

  

 

原文地址:https://www.cnblogs.com/LearningOnline/p/9054397.html