事件驱动、异步I/O

一、事件驱动模型

通常我们写服务器处理模型程序时候,有以下几个模型:

  1. 每收到一个请求,创建一个新的进程,来处理该请求
  2. 每收到一个请求,创建一个新的线程,来处理该请求
  3. 每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求(事件驱动)

以上3点优缺点:

  1. 创建进程的开销比较大,导致服务器性能变差,但实现比较简单
  2. 涉及到线程同步问题,可能会死锁
  3. 逻辑较为复杂,但是大多数网络服务器都采用这种方法

那么什么是事件驱动模型呢?举一个简单的例子:
当我们使用鼠标时候,那么电脑如何获取鼠标点击呢?
(1)创建一个线程,该线程一直循环检测是否有鼠标点击。
缺点:浪费CPU资源、容易堵塞(点1下还没处理完再点击无用)、不能同时检测鼠标和键盘(一次只能干一个事情)
(2)事件驱动模型:根据不同的事件做出不同的反应,很多UI平台都会提供onClick()事件。事件驱动模型的思路:
1.有一个事件队列
2.鼠标按下时,向队列中加入一个点击事件
3.有一个循环程序,不断地从队列取出事件,根据不同的事件,调用不同的函数
4.事件一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数
5.当处理线程处理完当前任务,要执行一个回调函数,告诉你该任务已经执行完毕

这就是事件驱动模型。


二、异步I/O

想要说异步I/O,我们要先谈谈linux系统下的4种网络模式方案。(阻塞I/O,非阻塞I/O,I/O多路复用,异步I/O)再说这个之前我们要先理解操作系统如何接收数据》》》数据通过socket发过来,先由操作系统存在内核空间,在从内核空间发送到用户空间(内存)。下面说一下4种模型:

    阻塞I/O:在linux中,默认情况下所有的socket都是阻塞的,在等待socket发数据时候,是阻塞的。当接收的数据被
             拷贝到内核缓冲区时候,也会阻塞。
    非阻塞I/O:在接收数据时候不会阻塞,一旦数据没准备好,返回一个error(一旦发现是error就知道数据没准备好)
               可以再次接收数据或者去干别的,内核数据拷贝到用户空间还是阻塞的。
    I/O多路复用(三种模式select,poll,epoll):这三种模式就是在不断地检查每个socket是否有数据发送过来
        单一进程单一线程,当用户调用select,那么整个进程就会被阻塞,同时内核会监听select负责的所有socket,
        当任何一个准备好,select就会返回数据,然后把数据从内核拷贝到用户空间。
    异步I/O:用户发起请求,就不用管了,可以干别的事。不会阻塞进程。内核去处理数据,当结束后发送一个signal给用户
            告诉用户完成了。

I/O多路复用中:
select:每次都要循环n个socket连接,浪费资源,只能监控n个连接
poll:取消了最多连接个数n,可以监控更多的连接
epoll:监控socket,会返回哪一个有数据,数据还在内核中,用户需要自己调用recv等,这样就不用全都循环一遍了
介绍一下I/O多路复用中的select模块:

select服务器:

#用select模拟socket server
#需要在非阻塞模式下进行
import select,socket,queue
server=socket.socket()
server.bind(('localhost',9000))
server.listen(1000)  #监听1000个
#设置非阻塞模式
server.setblocking(False)  #设置非阻塞模式
inputs=[server,] #监听的连接放在这里
outputs=[]

while True:
    readable,writeable,exceptional=select.select(inputs,outputs,inputs )  #第一个inputs是检测哪些链接,第二个inputs返回有问题的链接
    print(readable,writeable,exceptional)
    for r in readable:
        if r is server:  #代表进来一个新链接
            conn,addr=server.accept()  #没有连接就报错
            print('来了一个新链接:',addr)
            #建立连接完成,开始收数据,需要让内核通知你要发数据了,才能收
            inputs.append(conn) #要想实现客户端发数据来时,server能知道,就需要让select再监测这个conn
        #这样一来inputs中就有server和 conn,如果返回server说明又有新链接进来,如果返回conn说明可以接收数据了
        else:
            data=r.recv(1024)
            print('recv:',data)

            r.send(data)
            print('send done ....')

select 客户端:


import socket
HOST='localhost'
PORT=9000
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((HOST,PORT))
while True:
    msg=bytes(input('>>:'),encoding= 'utf-8')
    s.sendall(msg)
    data=s.recv(1024)
    print('Received',repr(data))  #repr格式化输出
s.close()

看一下结果:
这里写图片描述

我们开启两个客户端,分别给服务器发送消息,可以看见服务器连接了两个 客户端,也接收到了客户端数据。这就是I/O多路复用的一种模式,监听socket是否有消息发过来,并把数据存在用户空间。

猜你喜欢

转载自blog.csdn.net/weixin_42898819/article/details/82083529