日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

如何在 Asyncio 中使用 Socket

來源: 責編: 時間:2024-01-18 09:38:54 224觀看
導讀楔子本次我們來聊一聊 Socket,以及它如何與 asyncio 搭配使用。阻塞 SocketSocket 是對 TCP/IP 協(xié)議的一個封裝,可以讓我們更方便地使用 TCP/IP 協(xié)議,而不用關注背后的原理。并且我們經(jīng)常使用的 Web 框架,本質(zhì)上也是一個

楔子

6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

本次我們來聊一聊 Socket,以及它如何與 asyncio 搭配使用。6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

阻塞 Socket

6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

Socket 是對 TCP/IP 協(xié)議的一個封裝,可以讓我們更方便地使用 TCP/IP 協(xié)議,而不用關注背后的原理。并且我們經(jīng)常使用的 Web 框架,本質(zhì)上也是一個 Socket。6DJ28資訊網(wǎng)——每日最新資訊28at.com

所以 Socket 是操作系統(tǒng)對 TCP/IP 網(wǎng)絡協(xié)議棧的封裝,并提供了一系列的接口,我們通過這些接口可以實現(xiàn)網(wǎng)絡通信,而不用關注網(wǎng)絡協(xié)議的具體細節(jié)。6DJ28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片6DJ28資訊網(wǎng)——每日最新資訊28at.com

按照現(xiàn)有的網(wǎng)絡模型,Socket 并不屬于其中的任何一層,但我們可以簡單地將 Socket 理解為傳輸層之上的抽象層,負責連接應用層和傳輸層。Socket 提供了大量的 API,基于這些 API 我們可以非常方便地使用網(wǎng)絡協(xié)議棧,在不同主機間進行網(wǎng)絡通信。6DJ28資訊網(wǎng)——每日最新資訊28at.com

Linux 一切皆文件,Socket 也不例外,它被稱為套接字文件,在使用上和普通文件是類似的。6DJ28資訊網(wǎng)——每日最新資訊28at.com

Socket 是什么我們已經(jīng)知道了,下面來看看如何使用 Socket 進行編程。6DJ28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片6DJ28資訊網(wǎng)——每日最新資訊28at.com

整個過程如下:6DJ28資訊網(wǎng)——每日最新資訊28at.com

  • 服務端初始化 socket,此時會得到「主動套接字」;
  • 服務端調(diào)用 bind 方法,將套接字綁定在某個 IP 和端口上;
  • 服務端調(diào)用 listen 進行監(jiān)聽,此時「主動套接字」會變成「監(jiān)聽套接字」;
  • 服務端調(diào)用 accept,等待客戶端連接,此時服務端會阻塞在這里(調(diào)用的是阻塞的 API);
  • 客戶端同樣初始化 socket,得到主動套接字;
  • 客戶端調(diào)用主動套接字的 connect,向服務器端發(fā)起連接請求,如果連接成功,后續(xù)客戶端就用這個主動套接字進行數(shù)據(jù)的傳輸;
  • 當客戶端來連接時,那么服務端的 accept 將不再阻塞,并返回「已連接套接字」,后續(xù)服務端便用這個已連接套接字和客戶端進行數(shù)據(jù)傳輸;
  • 當客戶端來連接時,那么服務端的 accept 將不再阻塞,并返回「已連接套接字」,后續(xù)服務端便用這個已連接套接字和客戶端進行數(shù)據(jù)傳輸;

我們使用來編寫代碼演示一下這個過程,首先是服務端:6DJ28資訊網(wǎng)——每日最新資訊28at.com

