python11-IO多路復(fù)用

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ā)
    • socket + select + 多線程
  • 多線程
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ù)有多大
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • IO操作是不占用CPU的,IO多路復(fù)用,是要管理起所有的IO操作。IO多路復(fù)用的典型場景是監(jiān)聽socket對象內(nèi)部...
    極地瑞雪閱讀 665評論 0 1
  • Java程序員進(jìn)階三條必經(jīng)之路:數(shù)據(jù)庫、虛擬機(jī)、異步通信。 前言 從零單排高性能問題,這次輪到異步通信了。這個領(lǐng)域...
    MountainKing閱讀 7,956評論 0 15
  • 上一篇《聊聊同步、異步、阻塞與非阻塞》[http://www.lxweimin.com/p/aed6067eeac...
    七寸知架構(gòu)閱讀 140,506評論 57 445
  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作。那么我們對與外部設(shè)...
    VD2012閱讀 1,025評論 0 2
  • 《平凡之路》 來一場孤獨(dú)的遇見, 去擁抱沉默的邂逅, 看著野草葳蕤, 目睹野花枯萎, 它們有怎樣的故事, 有人會聽...
    林姬閱讀 309評論 2 0