python之模擬io模式

環境:python3.8html

參考:python

    https://segmentfault.com/a/1190000003063859(linux io模式等,本文的原理圖片來自這個 )linux

    https://blog.csdn.net/zhuangzi123456/article/details/84400108(select的使用)segmentfault

    https://blog.51cto.com/linzb/1911468(之前的筆記)服務器

本文全部模型的客戶端程序以下:app

import  socket
c=socket.socket()
c.connect(("127.0.0.1",456))
while True:
    c.sendall(input(":").encode('utf-8'))
    print(c.recv(1024).decode('utf-8'))


民以食爲天,讓咱們從吃飯提及。異步

假設你去食堂吃飯(取用戶空間外的數據),你要作的事是什麼?socket

  1. 排隊取餐(發起系統調用)ide

  2.打飯阿姨等廚師的飯菜端上來(內核等數據).net

  3.油膩的抽風阿姨把飯打給你(內核拷貝數據)

這就是所謂的阻塞i/o模型啦

block.png

體如今代碼就跟簡單的socket程序同樣

from socket import *
backlog=10
buffsize=1024
server=socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",5666))
server.listen(backlog)
clientsocket, clientaddr = server.accept()
while True:
    msg=clientsocket.recv(buffsize)
    print('client msg:', msg.decode('utf-8'))
    clientsocket.sendall(input('your msg:').encode('utf-8'))

recv方法發起了一個系統調用,等待內核把數據拷貝到用戶內存區,而後用戶進程才能讀取數據。

爲何要這麼拷貝數據呢?

由於socket程序運行在用戶態,而接受的數據被客戶端發送到網卡,網卡是硬件,只有內核纔有資格去操做硬件

這種模型會阻塞在兩個地方:

  1. 用戶在等待用戶空間有數據(你在等阿姨把飯給你)

  2. 系統在等待取得數據(阿姨在等廚師作好飯)


這比如飯還有好久才熟,你卻只能在那等着,不能出去玩,因而你想了個好辦法:

你仍是出去玩,可是隔一段時間回來問下:個人飯熟了嗎?

這個就是所謂的不阻塞io

nonblock.png

這裏我利用socket模塊的setblocking來模擬這個過程,當服務器收到客戶端的連接的時會打印「hello」,其餘時候隔3秒打印「waiting」,代碼以下:

import socket,select,time
s=socket.socket()
s.bind(("127.0.0.1",456))
s.listen(5)
s.setblocking(0)
while 1:
    try:
        print ('waiting')
        s.accept()
        print('hello')
    except Exception:
        time.sleep(3)

顯然,這種模型要本身大量的來回詢問,因此你又想了個偷懶的辦法:

色誘打飯阿姨,給阿姨留個號碼,讓她飯好了告訴你聲,本身跑出去玩

這就是所謂的io複用:

io複用.png

給阿姨留號碼又分兩種:

    一種是匿名電話,每次阿姨都要詢問這是哪一個英俊的小夥子的xx餐好了[,收匿名電話的阿姨也分兩類,有原則的(一次1024個,用select實現),無原則的(不限個數,用poll實現)]

    一種是留姓名的,阿姨省去輪詢步驟,飯好了直接通知(用epoll實現),能夠想象,這種效率是最高的


select實現以下:參考:https://blog.csdn.net/zhuangzi123456/article/details/84400108

import socket,select,time
s=socket.socket()
s.bind(("127.0.0.1",456))
s.listen(5)
s.setblocking(0)
l=[s,]


while True:
    r,w,err=select.select(l,[],[])
    for i in r:
        if s==i:
            conn,addr=s.accept()
            conn.setblocking(0)
            l.append(conn)
        else:
            print(i.recv(1024).decode('utf-8'))
            i.sendall('hello sir'.encode('utf-8'))


debug:

    select監聽的三個列表不能初始內容都爲空,不然後續會報錯(具體緣由待查明)

epoll實現以下:(抄自https://my.oschina.net/SnifferApache/blog/776123)

import socket, select
s=socket.socket()
host="127.0.0.1"
port=8889
s.bind((host,port))

fdmap={s.fileno():s}
s.listen(5)
p=select.poll()
p.register(s)

while	True:
	events=p.poll()
	for fd,event in events:
		if fd==s.fileno():
			c,addr = s.accept()
			print ('got connection from ',addr)
			p.register(c)
			fdmap[c.fileno()] = c
		elif event & select.POLLIN:
			data=fdmap[fd].recv(1024)
			if not data:
				print (fdmap[fd].getpeername(),'disconnected')
				p.unregister(fd)
				del fdmap[fd]
			else:	print (data)

epoll實現以下:(抄自http://scotdoyle.com/python-epoll-howto.html)

import socket, select

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n'
response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n'
response += b'Hello, world!'

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serversocket.bind(('0.0.0.0', 8080))
serversocket.listen(1)
serversocket.setblocking(0)

epoll = select.epoll()
epoll.register(serversocket.fileno(), select.EPOLLIN)

try:
   connections = {}; requests = {}; responses = {}
   while True:
      events = epoll.poll(1)
      for fileno, event in events:
         if fileno == serversocket.fileno():
            connection, address = serversocket.accept()
            connection.setblocking(0)
            epoll.register(connection.fileno(), select.EPOLLIN)
            connections[connection.fileno()] = connection
            requests[connection.fileno()] = b''
            responses[connection.fileno()] = response
         elif event & select.EPOLLIN:
            requests[fileno] += connections[fileno].recv(1024)
            if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
               epoll.modify(fileno, select.EPOLLOUT)
               print('-'*40 + '\n' + requests[fileno].decode()[:-2])
         elif event & select.EPOLLOUT:
            byteswritten = connections[fileno].send(responses[fileno])
            responses[fileno] = responses[fileno][byteswritten:]
            if len(responses[fileno]) == 0:
               epoll.modify(fileno, 0)
               connections[fileno].shutdown(socket.SHUT_RDWR)
         elif event & select.EPOLLHUP:
            epoll.unregister(fileno)
            connections[fileno].close()
            del connections[fileno]
finally:
   epoll.unregister(serversocket.fileno())
   epoll.close()
   serversocket.close()


如今好了,你已經能算好點過來等飯了,可是打飯仍是須要時間呀,因而你:

獻身阿姨,讓阿姨在你來的時候就已經打好飯等你,你只須要取走,這就是所謂的異步io模型

yibu.png

顯然這種模型是最爲高效的,這裏我能力有限不提供代碼,最後推薦看:https://segmentfault.com/a/1190000003063859

相關文章
相關標籤/搜索