import socket# socket.socket() 會返回一個「主動套接字」server = socket.socket(    # 表示使用 IPv4,如果是 socket.AF_INET6    # 則表示使用 IPv6    socket.AF_INET,    # 表示建立 TCP 連接,如果是 socket.SOCK_DGRAM    # 則表示建立 UDP 連接    socket.SOCK_STREAM)# 當然這兩個參數(shù)也可以不傳,因為默認就是它# 設置套接字屬性,這里讓端口釋放后立刻就能再次使用server.setsockopt(socket.SOL_SOCKET,                  socket.SO_REUSEADDR, True)# 將「主動套接字」綁定在某個 IP 和端口上server.bind(("localhost", 12345))# 監(jiān)聽,此時「主動套接字」會變成「監(jiān)聽套接字」server.listen(5)# 調(diào)用 accept,等待客戶端連接,此時會阻塞在這里# 如果客戶端連接到來,那么會返回「已連接套接字」,也就是這里的 conn# 至于 addr 則是一個元組,保存了客戶端連接的信息(IP 和端口)conn, addr = server.accept()# 下面我們通過「已連接套接字」conn 和客戶端進行消息的收發(fā)# 收消息使用 recv、發(fā)消息使用 send,和 read、write 本質(zhì)是一樣的while True:    msg = conn.recv(1024)    # 當客戶端斷開連接時,msg 會收到一個空字節(jié)串    if not msg:        print("客戶端已經(jīng)斷開連接")        conn.close()        break    print("客戶端發(fā)來消息:", msg.decode("utf-8"))    # 然后我們加點內(nèi)容之后,再給客戶端發(fā)過去    conn.send("服務端收到, 你發(fā)的消息是: ".encode("utf-8") + msg)

接下來編寫客戶端:6DJ28資訊網(wǎng)——每日最新資訊28at.com

import socket# 返回主動套接字client = socket.socket(socket.AF_INET,                       socket.SOCK_STREAM)# 連接服務端client.connect(("localhost", 12345))while True:    # 發(fā)送消息    data = input("請輸入內(nèi)容: ")    if data.strip().lower() in ("q", "quit", "exit"):        client.close()        print("Bye~~~")        break    client.send(data.encode("utf-8"))    print(client.recv(1024).decode("utf-8"))

啟動服務端和客戶端進行測試:6DJ28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片6DJ28資訊網(wǎng)——每日最新資訊28at.com

還是比較簡單的,當然我們這里的服務端每次只能和一個客戶端通信,如果想服務多個客戶端的話,那么需要為已連接套接字單獨開一個線程和客戶端進行通信,然后主線程繼續(xù)調(diào)用 accept 方法等待下一個客戶端。6DJ28資訊網(wǎng)——每日最新資訊28at.com

下面來編寫一下多線程的版本,這里只需要編寫服務端即可,客戶端代碼不變。6DJ28資訊網(wǎng)——每日最新資訊28at.com

import socketimport threadingserver = socket.socket()server.setsockopt(socket.SOL_SOCKET,                  socket.SO_REUSEADDR, True)server.bind(("localhost", 12345))server.listen(5)def handle_message(conn, addr):    while True:        msg = conn.recv(1024)        if not msg:            print(f"客戶端(ip: {addr[0]}, port: {addr[1]}) 已經(jīng)斷開連接")            conn.close()            break        print(f"客戶端(ip: {addr[0]}, port: {addr[1]}) 發(fā)來消息:",              msg.decode("utf-8"))        conn.send("服務端收到, 你發(fā)的消息是: ".encode("utf-8") + msg)while True:    conn, addr = server.accept()    threading.Thread(        target=handle_message,        args=(conn, addr)    ).start()

代碼很簡單,就是把已連接套接字和客戶端的通信邏輯寫在了單獨的函數(shù)中,每來一個客戶端,服務端都會啟動一個新的線程去執(zhí)行該函數(shù),然后繼續(xù)監(jiān)聽,等待下一個客戶端連接到來。6DJ28資訊網(wǎng)——每日最新資訊28at.com

然后客戶端代碼不變,我們啟動三個客戶端去和服務端通信,看看結(jié)果如何。6DJ28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片6DJ28資訊網(wǎng)——每日最新資訊28at.com

結(jié)果一切正常,當然我們這里的代碼比較簡單,就是普通的消息收發(fā)。你也可以實現(xiàn)一個更復雜的功能,比如文件下載器,把服務端當成網(wǎng)盤,支持客戶端上傳和下載文件,并不難。6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

非阻塞 Socket

6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

先回顧一下 socket 模型:6DJ28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片6DJ28資訊網(wǎng)——每日最新資訊28at.com

但是注意:我們說在 listen() 這一步,會將主動套接字轉(zhuǎn)化為監(jiān)聽套接字,但此時的監(jiān)聽套接字的類型是阻塞的。阻塞類型的監(jiān)聽套接字在調(diào)用 accept() 方法時,如果沒有客戶端來連接的話,就會一直處于阻塞狀態(tài),那么此時主線程就沒法干其它事情了。6DJ28資訊網(wǎng)——每日最新資訊28at.com

