非阻塞套接字与IO多路复用

一、基本IO模型

1、数据流的概念

python流量控制 python数据流_python


数据流(data stream)是一组有序,有起点和终点的字节的数据序列,是只能被读取一次或少数几次的点的有序序列。其包括输入流和输出流。

数据流分为输入流(InputStream)输出流(OutputStream)两类。输入流只能读不能写,而输出流只能写不能读。通常程序中使用输入流读出数据,输出流写入数据,就好像数据流入到程序并从程序中流出。采用数据流使程序的输入输出操作独立于相关设备。

输入流可从键盘或文件中获得数据,输出流可向显示器、打印机或文件中传输数据。

2、IO解释与IO交互

python流量控制 python数据流_linux_02


IOinput and output。在Unix世界里,一切皆文件。而文件是什么呢?文件就是一串二进制流,不管socket、还是FIFO、管道、终端,一切都是文件,一切都是流。在信息交换的过程中,对这些流进行数据的收发操作,简称为I/O操作(input and output) 往流中读出数据,系统调用read。写入数据,系统调用write。但是计算机里有这么多的流,怎么知道要操作哪个流呢?做到这个的就是文件描述符,即通常所说的fd。一个fd就是一个整数,所以对这个整数的操作,就是对这个文件(流)的操作。创建一个socket,通过系统调用会返回一个文件描述符,那么剩下对socket的操作就会传化为对这个描述符的操作。

3、阻塞IO

python流量控制 python数据流_linux_03


在实际情况汇总,很多时候数据在一开始还没有到达,这个时候内核就要等待足够的数据到来。

而在用户进程这边,整个进程会被阻塞,当内核一直等到数据准备好了,它就会将数据从内核中拷贝到用户内存,然后返回结果,用户进程才解除阻塞的状况,重新运行起来。

二、非阻塞IO模型与非阻塞套接字

1、非阻塞IO模型

python流量控制 python数据流_linux_04


从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果,用户进程判断结果是一个error时,它就知道数据还没有准备好。

于是它可以再次发送read操作,一但内核中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据拷贝到了用户内存,然后返回,非阻塞的接口相比于阻塞模型接口的显著差异在于,在被调用之后立即返回

2、非阻塞IO

import socket
server = socket.socket()
server.setblocking(False) #设置为非阻塞套接字,且需要在其他的执行之前
server.bind(('127.0.0.1',8989))
server.listen(6)
while True:
    try:
        conn,addr = server.accept()
        data = conn.recv(1024)
        conn.send(data)
    except BlockingIOError:
        pass
    except Exception:
        pass

利用异常处理来处理非阻塞IO产生的异常

3、并发与并行

3-1、并发

指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行

3-2、并行

指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,并任一个时刻点上有多个程序在处理机上运行。

4、实现并发

import socket
server = socket.socket()
# server.setblocking(False) #设置为非阻塞套接字,且需要在其他的执行之前
server.bind(('127.0.0.1',8989))
server.listen(6)
all_conn = [] # 添加一个列表来存储生成的对等连接套接字
while True: # 第一层循环只是用来生成对等连接套接字
    try:
        conn,addr = server.accept()
        server.setblocking(False)
        data = conn.recv(1024)
        print(data)
        conn.send(data)
    except BlockingIOError:
        pass
    except Exception as e:
        print(e)
    for conn in all_conn: # 处理多有的对等连接套接字
        try:
            recv_data = conn.recv(1024)
            if recv_data:
                print(recv_data)
                conn.send(recv_data)
            else:
                conn.close()
                all_conn.remove(conn)
        except BlockingIOError:
            pass
        except Exception as e:
            print(e)

并发
代码实现很简单,但是要理解过程
主要是用循环思想去解决问题

四、IO多路复用

1、IO多路复用

python流量控制 python数据流_linux_05


IO多路复用 在之前的非阻塞IO模型中,通过不断的询问来查看是否有数据,这会造成资源的浪费

将查看的过程由主动地查询,变成交给复用器来完成,这样能够更加节省系统资源,并且性能更好

2、epoll

非阻塞套接字与多路复用 非阻塞套接字需要自身遍历每个对等连接套接字,并且每次都是运行的IO操作
复用器不需要进行大量的IO操作,复用器会告诉你哪个对等套接字有数据过来。然后再去处理即可。
epoll epoll是一个惰性事件回调,即回调过程是用户自己去调用的,操作系统只起到了通知的作用。
epoll是linux上最好的IO多路复用器,但是也只有在linux上有,在其他的地方没有

3、代码实现

import socket
import selectors # IO多路复用选择器模块
epoll_select = selectors.EpollSelector() # 实例化
# epoll_select = selectors.DefaultSelector() # 默认选择器,根据不同操作系统自动选择
server = socket.socket()
server.bind(('127.0.0.1',8989))
server.listen(100)
def f_recv(conn):
    recv_data = conn.recv(1024)
    if recv_data:
        print(recv_data)
        conn.send(recv_data)
    else:
        conn.close()
def f_accept(server):
    conn,addr = server.accept() # 生成对等连接套接字
    epoll_select.register(conn,selectors.EVENT_READ,f_recv)
epoll_select.register(server,selectors.EVENT_READ,f_accept)
# selectors.EVENT_READ 有事件发生的时候,调用回调函数
while True:
    events = epoll_select.select() # 查询,只要有,就查出来
    for key,mask in events:
        func = key.data # 函数体
        conn = key.fileobj # 已经注册过的套接字
        func(conn)