事件驱动模型与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()

 

 

 

 

 

 

 

 

 

posted @ 2021-07-13 15:32  wode110  阅读(145)  评论(0编辑  收藏  举报