所以要設置為非阻塞,而非阻塞的監(jiān)聽套接字在調(diào)用 accept() 時,如果沒有客戶端來連接,那么主線程不會傻傻地等待,而是會直接返回,然后去做其它的事情。6DJ28資訊網(wǎng)——每日最新資訊28at.com

類似的,我們在創(chuàng)建已連接套接字的時候默認也是阻塞的,阻塞類型的已連接套接字在調(diào)用 send() 和 recv() 的時候也會處于阻塞狀態(tài)。比如當客戶端一直不發(fā)數(shù)據(jù)的時候,已連接套接字就會一直阻塞在 recv() 這一步。如果是非阻塞類型的已連接套接字,那么當調(diào)用 recv() 但卻收不到數(shù)據(jù)時,也不用處于阻塞狀態(tài),同樣可以直接返回去做其它事情。6DJ28資訊網(wǎng)——每日最新資訊28at.com

import socketserver = socket.socket()server.bind(("localhost", 12345))# 調(diào)用 setblocking 方法,傳入 False# 表示將監(jiān)聽套接字和已連接套接字的類型設置為非阻塞server.setblocking(False)server.listen(5)while True:    try:        # 非阻塞的監(jiān)聽套接字調(diào)用 accept() 時        # 如果發(fā)現(xiàn)沒有客戶端連接,則會立刻拋出 BlockingIOError        # 因此這里寫了個死循環(huán)        conn, addr = server.accept()    except BlockingIOError:        pass    else:        breakwhile True:    try:        # 同理,非阻塞的已連接套接字在調(diào)用 recv() 時        # 如果發(fā)現(xiàn)客戶端沒有發(fā)數(shù)據(jù),那么同樣會報錯        msg = conn.recv(1024)    except BlockingIOError:        pass    else:        print(msg.decode("utf-8"))        conn.send(b"data from server")

很明顯,雖然上面的代碼在運行的時候正常,但存在兩個問題:6DJ28資訊網(wǎng)——每日最新資訊28at.com

1)雖然 accept() 不阻塞了,在沒有客戶端連接時主線程可以去做其它事情,但如果后續(xù)有客戶端連接,主線程要如何得知呢?因此必須要有一種機制,能夠繼續(xù)在監(jiān)聽套接字上等待后續(xù)連接請求,并在請求到來時通知主線程。我們上面的做法是寫了一個死循環(huán),但很明顯這是沒有意義的,這種做法還不如使用阻塞的套接字。6DJ28資訊網(wǎng)——每日最新資訊28at.com

2)send() / recv() 不阻塞了,相當于 I/O 讀寫流程不再是阻塞的,讀寫方法都會瞬間完成并返回,也就是說它會采用能讀多少就讀多少、能寫多少就寫多少的策略來執(zhí)行 I/O 操作,這顯然更符合我們對性能的追求。6DJ28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片6DJ28資訊網(wǎng)——每日最新資訊28at.com

顯然對于非阻塞套接字而言,會面臨一個問題,那就是當我們執(zhí)行讀取操作時,有可能只讀了一部分數(shù)據(jù),剩余的數(shù)據(jù)客戶端還沒發(fā)過來,那么這些數(shù)據(jù)何時可讀呢?同理寫數(shù)據(jù)也是這種情況,當緩沖區(qū)滿了,而我們的數(shù)據(jù)還沒有寫完,那么剩下的數(shù)據(jù)又何時可寫呢?因此同樣要有一種機制,能夠在主線程做別的事情的時候繼續(xù)監(jiān)聽已連接套接字,并且在有數(shù)據(jù)可讀寫的時候通知主線程。6DJ28資訊網(wǎng)——每日最新資訊28at.com

這樣才能保證主線程既不會像基本 IO 模型一樣,一直在阻塞點等待,也不會無法處理實際到達的客戶端連接請求和可讀寫的數(shù)據(jù),而上面所提到的機制便是 I/O 多路復用。6DJ28資訊網(wǎng)——每日最新資訊28at.com

早期的所有框架都是非阻塞 + 回調(diào) + 基于 IO 多路復用的事件循環(huán),這種模式的性能也非常高,Redis 和 Nginx 都是基于這種方式實現(xiàn)了高并發(fā)。只是這種編碼方式非常痛苦,它將好端端的自上而下的邏輯分割的四分五裂,而且也不好維護,它使得開發(fā)人員在編寫業(yè)務邏輯的同時,還要關注并發(fā)細節(jié)。6DJ28資訊網(wǎng)——每日最新資訊28at.com

