1、事件驱动模型介绍 javascript
一、传统的编程模式 html
例如:线性模式大体流程java
开始--->代码块A--->代码块B--->代码块C--->代码块D--->......--->结束python
每个代码块里是完成各类各样事情的代码,但编程者知道代码块A,B,C,D...的执行顺序,惟一可以改变这个流程的是数据。输入不一样的数据,根据条件语句判断,流程或许就改成A--->C--->E...--->结束。每一次程序运行顺序或许都不一样,但它的控制流程是由输入数据和你编写的程序决定的。若是你知道这个程序当前的运行状态(包括输入数据和程序自己),那你就知道接下来甚至一直到结束它的运行流程。linux
例如:事件驱动型程序模型大体流程web
开始--->初始化--->等待编程
与上面传统编程模式不一样,事件驱动程序在启动以后,就在那等待,等待什么呢?等待被事件触发。传统编程下也有“等待”的时候,好比在代码块D中,你定义了一个input(),须要用户输入数据。但这与下面的等待不一样,传统编程的“等待”,好比input(),你做为程序编写者是知道或者强制用户输入某个东西的,或许是数字,或许是文件名称,若是用户输入错误,你还须要提醒他,并请他从新输入。事件驱动程序的等待则是彻底不知道,也不强制用户输入或者干什么。只要某一事件发生,那程序就会作出相应的“反应”。这些事件包括:输入信息、鼠标、敲击键盘上某个键还有系统内部定时器触发。缓存
二、事件驱动模型安全
一般,咱们写服务器处理模型的程序时,有如下几种模型:服务器
(1)每收到一个请求,建立一个新的进程,来处理该请求; (2)每收到一个请求,建立一个新的线程,来处理该请求; (3)每收到一个请求,放入一个事件列表,让主进程经过非阻塞I/O方式来处理请求
三、第三种就是协程、事件驱动的方式,通常广泛认为第(3)种方式是大多数网络服务器采用的方式
示例:
1 #事件驱动之鼠标点击事件注册 2 3 <!DOCTYPE html> 4 <html lang="en"> 5 <head> 6 <meta charset="UTF-8"> 7 <title>Title</title> 8 9 </head> 10 <body> 11 12 <p onclick="fun()">点我呀</p> 13 14 15 <script type="text/javascript"> 16 function fun() { 17 alert('约吗?') 18 } 19 </script> 20 </body> 21 22 </html>
执行结果:
在UI编程中,经常要对鼠标点击进行相应,首先如何得到鼠标点击呢?
两种方式:
那么这个方式有如下几个缺点:
目前大部分的UI编程都是事件驱动模型,如不少UI平台都会提供onClick()事件,这个事件就表明鼠标按下事件。事件驱动模型大致思路以下:
什么是事件驱动模型 ?
目前大部分的UI编程都是事件驱动模型,如不少UI平台都会提供onClick()事件,这个事件就表明鼠标按下事件。事件驱动模型大致思路以下:
需知:每一个cpu都有其一套可执行的专门指令集,如SPARC和Pentium,其实每一个硬件之上都要有一个控制程序,cpu的指令集就是cpu的控制程序。
2、IO模型准备
在进行解释以前,首先要说明几个概念:
一、用户空间和内核空间
例如:采用虚拟存储器,对于32bit操做系统,它的寻址空间(虚拟存储空间为4G,即2的32次方)。
操做系统的核心是内核,独立于普通的应用程序,能够访问受保护的内存空间,也能够访问底层硬件的全部权限。
为了保证用户进程不能直接操做内核(kernel),保证内核的安全,操做系统将虚拟空间划分为两部分:一部分为内核空间,另外一部分为用户空间。
那么操做系统是如何分配空间的?这里就会涉及到内核态和用户态的两种工做状态。
1G: 0 --->内核态
3G: 1 --->用户态
CPU的指令集,是经过0和1 决定你是用户态,仍是内核态
计算机的两种工做状态: 内核态和用户态
cpu的两种工做状态:
如今的操做系统都是分时操做系统,分时的根源,来自于硬件层面操做系统内核占用的内存与应用程序占用的内存彼此之间隔离。cpu经过psw(程序状态寄存器)中的一个2进制位来控制cpu自己的工做状态,即内核态与用户态。
内核态:操做系统内核只能运做于cpu的内核态,这种状态意味着能够执行cpu全部的指令,能够执行cpu全部的指令,这也意味着对计算机硬件资源有着彻底的控制权限,而且能够控制cpu工做状态由内核态转成用户态。
用户态:应用程序只能运做于cpu的用户态,这种状态意味着只能执行cpu全部的指令的一小部分(或者称为全部指令的一个子集),这一小部分指令对计算机的硬件资源没有访问权限(好比I/O),而且不能控制由用户态转成内核态。
二、进程切换
为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复之前挂起的某个进程的执行,这种行为就被称为进程切换。
总结:进程切换是很消耗资源的。
三、进程的阻塞
正在执行的进程,因为期待的某些事件未发生,如请求系统资源失败、等待某种操做的完成、新数据还没有到达或无新工做作等,则由系统自动执行阻塞原语(Block),使本身由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也所以只有处于运行态的进程(得到CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。
四、文件描述符fd
文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。
文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者建立一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写每每会围绕着文件描述符展开。可是文件描述符这一律念每每只适用于UNIX、Linux这样的操做系统。
五、缓存 I/O
缓存 I/O 又被称做标准 I/O,大多数文件系统的默认 I/O 操做都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操做系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操做系统内核的缓冲区中,而后才会从操做系统内核的缓冲区拷贝到应用程序的地址空间。用户空间无法直接访问内核空间的,内核态到用户态的数据拷贝。
缓存 I/O 的缺点:
数据在传输过程当中须要在应用程序地址空间和内核进行屡次数据拷贝操做,这些数据拷贝操做所带来的 CPU 以及内存开销是很是大的。
本文讨论的背景是Linux环境下的network IO。
IO发生时涉及的对象和步骤:
对于一个network IO (这里咱们以read举例),它会涉及到两个系统对象,
一、一个是调用这个IO的process (or thread),
二、另外一个就是系统内核(kernel)。
当一个read操做发生时,它会经历两个阶段:
一、等待数据准备 (Waiting for the data to be ready)
二、将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,由于这些IO Model的区别就是在两个阶段上各有不一样的状况。
常见的几种IO 模型:
1、不经常使用的IO模型
一、信号驱动IO模型(Signal-driven IO)
使用信号,让内核在描述符就绪时发送SIGIO信号通知应用程序,称这种模型为信号驱动式I/O(signal-driven I/O)。
原理图:
首先开启套接字的信号驱动式I/O功能,并经过sigaction系统调用安装一个信号处理函数。该系统调用将当即返回,咱们的进程继续工做,也就是说进程没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。随后就能够在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已经准备好待处理,也能够当即通知主循环,让它读取数据报。
不管如何处理SIGIO信号,这种模型的优点在于等待数据报到达期间进程不被阻塞。主循环能够继续执行 ,只要等到来自信号处理函数的通知:既能够是数据已准备好被处理,也能够是数据报已准备好被读取。
2、经常使用的四种IO模型:
一、 blocking IO(阻塞IO模型)
原理图:
示例:一收一发程序会进入死循环
server.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #Author: nulige 4 5 import socket 6 7 sk=socket.socket() 8 9 sk.bind(("127.0.0.1",8080)) 10 11 sk.listen(5) 12 13 while 1: 14 conn,addr=sk.accept() 15 16 while 1: 17 conn.send("hello client".encode("utf8")) 18 data=conn.recv(1024) 19 print(data.decode("utf8"))
client.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #Author: nulige 4 5 import socket 6 7 sk=socket.socket() 8 9 sk.connect(("127.0.0.1",8080)) 10 11 while 1: 12 data=sk.recv(1024) 13 print(data.decode("utf8")) 14 sk.send(b"hello server")
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来讲,不少时候数据在一开始尚未到达(好比,尚未收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,而后kernel返回结果,用户进程才解除block的状态,从新运行起来。
因此,blocking IO的特色就是在IO执行的两个阶段都被block了。
二、non-blocking IO(非阻塞IO)
原理图:
从图中能够看出,当用户进程发出read操做时,若是kernel中的数据尚未准备好,那么它并不会block用户进程,而是马上返回一个error。从用户进程角度讲 ,它发起一个read操做后,并不须要等待,而是立刻就获得了一个结果。用户进程判断结果是一个error时,它就知道数据尚未准备好,因而它能够再次发送read操做。一旦kernel中的数据准备好了,而且又再次收到了用户进程的system call,那么它立刻就将数据拷贝到了用户内存,而后返回。
因此,用户进程实际上是须要不断的主动询问kernel数据好了没有。
注意:
在网络IO时候,非阻塞IO也会进行recvform系统调用,检查数据是否准备好,与阻塞IO不同,”非阻塞将大的整片时间的阻塞分红N多的小的阻塞, 因此进程不断地有机会 ‘被’ CPU光顾”。即每次recvform系统调用之间,cpu的权限还在进程手中,这段时间是能够作其余事情的,
也就是说非阻塞的recvform系统调用调用以后,进程并无被阻塞,内核立刻返回给进程,若是数据还没准备好,此时会返回一个error。进程在返回以后,能够干点别的事情,而后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程一般被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。须要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
示例:
服务端:
1 import time 2 import socket 3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 sk.bind(('127.0.0.1',6667)) 5 sk.listen(5) 6 sk.setblocking(False) #设置成非阻塞状态 7 while True: 8 try: 9 print ('waiting client connection .......') 10 connection,address = sk.accept() # 进程主动轮询 11 print("+++",address) 12 client_messge = connection.recv(1024) 13 print(str(client_messge,'utf8')) 14 connection.close() 15 except Exception as e: #捕捉错误 16 print (e) 17 time.sleep(4) #每4秒打印一个捕捉到的错误
客户端:
1 import time 2 import socket 3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 5 while True: 6 sk.connect(('127.0.0.1',6667)) 7 print("hello") 8 sk.sendall(bytes("hello","utf8")) 9 time.sleep(2) 10 break
缺点:
一、发送了太多系统调用数据
二、数据处理不及时
三、IO multiplexing(IO多路复用)
IO multiplexing这个词可能有点陌生,可是若是我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。咱们都知道,select/epoll的好处就在于单个process就能够同时处理多个网络链接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的全部socket,当某个socket有数据到达了,就通知用户进程。
IO多路复用的三种方式:
一、select--->效率最低,但有最大描述符限制,在linux为1024。
二、poll ---->和select同样,但没有最大描述符限制。
三、epoll --->效率最高,没有最大描述符限制,支持水平触发与边缘触发。
IO多路复用的优点:同时能够监听多个链接,用的是单线程,利用空闲时间实现并发。
注意:
Linux系统: select、poll、epoll
Windows系统:select
Mac系统:select、poll
原理图:
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”全部select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操做,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并无太大的不一样,事实上,还更差一些。由于这里须要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。可是,用select的优点在于它能够同时处理多个connection。(多说一句。因此,若是处理的链接数不是很高的话,使用select/epoll的web server不必定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优点并非对于单个链接能处理得更快,而是在于能处理更多的链接。)
在IO multiplexing Model中,实际中,对于每个socket,通常都设置成为non-blocking,可是,如上图所示,整个用户的process实际上是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
注意1:select函数返回结果中若是有文件可读了,那么进程就能够经过调用accept()或recv()来让kernel将位于内核中准备到的数据copy到用户区。
注意2: select的优点在于能够处理多个链接,不适用于单个链接
示例:
server.py
1 #server.py 2 3 import socket 4 import select 5 sk=socket.socket() 6 sk.bind(("127.0.0.1",9904)) 7 sk.listen(5) 8 9 while True: 10 # sk.accept() #文件描述符 11 r,w,e=select.select([sk,],[],[],5) #输入列表,输出列表,错误列表,5: 是监听5秒 12 for i in r: #[sk,] 13 conn,add=i.accept() 14 print(conn) 15 print("hello") 16 print('>>>>>>')
client.py
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",9904)) 6 7 while 1: 8 inp=input(">>").strip() 9 sk.send(inp.encode("utf8")) 10 data=sk.recv(1024) 11 print(data.decode("utf8"))
IO多路复用中的两种触发方式:
水平触发:若是文件描述符已经就绪能够非阻塞的执行IO操做了,此时会触发通知.容许在任意时刻重复检测IO的状态, 没有必要每次描述符就绪后尽量多的执行IO.select,poll就属于水平触发。
边缘触发:若是文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知.在收到一个IO事件通知后要尽量 多的执行IO操做,由于若是在一次通知中没有执行完IO那么就须要等到下一次新的IO活动到来才能获取到就绪的描述 符.信号驱动式IO就属于边缘触发。
epoll:便可以采用水平触发,也能够采用边缘触发。
一、水平触发
只有高电平或低电平的时候才触发
1-----高电平---触发
0-----低电平---不触发
示例:
server服务端
1 #水平触发 2 import socket 3 import select 4 sk=socket.socket() 5 sk.bind(("127.0.0.1",9904)) 6 sk.listen(5) 7 8 while True: 9 r,w,e=select.select([sk,],[],[],5) #input输入列表,output输出列表,erron错误列表,5: 是监听5秒 10 for i in r: #[sk,] 11 print("hello") 12 13 print('>>>>>>')
client客户端
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",9904)) 6 7 while 1: 8 inp=input(">>").strip() 9 sk.send(inp.encode("utf8")) 10 data=sk.recv(1024) 11 print(data.decode("utf8"))
二、边缘触发
1---------高电平--------触发
0---------低电平--------触发
IO多路复用优点:同时能够监听多个链接
示例:select能够监控多个对象
服务端
1 #优点 2 import socket 3 import select 4 sk=socket.socket() 5 sk.bind(("127.0.0.1",9904)) 6 sk.listen(5) 7 inp=[sk,] 8 9 while True: 10 r,w,e=select.select(inp,[],[],5) #[sk,conn],5是每隔几秒监听一次 11 12 for i in r: #[sk,] 13 conn,add=i.accept() #发送系统调用 14 print(conn) 15 print("hello") 16 inp.append(conn) 17 # conn.recv(1024) 18 print('>>>>>>')
客户端:
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",9904)) 6 7 while 1: 8 inp=input(">>").strip() 9 sk.send(inp.encode("utf8")) 10 data=sk.recv(1024) 11 print(data.decode("utf8"))
多了一个判断,用select方式实现的并发
示例:实现并发聊天功能 (select+IO多路复用,实现并发)
服务端:
1 import socket 2 import select 3 sk=socket.socket() 4 sk.bind(("127.0.0.1",8801)) 5 sk.listen(5) 6 inputs=[sk,] 7 while True: #监听sk和conn 8 r,w,e=select.select(inputs,[],[],5) #conn发生变化,sk不变化就走else 9 print(len(r)) 10 #判断sk or conn 谁发生了变化 11 for obj in r: 12 if obj==sk: 13 conn,add=obj.accept() 14 print(conn) 15 inputs.append(conn) 16 else: 17 data_byte=obj.recv(1024) 18 print(str(data_byte,'utf8')) 19 inp=input('回答%s号客户>>>'%inputs.index(obj)) 20 obj.sendall(bytes(inp,'utf8')) 21 22 print('>>',r)
客户端:
1 import socket 2 sk=socket.socket() 3 sk.connect(('127.0.0.1',8801)) 4 5 while True: 6 inp=input(">>>>") 7 sk.sendall(bytes(inp,"utf8")) 8 data=sk.recv(1024) 9 print(str(data,'utf8'))
执行结果:
先运行服务端,再运行多个客户端,就能够聊天啦。(能够接收多个客户端消息)
1 #server 2 >> [<socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8801)>] 3 1 4 hello 5 回答1号客户>>>word 6 >> [<socket.socket fd=344, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8801), raddr=('127.0.0.1', 54388)>] 7 1 8 9 #clinet 10 >>>>hello 11 word
用户进程发起read操做以后,马上就能够开始去作其它的事。而另外一方面,从kernel的角度,当它受到一个asynchronous read以后,首先它会马上返回,因此不会对用户进程产生任何block。而后,kernel会等待数据准备完成,而后将数据拷贝到用户内存,当这一切都完成以后,kernel会给用户进程发送一个signal,告诉它read操做完成了。
异步最大特色:全程无阻塞
synchronous IO(同步IO)和asynchronous IO(异步IO)的区别:
二者的区别就在于synchronous IO作”IO operation”的时候会将process阻塞。(有一丁点阻塞,都是同步IO)按照这个定义,以前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO(同步IO)。
同步IO:包括 blocking IO、non-blocking、select、poll、epoll(故:epool只是伪异步而已)(有阻塞)
异步IO:包括:asynchronous (无阻塞)
五种IO模型比较:
通过上面的介绍,会发现non-blocking IO和asynchronous IO的区别仍是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,可是它仍然要求进程去主动的check,而且当数据准备完成之后,也须要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则彻底不一样。它就像是用户进程将整个IO操做交给了他人(kernel)完成,而后他人作完后发信号通知。在此期间,用户进程不须要去检查IO操做的状态,也不须要主动的去拷贝数据。
五、selectors模块应用
python封装好的模块:selectors
selectors模块: 会选择一个最优的操做系统实现方式
示例:
select_module.py
1 import selectors 2 import socket 3 4 sel = selectors.DefaultSelector() 5 6 def accept(sock, mask): 7 conn, addr = sock.accept() # Should be ready 8 print('accepted', conn, 'from', addr) 9 conn.setblocking(False) #设置成非阻塞 10 sel.register(conn, selectors.EVENT_READ, read) #conn绑定的是read 11 12 def read(conn, mask): 13 try: 14 data = conn.recv(1000) # Should be ready 15 if not data: 16 raise Exception 17 print('echoing', repr(data), 'to', conn) 18 conn.send(data) # Hope it won't block 19 except Exception as e: 20 print('closing', conn) 21 sel.unregister(conn) #解除注册 22 conn.close() 23 24 sock = socket.socket() 25 sock.bind(('localhost', 8090)) 26 sock.listen(100) 27 sock.setblocking(False) 28 #注册 29 sel.register(sock, selectors.EVENT_READ, accept) 30 print("server....") 31 32 while True: 33 events = sel.select() #监听[sock,conn1,conn2] 34 print("events",events) 35 #拿到2个元素,一个key,一个mask 36 for key, mask in events: 37 # print("key",key) 38 # print("mask",mask) 39 callback = key.data #绑定的是read函数 40 # print("callback",callback) 41 callback(key.fileobj, mask) #key.fileobj=sock,conn1,conn2
client.py
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",8090)) 6 while 1: 7 inp=input(">>>") 8 sk.send(inp.encode("utf8")) #发送内容 9 data=sk.recv(1024) #接收信息 10 print(data.decode("utf8")) #打印出来
执行结果:
先运行select_module.py,再运行clinet.py
1 #server 2 3 server.... 4 events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)] 5 accepted <socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)> from ('127.0.0.1', 57638) 6 events [(SelectorKey(fileobj=<socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)>, fd=376, events=1, data=<function read at 0x015C26A8>), 1)] 7 echoing b'hello' to <socket.socket fd=376, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57638)> 8 events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)] 9 accepted <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)> from ('127.0.0.1', 57675) 10 events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)] 11 echoing b'uuuu' to <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)> 12 events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)] 13 closing <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57675)> 14 events [(SelectorKey(fileobj=<socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090)>, fd=312, events=1, data=<function accept at 0x01512F60>), 1)] 15 accepted <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)> from ('127.0.0.1', 57876) 16 events [(SelectorKey(fileobj=<socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)>, fd=324, events=1, data=<function read at 0x015C26A8>), 1)] 17 echoing b'welcome' to <socket.socket fd=324, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8090), raddr=('127.0.0.1', 57876)> 18 19 #clinet (启动两个client) 20 >>>hello 21 hello 22 23 >>>welcome 24 welcome
6、I/O多路复用的应用场景
(1)当客户处理多个描述字时(通常是交互式输入和网络套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种状况是可能的,但不多出现。
(3)若是一个TCP服务器既要处理监听套接口,又要处理已链接套接口,通常也要用到I/O复用。
(4)若是一个服务器即要处理TCP,又要处理UDP,通常要使用I/O复用。
(5)若是一个服务器要处理多个服务或多个协议,通常要使用I/O复用。
'''与多进程和多线程技术相比,I/O多路复用技术的最大优点是系统开销小,系统没必要建立进程/线程,也没必要维护这些进程/线程,从而大大减少了系统的开销。'''
最后,再举几个不是很恰当的例子来讲明这四个IO Model:
有A,B,C,D四我的在钓鱼:
A用的是最老式的鱼竿,因此呢,得一直守着,等到鱼上钩了再拉杆;【阻塞】
B的鱼竿有个功能,可以显示是否有鱼上钩(这个显示功能一直去判断鱼是否上钩),因此呢,B就和旁边的MM聊天,隔会再看看有没有鱼上钩,有的话就迅速拉杆;【非阻塞】
C用的鱼竿和B差很少,但他想了一个好办法,就是同时放好几根鱼竿,而后守在旁边,一旦有显示说鱼上钩了,它就将对应的鱼竿拉起来;【同步】
D是个有钱人,干脆雇了一我的帮他钓鱼,一旦那我的把鱼钓上来了,就给D发个短信(消息回掉机制,主动告知)。【异步】
做业:
一、使用IO多路复用,作一个ftp的上传和下载做业
要求:实现多用户操做,能够同时上传和下载(不能用socketserver),有显示进度条。