IO多路復(fù)用
- socket在客戶端與服務(wù)端建立連接后,之后的請求都需要等待
- 原生的socket服務(wù)端只能在同一時刻處理一個請求
- IO多路復(fù)用:
- 可以監(jiān)聽多個文件描述符(socket對象),一旦文件描述符的狀態(tài)出現(xiàn)變化,就會感知到
- 一旦有人給服務(wù)器發(fā)送請求,服務(wù)端的socket就會發(fā)生變化
- 或服務(wù)端通過Socket給客戶端發(fā)送數(shù)據(jù),服務(wù)端的socket也會發(fā)生變化
讓socket監(jiān)聽多個端口
- 原生的socket只能監(jiān)聽一個端口
- 通過select模塊實(shí)現(xiàn)socket監(jiān)聽多個端口
- [注]:socket中send和sendall:send發(fā)送的內(nèi)容不一定全部發(fā)送出去,返回值為發(fā)送了多少,sendall底層調(diào)用send,通過循環(huán)把所有的數(shù)據(jù)發(fā)送出去
- IO多路復(fù)用可以接收多個文件描述符,一旦有哪個文件描述符的狀態(tài)發(fā)生變化,就會幫忙處理
import socket
import select
server1 = socket.socket()
ip_port1 = ('127.0.0.1', 8001)
server1.bind(ip_port1)
server1.listen(5)
server2 = socket.socket()
ip_port2 = ('127.0.0.1', 8002)
server2.bind(ip_port2)
server2.listen(5)
print('服務(wù)器啟動.........')
inputs = [server1, server2]
while True:
# r_list:為第一個參數(shù)傳入的列表,一旦這個列表中的文件描述符對象發(fā)生改變,就會把這個對象放入r_list
# w_list:為第二個參數(shù)的列表對象關(guān)聯(lián),第二個參數(shù)的列表里有什么,w_list就有什么
# e_list:為第三個參數(shù)的列表關(guān)聯(lián),一旦這個列表的文件描述符對象發(fā)生異常,就會把異常對象傳入e_list中,通常把異常的文件描述符從監(jiān)聽中移除
# 參數(shù)四:表示每隔多久掃描一次,這里為1秒
r_list, w_list, e_list = select.select(inputs, [], [], 1)
print('r_list大小:%d'%len(r_list))
for sk in r_list:
conn, addr = sk.accept()
print('服務(wù)器接收到請求,客戶端ip: %s .port: %s' % (addr[0], addr[1]))
conn.sendall(bytes('Hello_World!', encoding='utf-8'))
- IO多路復(fù)用跟系統(tǒng)底層有關(guān),由系統(tǒng)底層實(shí)現(xiàn)的,跟python無關(guān),python只是通過select模塊調(diào)用,window系統(tǒng)只支持select
- IO多路復(fù)用發(fā)展
- 計(jì)算機(jī)一開始只有select,大家都用select
- select:性能比較低,底層通過for循環(huán)逐個遍歷,最多支持1024個
- poli:對select優(yōu)化,個數(shù)沒有限制了,但是依然不是并發(fā)的(for循環(huán))
- epoli:內(nèi)部不再是for循環(huán),通過異步的方式,哪個文件描述符發(fā)生變化,主動告訴系統(tǒng)
socket實(shí)現(xiàn)可以接收多個客戶端訪問
import socket
import select
sk1 = socket.socket()
ip_port1 = ('127.0.0.1', 8001)
sk1.bind(ip_port1)
sk1.listen(5)
print('服務(wù)端啟動...........')
inputs = [sk1, ]
# 一旦有客戶端訪問服務(wù)端,服務(wù)端通過accept()獲取到客戶端的socket(conn)放入inputs列表中進(jìn)行監(jiān)聽
while True:
r_list, w_list, e_list = select.select(inputs, [], [], 1)
for sk in r_list:
if sk == sk1:
# 一旦有人訪問,sk1就會發(fā)生變化
conn, addr = sk.accept()
conn.sendall(bytes('Hello_World', encoding='utf-8'))
# 把客戶端的socket(conn)方法inputs監(jiān)聽,一旦客戶端發(fā)送數(shù)據(jù)過來,conn會發(fā)生變化
inputs.append(conn)
else:
# conn發(fā)生變化
try:
recv_bytes = sk.recv(1024)
except Exception as e:
inputs.remove(sk)
print(e)
else:
recv_str = str(recv_bytes, encoding='utf-8')
print(recv_str)
sk.sendall(bytes("回復(fù):" + recv_str, encoding='utf-8'))
finally:
pass
- 在python2.7中,如果客戶端斷開了連接,默認(rèn)會發(fā)送一個空值給服務(wù)端,服務(wù)端通過:if rev_bytes來判斷是否斷開連接,移除監(jiān)聽
- python3.x中,客戶端斷開連接時變成拋異常,通過try來處理斷開的邏輯
- 上面的操作沒有實(shí)現(xiàn)并發(fā),比socket的優(yōu)勢是可以處理多個請求,但不是并發(fā)進(jìn)行,通過for循環(huán)
select實(shí)現(xiàn)讀寫分離
import socket
import select
sk1 = socket.socket()
ip_port1 = ('127.0.0.1', 8001)
sk1.bind(ip_port1)
sk1.listen(5)
print('服務(wù)端啟動...........')
inputs = [sk1, ]
outputs = []
message_dict = {}
# 一旦有客戶端訪問服務(wù)端,服務(wù)端通過accept()獲取到客戶端的socket(conn)放入inputs列表中進(jìn)行監(jiān)聽
while True:
r_list, w_list, e_list = select.select(inputs, outputs, [], 1)
for sk in r_list:
if sk == sk1:
# 一旦有人訪問,sk1就會發(fā)生變化
conn, addr = sk.accept()
conn.sendall(bytes('Hello_World', encoding='utf-8'))
# 把客戶端的socket(conn)方法inputs監(jiān)聽,一旦客戶端發(fā)送數(shù)據(jù)過來,conn會發(fā)生變化
inputs.append(conn)
message_dict[conn] = []
else:
# conn發(fā)生變化
try:
recv_bytes = sk.recv(1024)
except Exception as e:
inputs.remove(sk)
print(e)
else:
recv_str = str(recv_bytes, encoding='utf-8')
message_dict[sk].append(recv_str)
outputs.append(sk)
finally:
pass
print(w_list)
for conn in w_list:
recv_str = message_dict[conn].pop()
print(recv_str)
conn.sendall(bytes("回復(fù):" + recv_str, encoding='utf-8'))
outputs.clear()
梳理
- select以后基本不會用到,但是這個所有的網(wǎng)絡(luò)通信的根本,很多源碼都會有,如果不懂,則看源碼的時候會很吃力
- socketserver真正實(shí)現(xiàn)了并發(fā)
- 多線程
import threading
import time
def process(arg):
print(arg)
time.sleep(1)
# 如果這樣子執(zhí)行,會執(zhí)行10秒
for i in rang(10):
process(i)
# 通過多線程,瞬間完成
for i in rang(10):
t = threading.Thread(target:process,arg=i)
t.start()
- socket通信技巧
- socket在send和recv都有大小限制
- 如果傳輸人的數(shù)據(jù)超過1024,一次接收不完,需要接收多次,如何知道要接收多層次,這就要在發(fā)送之前告訴另一端數(shù)據(jù)有多大