事件驱动模型与IO模型
事件驱动模型
事件驱动编程,是一种编程范式。这里程序的执行流由外部事件来决定。它的特点是包含一个循环,当外部事件发生时,使用回调机制来触发相应的处理。
另外2种常见的编程范式是(单线程)同步以及多线程编程
IO模型
同步IO(synchronous),异步IO(asynchronous),阻塞IO(blocking) 和非阻塞IO(non-blocking) 都是独立的,不一起出现
5种IO模型如下:
-blocking IO
-nonblocking IO
-IO multiplexing(IO多路复用)
-signal driven IO
-asynchronous IO
blocking IO (阻塞IO)
在linux 中,默认情况下所有的socket 都是blocking IO,阻塞IO 只发生了一次系统调用,进程进入阻塞状态时,是不占用CPU的。
实例:
-server 端
import socket
sk=socket.socket()
sk.bind(("127.0.0.1",8080))
sk.listen(5)
while 1:
conn,addr=sk.accept()
while 1:
conn.send("hello client".encode("utf8"))
data=conn.recv(1024)
print(data.decode("utf8"))
-client端
import socket
sk=socket.socket()
sk.connect(("127.0.0.1",8080))
while 1:
data=sk.recv(1024)
print(data.decode("utf8"))
sk.send(b"hello server")
非阻塞IO
缺点:不能及时收发消息,系统调用太多次
实例
-server 端
import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(('127.0.0.1',6667))
sk.listen(5)
sk.setblocking(False) #此代码代表不会一值等待连接(如上图)/非阻塞
print ('waiting client connection .......')
while True:
try:
connection,address = sk.accept() # 进程主动轮询
print("+++",address)
client_messge = connection.recv(1024)
print(str(client_messge,'utf8'))
connection.close()
except Exception as e:
print (e)
time.sleep(4)
-client 端
import time
import socket
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
while True:
sk.connect(('127.0.0.1',6667))
print("hello")
sk.sendall(bytes("hello","utf8"))
time.sleep(2)
break
IO multiplexing(IO多路复用)
IO多路复用实现并发的方法:
1)select 可跨平台使用,但是监听连接数最大为1024
2) poll
3) epoll (推荐使用):效率高,最好的实现方式
实例一:
-server 端
import socket
import select
sk=socket.socket()
sk.bind(("127.0.0.1",9904))
sk.listen(5)
inp=[sk,]
while True:
r,w,e=select.select(inp,[],[],5) #[sk,conn] #select监听。
#inp是指输入内容,第一个[]是输出,第二个[]是报错,数字5是每隔5秒监听一次(可自己设置秒数)
#此处的r 相当于是谁变动r就等于谁。
for i in r:#[sk,]
conn,add=i.accept()
print(conn)
print("hello")
inp.append(conn)
print('>>>>>>')
-client 端
import socket
sk=socket.socket()
sk.connect(("127.0.0.1",9904))
while 1:
inp=input(">>").strip()
sk.send(inp.encode("utf8"))
data=sk.recv(1024)
print(data.decode("utf8"))
实例二:
server 端
import socket
import select
sk=socket.socket()
sk.bind(("127.0.0.1",8801))
sk.listen(5)
inputs=[sk,]
while True:
r,w,e=select.select(inputs,[],[],5)#此处的r 相当于是谁变动,r等于谁
for obj in r:#[sk,]
if obj==sk:
conn,add=obj.accept()#此处相当于取出sk,已监听sk.
print(conn)
inputs.append(conn)
else:
data_byte=obj.recv(1024)
print(str(data_byte,'utf8'))
inp=input('回答%s号客户>>>'%inputs.index(obj))
obj.sendall(bytes(inp,'utf8'))
print('>>',r)
当客户端与服务端连接上的时候 :inputs= [sk,] select 可以捕捉到sk变动,监听sk,执行obj ==sk 条件下代码,将conn 加入到inputs中,所以此时inputs = [sk,conn,] 此时监听sk和 conn.
但是当发消息时,因为只有conn变动,所以运行的是else下代码
client 端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8801))
while True:
inp = input(">>>>")
sk.sendall(bytes(inp, "utf8"))
data = sk.recv(1024)
print(str(data, 'utf8'))
python 封装的实现IO多复路模型的模块-selectors
服务端代码如下:
import selectors
import socket
sel = selectors.DefaultSelector() #创建selector 对象
def accept(sock,mask):
conn,addr = sock.accept()
print('accepted', conn, 'from', addr)
conn.setblocking(False)#设置非阻塞,代码代表不会一直等待
sel.register(conn,selectors.EVENT_READ,read)
#将conn 与read函数绑在一起,并将conn 加入sel.select()列表中
def read(conn,mask):
try:
data = conn.recv(1000)
if not data:
raise Exception
print('echoing', data.decode('utf-8'), 'to', conn)
conn.send(data)
except Exception as e:#一旦断开连接,会执行以下代码
print('closing', conn)
sel.unregister(conn)
#此代码代表解绑conn,意味着sel.select()列表中不在有它
conn.close()
sock = socket.socket()
sock.bind(("192.168.1.5",8082))
sock.listen(1000)
sock.setblocking(False)#设置socket 非阻塞,代码代表不会一直等待
sel.register(sock, selectors.EVENT_READ, accept)
#将socket 与accept函数绑在一起,并将sock 加入sel.select()列表中
print("server.....")
#当与客户端连接上时,sock有变化,events = [sock,],callback =sock=accept,accept(),accept执行,代码如下。
此时已经监听了活动socket,而accept()函数执行又将conn加入events中进行监听。
#当发送消息时,conn 有变化,所以此时的events = [conn,],callback = conn =read,read()执行,代码同样如下。
while True:
events =sel.select()#此处events相当于是变动的监听对象,谁变动,此处就是谁
for key, mask in events:
callback = key.data
#当与客户端连接上时,第一次运行时此处相当于callback=accept
#发送消息时,callback = read
callback(key.fileobj, mask)
#第一次与客户端连接时,此处相当于accept(sock,mask),运行accept 函数。
#发消息时,此处=read(conn,mask)
客户端
import socket
sk=socket.socket()
sk.connect(("192.168.1.5",8082))
while 1:
inp = input(">>>>").strip()
sk.send(inp.encode('utf-8'))
data = sk.recv(1024)
print(data.decode('utf-8'))
异步IO(asynchronous IO)
异步IO:全程无阻塞
以下是这几种IO模型的区别图:
作业:用selectors 实现文件的上传与下载并发。
客户端:
import socket
import sys,os
Base_DIR = os.path.basename(os.path.abspath(__file__))
class selectFtClient:
def __init__(self):
self.args = sys.argv # sys.argv 相当于系统从外部获取数据,以列表形式存放[代码本身文件路径,输入内容]
if len(self.args) > 1:
self.port = (self.args[1], int(self.args[2]))
else:
self.port = ('192.168.1.4', 8080)
self.creat_socket()
self.command_fanout()
def creat_socket(self):
try:
self.sk = socket.socket()
self.sk.connect(self.port)
print('连接服务器成功')
except Exception as e:
print("error",e)
def command_fanout(self):
while True:
cmd = input('>>>').strip()
if not cmd: #代表输入为空
break
cmd,file = cmd.split()
if hasattr(self,cmd):#判断是否有cmd方法
func = getattr(self,cmd)
func(cmd,file)
else:
print('调用错误!')
def put(self,cmd,file):
if os.path.isfile(file):
fileName = os.path.basename(file)
fileSize = os.path.getsize(file)
fileInfo = "%s|%s|%s"%(cmd,fileName,fileSize)
self.sk.send(fileInfo.encode('utf8'))
recvStatus = self.sk.recv(1024)
print('recvStatus',recvStatus)
hasSend = 0
if recvStatus.decode('utf8')=="ok":
with open(file,'rb') as f:
while fileSize > hasSend:
contant = f.read(1024)
recv_size = len(contant)
self.sk.send(contant)
hasSend +=recv_size
s= str(int(hasSend/fileSize*100))+"%"
print('正在上传文件:%s,已上传:%s'%(fileName,s))
print('%s上传完毕'%fileName)
else:
print('文件不存在')
def get(self,cmd,file):
pass
if __name__ == "__main__":
selectFtClient()
服务端:
import os
import sys
import time
BASE_DIR = os.path.dirname(os.path.abspath(__file__))#upload项
import socket
import selectors
class selectFtpServer:
def __init__(self):
self.dic = {}
self.sel = selectors.DefaultSelector() #创建selectors对象
self.creat_socket()
self.handle()
def creat_socket(self):
server = socket.socket()#创建socket
server.bind(('192.168.1.4',8080))#绑定端口
server.listen(5)#监听
server.setblocking(False)#设置不阻塞
self.sel.register(server,selectors.EVENT_READ,self.accept)
print('服务端已开启,等待用户连接....')
def handle(self):
while True:
events = self.sel.select()#监听有变化的
for key,mask in events:
callback =key.data
callback(key.fileobj,mask)
def accept(self,sock,mask):
conn,addr = sock.accept
print('connected conn: %s '%conn)
print('connected addr: %s '%addr)
conn.setblocking(False)
self.sel.register(conn,selectors.EVENT_READ,self.read)
self.dic[conn]={}
def read(self,conn,mask):
if not self.dic[conn]:
data = conn.recv(1024)
cmd, filename, filesize = data.decode('utf8').split('|')
self.dic = {conn: {'cmd': cmd, 'filename': filename, 'filesize': int(filesize)}}
if cmd == 'put':
conn.send("ok".encode('utf8'))
if self.dic[conn]['cmd'] == 'get':
file = os.path.join(BASE_DIR, 'download', filename)
if os.path.exists(file):
fileSize = os.path.getsize(file)
send_info = "%s|%s" % ('yes',fileSize)
conn.send(send_info.encode('utf8'))
else:
send_info = '%s|%s'%('no',0)
conn.send(send_info.encode('utf8'))
else:
if self.dic[conn].get('cmd', None):
cmd = self.dic[conn].get('cmd')
if hasattr(self, cmd):
func = getattr(self, cmd)
func(conn)
else:
print('error cmd !')
conn.close()
else:
print('error cmd !')
def put(self,conn):
fileName = self.dic[conn]['fileName']
fileSize = self.dic[conn]['fileSize']
path = os.path.join(BASE_DIR,'upload',fileName)
recv_data = conn.recv(1024)
self.hasRecv +=len(recv_data)
with open(path,'ab') as f:
f.write(recv_data)
if fileSize ==self.hasRecv:
if conn in self.dic.keys():
self.dic[conn]={}
print('%s上传完毕!'%fileName)
if __name__ == "__main__":
selectFtpServer()