<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ### 導航 - [索引](../genindex.xhtml "總目錄") - [模塊](../py-modindex.xhtml "Python 模塊索引") | - [下一頁](sorting.xhtml "排序指南") | - [上一頁](regex.xhtml "正則表達式HOWTO") | - ![](https://box.kancloud.cn/a721fc7ec672275e257bbbfde49a4d4e_16x16.png) - [Python](https://www.python.org/) ? - zh\_CN 3.7.3 [文檔](../index.xhtml) ? - [Python 常用指引](index.xhtml) ? - $('.inline-search').show(0); | # 套接字編程指南 作者Gordon McMillan 摘要 套接字幾乎無處不在,但是它卻是被誤解最嚴重的技術之一。這是一篇簡單的套接字概述。并不是一篇真正的教程 —— 你需要做更多的事情才能讓它工作起來。其中也并沒有涵蓋細節(細節會有很多),但是我希望它能提供足夠的背景知識,讓你像模像樣的開始使用套接字 ## 套接字 我將只討論關于 INET(比如:IPv4 地址族)的套接字,但是它將覆蓋幾乎 99% 的套接字使用場景。并且我將僅討論 STREAM(比如:TCP)類型的套接字 - 除非你真的知道你在做什么(那么這篇 HOWTO 可能并不適合你),使用 STREAM 類型的套接字將會得到比其它類型更好的表現與性能。我將嘗試揭開套接字的神秘面紗,也會講到一些阻塞與非阻塞套接字的使用。但是我將以阻塞套接字為起點開始討論。只有你了解它是如何工作的以后才能處理非阻塞套接字。 理解這些東西的難點之一在于「套接字」可以表示很多微妙差異的東西,這取決于上下文。所以首先,讓我們先分清楚「客戶端」套接字和「服務端」套接字之間的不同,客戶端套接字表示對話的一端,服務端套接字更像是總機接線員。客戶端程序只能(比如:你的瀏覽器)使用「客戶端」套接字;網絡服務器則可以使用「服務端」套接字和「客戶端」套接字來會話 ### 歷史 目前為止,在各種形式的 IPC 中,套接字是最流行的。在任何指定的平臺上,可能會有其它更快的 IPC 形式,但是就跨平臺通信來說,套接字大概是唯一的玩法 套接字做為 BSD Unix 操作系統的一部分在伯克利誕生,像野火一樣在因特網傳播。有一個很好的原因 —— 套接字與 INET 的結合使得與世界各地的任意機器間通信變得令人難以置信的簡單(至少對比與其他方案來說) ## 創建套接字 簡略地說,當你點擊帶你來到這個頁面的鏈接時,你的瀏覽器就已經做了下面這幾件事情: ``` # create an INET, STREAMing socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # now connect to the web server on port 80 - the normal http port s.connect(("www.python.org", 80)) ``` 當連接完成,套接字可以用來發送請求來接收頁面上顯示的文字。同樣是這個套接字也會用來讀取響應,最后再被銷毀。是的,被銷毀了。客戶端套接字通常用來做一次交換(或者說一小組序列的交換)。 網絡服務器發生了什么這個問題就有點復雜了。首頁,服務器創建一個「服務端套接字」: ``` # create an INET, STREAMing socket serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # bind the socket to a public host, and a well-known port serversocket.bind((socket.gethostname(), 80)) # become a server socket serversocket.listen(5) ``` 有幾件事需要注意:我們使用了 `socket.gethostname()`,所以套接字將外網可見。如果我們使用的是 `s.bind(('localhost', 80))` 或者 `s.bind(('127.0.0.1', 80))`,也會得到一個「服務端」套接字,但是后者只在同一機器上可見。`s.bind(('', 80))` 則指定套接字可以被機器上的任何地址碰巧連接 第二個需要注點是:低端口號通常被一些「常用的」服務(HTTP, SNMP 等)所保留。如果你想把程序跑起來,最好使用一個高位端口號(通常是4位的數字)。 最后,`listen` 方法的參數會告訴套接字庫,我們希望在拒絕外部請求連接前最多使用 5 個連接請求的隊列。如果所有的代碼都要正確的寫出來,代碼量將會很大。 現在我們已經有一個「服務端」套接字,監聽了 80 端口,我們可以進入網絡服務器的主循環了: ``` while True: # accept connections from outside (clientsocket, address) = serversocket.accept() # now do something with the clientsocket # in this case, we'll pretend this is a threaded server ct = client_thread(clientsocket) ct.run() ``` 事際上,通常有 3 種方法可以讓這個循環工作起來 - 調度一個線程來處理 `客戶端套接字`,或者把這個應用改成使用非阻塞模式套接字,亦或是使用 `select` 庫來實現「服務端」套接字與任意活動 `客戶端套接字` 之間的多路復用。稍后會詳細介紹。現在最重要的是理解:這就是一個 *服務端* 套接字做的 *所有* 事情。它并沒有發送任何數據。也沒有接收任何數據。它只創建「客戶端」套接字。每個 `客戶端套接字` 都是為了響應某些其它客戶端套接字 `connect()` 到我們綁定的主機。一旦創建 `客戶端套接字` 完成,就會返回并監聽更多的連接請求。現個客戶端可以隨意通信 - 它們使用了一些動態分配的端口,會話結束時端口才會被回收 ### 進程間通信 如果你需要在同一臺機器上進行兩個進程間的快速 IPC 通信,你應該了解管道或者共享內存。如果你決定使用 AF\_INET 類型的套接字,綁定「服務端」套接字到 `'localhost'` 。在大多數平臺,這將會使用一個許多網絡層間的通用快捷方式(本地回環地址)并且速度會快很多 參見 [`multiprocessing`](../library/multiprocessing.xhtml#module-multiprocessing "multiprocessing: Process-based parallelism.") 模塊使跨平臺 IPC 通信成為一個高層的 API ## 使用一個套接字 首先需要注意,瀏覽器的「客戶端」套接字和網絡服務器的「客戶端」套接字是極為相似的。即這種會話是「點對點」的。或者也可以說 *你作為設計師需要自行決定會話的規則和禮節* 。通常情況下,`連接` 套接字通過發送一個請求或者信號來開始一次會話。但這屬于設計決定,并不是套接字規則。 現在有兩組用于通信的動詞。你可以使用 `send` 和 `recv` ,或者你可以把客戶端套接字改成文件類型的形式來使用 `read` 和 `write` 方法。后者是 Java 語言中表示套接字的方法,我將不會在這兒討論這個,但是要提醒你需要調用套接字的 `flush` 方法。這些是“緩沖”的文件,一個經常出現的錯誤是 `write` 一些東西,然后就直接開始 `read` 一個響應。如果不調用 `flush` ,你可能會一直等待這個響應,因為請求可能還在你的輸出緩沖中。 現在我來到了套接字的兩個主要的絆腳石 - `send` 和 `recv` 操作網絡緩沖區。它們并不一定可以處理所有你想要(期望)的字節,因為它們主要關注點是處理網絡緩沖。通常,它們在關聯的網絡緩沖區 `send` 或者清空 `recv` 時返回。然后告訴你處理了多少個字節。*你* 的責任是一直調用它們直到你所有的消息處理完成。 當 `recv` 方法返回 0 字節時,就表示另一端已經關閉(或者它所在的進程關閉)了連接。你再也不能從這個連接上獲取到任何數據了。你可以成功的發送數據;我將在后面討論這一點。 像 HTTP 這樣的協議只使用一個套接字進行一次傳輸。客戶端發送一個請求,然后讀取響應。就這么簡單。套接字會被銷毀。 表示客戶端可以通過接收 0 字節序列表示檢測到響應的結束。 但是如果你打算在隨后來的傳輸中復用套接字的話,你需要明白 *套接字里面是不存在 :abbr:`EOT (傳輸結束)`* 的。重復一下:套接字 `send` 或者 `recv` 完 0 字節后返回,連接會中斷。如果連接沒有被斷開,你可能會永遠處于等待 `recv` 的狀態,因為(就目前來說)套接字 *不會* 告訴你不用再讀取了。現在如果你細心一點,你可能會意識到套接字基本事實:*消息必須要么具有固定長度,要么可以界定,要么指定了長度(比較好的做法),要么以關閉連接為結束*。選擇完全由你而定(這比讓別人定更合理)。 假定你不希望結束連接,那么最簡單的解決方案就是使用定長消息: ``` class MySocket: """demonstration class only - coded for clarity, not efficiency """ def __init__(self, sock=None): if sock is None: self.sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM) else: self.sock = sock def connect(self, host, port): self.sock.connect((host, port)) def mysend(self, msg): totalsent = 0 while totalsent < MSGLEN: sent = self.sock.send(msg[totalsent:]) if sent == 0: raise RuntimeError("socket connection broken") totalsent = totalsent + sent def myreceive(self): chunks = [] bytes_recd = 0 while bytes_recd < MSGLEN: chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048)) if chunk == b'': raise RuntimeError("socket connection broken") chunks.append(chunk) bytes_recd = bytes_recd + len(chunk) return b''.join(chunks) ``` 發送分部代碼幾乎可用于任何消息傳遞方案 —— 在 Python 中你發送字符串,可以使用 `len()` 方法來確定它的長度(即使它嵌入了 `\0` 字符),主要是接收代碼變得更復雜。(在 C 語言中,并沒有更糟糕,除非消息嵌入了 `\0` 字符而且你又無法使用 `strlen` ) 最簡單的改進是讓消息的第一個字符表示消息類型,由類型決定長度。現在你需要兩次 `recv`- 第一次取(至少)第一個字符來知曉長度,第二次在循環中獲取剩余所有的消息。如果你決定到分界線,你將收到一些任意大小的塊,(4096 或者 8192 通常是比較合適的網絡緩沖區大小),掃描你接收到的分界符 一個需要意識到的復雜情況是:如果你的會話協議允許多個消息被發送回來(沒有響應),調用 `recv` 傳入任意大小的塊,你可能會因為讀到后續接收的消息而停止讀取。你需要將它放在一邊并保存,直到它需要為止。 以其長度(例如,作為5個數字字符)作為消息前綴時會變得更復雜,因為(信不信由你)你可能無法在一個 `recv` 中獲得所有5個字符。在一般使用時,你會僥幸避免該狀況;但是在高網絡負載中,除非你使用兩個 `recv` 循環,否則你的代碼將很快中斷 —— 第一個用于確定長度,第二個用于獲取消息的數據部分。這很討厭。當你發現 `send` 并不總是設法在支持搞定一切時,你也會有這種感覺。 盡管已經閱讀過這篇文章,但最終還是會有所了解! 限于篇幅,建立你的角色,(保持與我的競爭位置),這些改進將留給讀者做為練習。現在讓我們繼續。 ### 二進制數據 通過套接字傳送二進制數據是可行的。主要問題在于并非所有機器都用同樣的二進制數據格式。比如 Motorola 芯片用兩個十六進制字節 00 01 來表示一個 16 位整數值 1。而 Intel 和 DEC 則會做字節反轉 —— 即用 01 00 來表示 1。套接字庫要求轉換 16 位和 32 位整數 —— `ntohl, htonl, ntohs, htons` 其中的「n」表示 *network*,「h」表示 *host*,「s」表示 *short*,「l」表示 *long*。在網絡序列就是主機序列時它們什么都不做,但是如果機器是字節反轉的則會適當地交換字節序。 在現今的 32 位機器中,二進制數據的 ascii 表示往往比二進制表示要小。這是因為在非常多的時候所有 long 的值均為 0 或者 1。字符串形式的 "0" 為兩個字節,而二進制形式則為四個。當然這不適用于固定長度的信息。自行決定,請自行決定。 ## 斷開連接 嚴格地講,你應該在 `close` 它之前將套接字 `shutdown` 。 `shutdown` 是發送給套接字另一端的一種建議。調用時參數不同意義也不一樣,它可能意味著「我不會再發送了,但我仍然會監聽」,或者「我沒有監聽了,真棒!」。然而,大多數套接字庫或者程序員都習慣了忽略使用這種禮節,因為通常情況下 `close` 與 `shutdown(); close()` 是一樣的。所以在大多數情況下,不需要顯式的 `shutdown` 。 高效使用 `shutdown` 的一種方法是在類似 HTTP 的交換中。客戶端發送請求,然后執行 `shutdown(1)` 。 這告訴服務器“此客戶端已完成發送,但仍可以接收”。服務器可以通過接收 0 字節來檢測 “EOF” 。它可以假設它有完整的請求。服務器發送回復。如果 `send` 成功完成,那么客戶端仍在接收。 Python 進一步自動關閉,并說當一個套接字被垃圾收集時,如果需要它會自動執行 `close` 。但依靠這個機制是一個非常壞的習慣。如果你的套接字在沒有 `close` 的情況下就消失了,那么另一端的套接字可能會無限期地掛起,以為你只是慢了一步。完成后 *請*`close` 你的套接字。 ### 套接字何時銷毀 使用阻塞套接字最糟糕的事情可能就是當另一邊下線時(沒有 `close` )會發生什么。你的套接字可能會掛起。 TCP 是一種可靠的協議,它會在放棄連接之前等待很長時間。如果你正在使用線程,那么整個線程基本上已經死了。你無能為力。只要你沒有做一些愚蠢的事情,比如在進行阻塞讀取時持有一個鎖,那么線程并沒有真正消耗掉資源。 *不要* 嘗試殺死線程 —— 線程比進程更有效的部分原因是它們避免了與自動回收資源相關的開銷。換句話說,如果你設法殺死線程,你的整個進程很可能被搞壞。 ## 非阻塞的套接字 如果你已理解上述內容,那么你已經了解了使用套接字的機制所需了解的大部分內容。你仍將以相同的方式使用相同的函數調用。 只是,如果你做得對,你的應用程序幾乎是由內到外的。 在 Python 中,使用 `socket.setblocking(0)` 使其無阻塞。 在 C 中,它更復雜,(一方面,你需要在 BSD 風格的 `O_NONBLOCK` 和幾乎無法區分的 Posix 風格的 `O_NDELAY` 之間做出選擇,這與 `TCP_NODELAY` 完全不同。) 。但這是完全相同的想法。你可以在創建套接字之后但在使用之前執行此操作。 (實際上,如果你瘋了,你可以來回切換。) 主要的機制差異是 `send` 、 `recv` 、 `connect` 和 `accept` 可以在沒有做任何事情的情況下返回。 你(當然)有很多選擇。你可以檢查返回代碼和錯誤代碼,通常會讓自己發瘋。如果你不相信我,請嘗試一下。你的應用程序將變得越來越大、越來越 Bug 、吸干 CPU。因此,讓我們跳過腦死亡的解決方案并做正確的事。 使用 `select` 庫 在 C 中,編碼 `select` 相當復雜。 在 Python 中,它是很簡單,但它與 C 版本足夠接近,如果你在 Python 中理解 `select` ,那么在 C 中你會幾乎不會遇到麻煩: ``` ready_to_read, ready_to_write, in_error = \ select.select( potential_readers, potential_writers, potential_errs, timeout) ``` 你傳遞給 `select` 三個列表:第一個包含你可能想要嘗試讀取的所有套接字;第二個是你可能想要嘗試寫入的所有套接字,以及要檢查錯誤的最后一個(通常為空)。你應該注意,套接字可以進入多個列表。 `select` 調用是阻塞的,但你可以給它一個超時。這通常是一件明智的事情 —— 給它一個很長的超時(比如一分鐘),除非你有充分的理由不這樣做。 作為返回,你將獲得三個列表。它們包含實際可讀、可寫和有錯誤的套接字。 這些列表中的每一個都是你傳入的相應列表的子集(可能為空)。 如果一個套接字在輸出可讀列表中,那么你可以像我們一樣接近這個業務,那個套接字上的 `recv` 將返回 *一些內容* 。可寫列表的也相同,你將能夠發送 *一些內容* 。 也許不是你想要的全部,但 *有些東西* 比沒有東西更好。 (實際上,任何合理健康的套接字都將以可寫方式返回 —— 它只是意味著出站網絡緩沖區空間可用。) 如果你有一個“服務器”套接字,請將其放在 potential\_readers 列表中。如果它出現在可讀列表中,那么你的 `accept` (幾乎肯定)會起作用。如果你已經創建了一個新的套接字 `connect` 其他人,請將它放在 potential\_writers 列表中。如果它出現在可寫列表中,那么它有可能已連接。 實際上,即使使用阻塞套接字, `select` 也很方便。這是確定是否阻塞的一種方法 —— 當緩沖區中存在某些內容時,套接字返回為可讀。然而,這仍然無助于確定另一端是否完成或者只是忙于其他事情的問題。 **可移植性警告** :在 Unix 上, `select` 適用于套接字和文件。 不要在 Windows 上嘗試。在 Windows 上, `select` 僅適用于套接字。另請注意,在 C 中,許多更高級的套接字選項在 Windows 上的執行方式不同。事實上,在 Windows 上我通常在使用我的套接字使用線程(非常非常好)。 ### 導航 - [索引](../genindex.xhtml "總目錄") - [模塊](../py-modindex.xhtml "Python 模塊索引") | - [下一頁](sorting.xhtml "排序指南") | - [上一頁](regex.xhtml "正則表達式HOWTO") | - ![](https://box.kancloud.cn/a721fc7ec672275e257bbbfde49a4d4e_16x16.png) - [Python](https://www.python.org/) ? - zh\_CN 3.7.3 [文檔](../index.xhtml) ? - [Python 常用指引](index.xhtml) ? - $('.inline-search').show(0); | ? [版權所有](../copyright.xhtml) 2001-2019, Python Software Foundation. Python 軟件基金會是一個非盈利組織。 [請捐助。](https://www.python.org/psf/donations/) 最后更新于 5月 21, 2019. [發現了問題](../bugs.xhtml)? 使用[Sphinx](http://sphinx.pocoo.org/)1.8.4 創建。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看