環境: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
排隊取餐(發起系統調用)ide
2.打飯阿姨等廚師的飯菜端上來(內核等數據).net
3.油膩的抽風阿姨把飯打給你(內核拷貝數據)
這就是所謂的阻塞i/o模型啦
體如今代碼就跟簡單的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程序運行在用戶態,而接受的數據被客戶端發送到網卡,網卡是硬件,只有內核纔有資格去操做硬件
這種模型會阻塞在兩個地方:
用戶在等待用戶空間有數據(你在等阿姨把飯給你)
系統在等待取得數據(阿姨在等廚師作好飯)
這比如飯還有好久才熟,你卻只能在那等着,不能出去玩,因而你想了個好辦法:
你仍是出去玩,可是隔一段時間回來問下:個人飯熟了嗎?
這個就是所謂的不阻塞io
這裏我利用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複用:
給阿姨留號碼又分兩種:
一種是匿名電話,每次阿姨都要詢問這是哪一個英俊的小夥子的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模型
顯然這種模型是最爲高效的,這裏我能力有限不提供代碼,最後推薦看:https://segmentfault.com/a/1190000003063859