天天看點

《Python核心程式設計(第3版)》——2.3 套接字:通信端點

本節書摘來自異步社群《python核心程式設計(第3版)》一書中的第2章,第2.3節,作者[美] wesley chun(衛斯理 春),孫波翔 李斌 李晗 譯,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

本節将介紹套接字(socket),給出有關其起源的一些背景知識,并讨論各種類型的套接字。最後,将講述如何利用它們使運作在不同(或相同)計算機上的程序互相通信。

套接字是計算機網絡資料結構,它展現了上節中所描述的“通信端點”的概念。在任何類型的通信開始之前,網絡應用程式必須建立套接字。可以将它們比作電話插孔,沒有它将無法進行通信。

套接字的起源可以追溯到20世紀70年代,它是加利福尼亞大學的伯克利版本unix(稱為bsd unix)的一部分。是以,有時你可能會聽過将套接字稱為伯克利套接字或bsd套接字。套接字最初是為同一主機上的應用程式所建立,使得主機上運作的一個程式(又名一個程序)與另一個運作的程式進行通信。這就是所謂的程序間通信(inter process communication,ipc)。有兩種類型的套接字:基于檔案的和面向網絡的。

unix套接字是我們所講的套接字的第一個家族,并且擁有一個“家族名字”af_unix(又名af_local,在posix1.g标準中指定),它代表位址家族(address family):unix。包括python在内的大多數受歡迎的平台都使用術語位址家族及其縮寫af;其他比較舊的系統可能會将位址家族表示成域(domain)或協定家族(protocol family),并使用其縮寫pf而非af。類似地,af_local(在2000~2001年标準化)将代替af_unix。然而,考慮到後向相容性,很多系統都同時使用二者,隻是對同一個常數使用不同的别名。python本身仍然在使用af_unix。

因為兩個程序運作在同一台計算機上,是以這些套接字都是基于檔案的,這意味着檔案系統支援它們的底層基礎結構。這是能夠說得通的,因為檔案系統是一個運作在同一主機上的多個程序之間的共享常量。

第二種類型的套接字是基于網絡的,它也有自己的家族名字af_inet,或者位址家族:網際網路。另一個位址家族af_inet6用于第6版網際網路協定(ipv6)尋址。此外,還有其他的位址家族,這些要麼是專業的、過時的、很少使用的,要麼是仍未實作的。在所有的位址家族之中,目前af_inet是使用得最廣泛的。

python 2.5中引入了對特殊類型的linux套接字的支援。套接字的af_netlink家族(無連接配接[見2.3.3節])允許使用标準的bsd套接字接口進行使用者級别和核心級别代碼之間的ipc。之前那種解決方案比較麻煩,而這個解決方案可以看作一種比前一種更加優雅且風險更低的解決方案,例如,添加新系統調用、/proc支援,或者對一個作業系統的“ioctl”。

針對linux的另一種特性(python 2.6中新增)就是支援透明的程序間通信(tipc)協定。tipc允許計算機叢集之中的機器互相通信,而無須使用基于ip的尋址方式。python對tipc的支援以af_tipc家族的方式呈現。

總的來說,python隻支援af_unix、af_netlink、af_tipc和af_inet家族。因為本章重點讨論網絡程式設計,是以在本章剩餘的大部分内容中,我們将使用af_inet。

如果一個套接字像一個電話插孔——允許通信的一些基礎設施,那麼主機名和端口号就像區号和電話号碼的組合。然而,擁有硬體和通信的能力本身并沒有任何好處,除非你知道電話打給誰以及如何撥打電話。一個網絡位址由主機名和端口号對組成,而這是網絡通信所需要的。此外,并未事先說明必須有其他人在另一端接聽;否則,你将聽到這個熟悉的聲音“對不起,您所撥打的電話是空号,請核對後再撥”。你可能已經在浏覽網頁的過程中見過一個網絡類比,例如“無法連接配接伺服器,伺服器沒有響應或者伺服器不可達。”

1.面向連接配接的套接字

不管你采用的是哪種位址家族,都有兩種不同風格的套接字連接配接。第一種是面向連接配接的,這意味着在進行通信之前必須先建立一個連接配接,例如,使用電話系統給一個朋友打電話。這種類型的通信也稱為虛拟電路或流套接字。

面向連接配接的通信提供序列化的、可靠的和不重複的資料傳遞,而沒有記錄邊界。這基本上意味着每條消息可以拆分成多個片段,并且每一條消息片段都確定能夠到達目的地,然後将它們按順序組合在一起,最後将完整消息傳遞給正在等待的應用程式。

實作這種連接配接類型的主要協定是傳輸控制協定(更為人熟知的是它的縮寫tcp)。為了建立tcp套接字,必須使用sock_stream作為套接字類型。tcp套接字的名字sock_stream基于流套接字的其中一種表示。因為這些套接字(af_inet)的網絡版本使用網際網路協定(ip)來搜尋網絡中的主機,是以整個系統通常結合這兩種協定(tcp和ip)來進行(當然,也可以使用tcp和本地[非網絡的af_local/af_unix]套接字,但是很明顯此時并沒有使用ip)。

2.無連接配接的套接字

與虛拟電路形成鮮明對比的是資料報類型的套接字,它是一種無連接配接的套接字。這意味着,在通信開始之前并不需要建立連接配接。此時,在資料傳輸過程中并無法保證它的順序性、可靠性或重複性。然而,資料報确實儲存了記錄邊界,這就意味着消息是以整體發送的,而并非首先分成多個片段,例如,使用面向連接配接的協定。

使用資料報的消息傳輸可以比作郵政服務。信件和包裹或許并不能以發送順序到達。事實上,它們可能不會到達。為了将其添加到并發通信中,在網絡中甚至有可能存在重複的消息。

既然有這麼多副作用,為什麼還使用資料報呢(使用流套接字肯定有一些優勢)?由于面向連接配接的套接字所提供的保證,是以它們的設定以及對虛拟電路連接配接的維護需要大量的開銷。然而,資料報不需要這些開銷,即它的成本更加“低廉”。是以,它們通常能提供更好的性能,并且可能适合一些類型的應用程式。

實作這種連接配接類型的主要協定是使用者資料報協定(更為人熟知的是其縮寫udp)。為了建立udp套接字,必須使用sock_dgram作為套接字類型。你可能知道,udp套接字的sock_dgram名字來自于單詞“datagram”(資料報)。因為這些套接字也使用網際網路協定來尋找網絡中的主機,是以這個系統也有一個更加普通的名字,即這兩種協定(udp和ip)的組合名字,或udp/ip。