因此使用多路復用 + 回調(diào)的方式編寫異步化代碼,雖然并發(fā)量能上去,但是對開發(fā)者很不友好;而使用同步的方式編寫同步代碼,雖然很容易理解,可并發(fā)量卻又上不去。那么問題來了,有沒有一種辦法,能夠讓我們在享受異步化帶來的高并發(fā)的同時,又能以同步的方式去編寫代碼呢?也就是我們能不能以同步的方式去編寫異步化的代碼呢?6DJ28資訊網(wǎng)——每日最新資訊28at.com

答案是可以的,使用「協(xié)程」便可以辦到。協(xié)程在這種模式的基礎之上又批了一層外衣,兼顧了開發(fā)效率與運行效率。6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

在 asyncio 中使用 Socket

6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

asyncio 的事件循環(huán)提供了處理套接字的一些方法,我們主要會用到三個:6DJ28資訊網(wǎng)——每日最新資訊28at.com

  • sock_accept()
  • sock_recv()
  • sock_sendall()

這些方法類似于前面使用的套接字方法,但不同之處在于,它們需要接收非阻塞套接字作為參數(shù),然后返回協(xié)程。我們可以等待協(xié)程,直到有數(shù)據(jù)可供操作。6DJ28資訊網(wǎng)——每日最新資訊28at.com

先來看一下 sock_accept(),它類似于 server.accept()。6DJ28資訊網(wǎng)——每日最新資訊28at.com

conn,add = await loop.sock_accept(sock)

然后 sock_recv 和 sock_sendall 的調(diào)用方式與 sock_accept 類似,它們接收一個套接字,然后返回協(xié)程對象。通過 await 表達式,sock_recv 將會阻塞,直到套接字有可以處理的字節(jié);sock_sendall 接收一個套接字和要發(fā)送的數(shù)據(jù),同樣會陷入阻塞,直到要發(fā)送給套接字的所有數(shù)據(jù)都發(fā)送完畢,成功時返回 None。6DJ28資訊網(wǎng)——每日最新資訊28at.com

data = await loop.sock_recv(sock)await loop.sock_sendall(sock, data)

下面我們就基于 asyncio 設計一個回顯服務器。6DJ28資訊網(wǎng)——每日最新資訊28at.com

import asyncioimport socketasync def echo(conn: socket.socket):    loop = asyncio.get_running_loop()    # 無限循環(huán)等待來自客戶端連接的數(shù)據(jù)    try:        while data := await loop.sock_recv(conn, 1024):            # 收到數(shù)據(jù)之后再將其發(fā)送給客戶端            # 為了區(qū)分,我們發(fā)送的時候在結(jié)尾加一個 b"~"            await loop.sock_sendall(conn, data + b"~")    except Exception as e:        print(f"服務出錯: {e}")    finally:        conn.close()async def listen_for_conn(server: socket.socket):    loop = asyncio.get_running_loop()    while True:        conn, addr = await loop.sock_accept(server)        conn.setblocking(False)        print(f"收到客戶端 {addr} 的連接")        # 每次連接時,都創(chuàng)建一個任務來監(jiān)聽客戶端的數(shù)據(jù)        asyncio.create_task(echo(conn))async def main():    server = socket.socket()    server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)    server.setblocking(False)    server.bind(("localhost", 12345))    server.listen()    await listen_for_conn(server)asyncio.run(main())

運行這個應用程序可以同時服務多個客戶端,它里面同樣使用了 IO 多路復用,只不過事件循環(huán)將它封裝起來了,我們不需要直接面對。所以這種編程模式就簡單多了。6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

小結(jié)

6DJ28資訊網(wǎng)——每日最新資訊28at.com

6DJ28資訊網(wǎng)——每日最新資訊28at.com

如果使用阻塞套接字創(chuàng)建應用程序,那么阻塞套接字將在等待數(shù)據(jù)時停止整個線程。這阻止了我們實現(xiàn)并發(fā),因為一次只能從一個客戶端獲取數(shù)據(jù)。6DJ28資訊網(wǎng)——每日最新資訊28at.com

使用非阻塞套接字構(gòu)建應用程序,這些套接字總是會立即返回,而結(jié)果有兩種:要么已經(jīng)準備好了數(shù)據(jù),要么因為沒有數(shù)據(jù)而出現(xiàn)異常。6DJ28資訊網(wǎng)——每日最新資訊28at.com

使用 asyncio 的事件循環(huán)方法來構(gòu)建具有非阻塞套接字的應用程序,這些方法接收一個套接字并返回一個協(xié)程,然后可在 await 表達式中使用它。這將暫停父協(xié)程,直到套接字帶有數(shù)據(jù)。事件循環(huán)就是基于 IO 多路復用做的一個封裝,而 IO 多路復用能夠?qū)崿F(xiàn)的前提之一就是:套接字必須是非阻塞的。6DJ28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-64097-0.html如何在 Asyncio 中使用 Socket

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 你知道.NET中的數(shù)組在內(nèi)存中如何布局的嗎?

下一篇: Gorm 框架原理&源碼解析

標簽:
  • 熱門焦點
  • Mate60手機殼曝光 致敬自己的經(jīng)典設計

    8月3日消息,今天下午博主數(shù)碼閑聊站帶來了華為Mate60的第三方手機殼圖,可以讓我們在真機發(fā)布之前看看這款華為全新旗艦的大致輪廓。從曝光的圖片看,Mate 60背后攝像頭面積依然
  • 石頭自清潔掃拖機器人G10S評測:多年黑科技集大成之作 懶人終極福音

    科技圈經(jīng)常能看到一個詞叫“縫合怪”,用來形容那些把好多功能或者外觀結(jié)合在一起的產(chǎn)品,通常這樣的詞是貶義詞,但如果真的是產(chǎn)品縫合的好、縫合的實用的話,那它就成了中性詞,今
  • 6月iOS設備好評榜:第一蟬聯(lián)榜首近一年

    作為安兔兔各種榜單里變化最小的那個,2023年6月的iOS好評榜和上個月相比沒有任何排名上的變化,僅僅是部分設備好評率的下降,長年累月的用戶評價和逐漸退出市場的老款機器讓這
  • 大廠卷向扁平化

    來源:新熵作者丨南枝 編輯丨月見大廠職級不香了。俗話說,兵無常勢,水無常形,互聯(lián)網(wǎng)企業(yè)調(diào)整職級體系并不稀奇。7月13日,淘寶天貓集團啟動了近年來最大的人力制度改革,目前已形成一
  • 東方甄選單飛:有些鳥注定是關不住的

    作者:彭寬鴻來源:華爾街科技眼‍‍‍‍‍‍‍‍‍‍東方甄選創(chuàng)始人俞敏洪帶隊的“7天甘肅行”直播活動已在近日順利收官。成立后一
  • 首發(fā)天璣9200+ iQOO Neo8系列發(fā)布首銷售價2299元起

    2023年5月23日晚,iQOO Neo8系列正式發(fā)布。其中,Neo系列首款Pro之作——iQOO Neo8 Pro強悍登場,限時售價3099元起;價位段最強性能手機iQOO Neo8同期上市
  • 朋友圈可以修改可見范圍了 蘋果用戶可率先體驗

    近日,iOS用戶迎來微信8.0.27正式版更新,除了可更換二維碼背景外,還新增了多項實用功能。在新版微信中,朋友圈終于可以修改可見范圍,簡單來說就是已發(fā)布的朋友圈
  • 微軟發(fā)布Windows 11新版 引入全新任務欄狀態(tài)

    近日,微軟發(fā)布了Windows 11新版,而Build 22563更新主要引入了幾周前曝光的平板模式任務欄等,系統(tǒng)更流暢了。更新中,Windows 11加入了專門針對平板優(yōu)化的任務欄
  • 親歷馬斯克血洗Twitter,硅谷的苦日子在后頭

    文/劉哲銘  編輯/李薇  馬斯克再次揮下裁員大刀。  美國時間11月14日,Twitter約4400名外包員工遭解雇,此次被解雇的員工的主要工作為內(nèi)容審核等。此前,T
Top 主站蜘蛛池模板: 云浮市| 都江堰市| 璧山县| 南靖县| 嘉兴市| 霍林郭勒市| 灵川县| 五台县| 招远市| 忻城县| 中方县| 阳东县| 霞浦县| 苍溪县| 廉江市| 汉川市| 鄂尔多斯市| 米泉市| 旬邑县| 荣昌县| 中宁县| 宁远县| 香港 | 郓城县| 务川| 来安县| 吐鲁番市| 盐山县| 赣州市| 宜川县| 谢通门县| 吉安市| 枣阳市| 孙吴县| 平江县| 丰顺县| 保康县| 静安区| 新和县| 神木县| 青岛市|