很早以前研究過c#和c++的網絡通信,參考我的文章:
python網絡程式設計也類似。同時最近找工作筆試面試考察socket套接字、tcp\udp差別比較多,是以這篇文章主要精簡了《python核心程式設計(第二版)》第16章内容。内容包括:伺服器和用戶端架構、套接字socket、tcp\udp通信執行個體和常見筆試考題。
最後希望文章對你有所幫助,如果有不足之處,還請海涵~
1.什麼是用戶端/服務區架構?
書中的定義是伺服器是一個軟體或硬體,用于向一個或多個用戶端(客戶)提供所需要的“服務”。伺服器存在的唯一目的就是等待客戶的請求,給這些客戶服務,然後再等待其他的請求。而客戶連接配接上(預先已知的)伺服器,提出自己的請求,發送必要的資料,然後等待伺服器完成請求或說明失敗原因的回報。
伺服器不停的處理外來的請求,而客戶一次隻能提出一個服務的請求,等待結果。再結束這個事務。客戶之後可以再提出其他的請求,隻是這個請求會被視為另一個不同的事務了。
2.硬體用戶端/伺服器架構和軟體用戶端/伺服器架構
硬體的用戶端/伺服器架構,例如列印伺服器、檔案伺服器(客戶可以遠端把伺服器的磁盤映射到自己本體并使用);軟體用戶端/伺服器架構主要是程式的運作、資料收發、更新等,最常見的是web伺服器、資料庫伺服器。如一台機器存放一些網頁或web應用程式,然後啟動服務。其伺服器的任務就是接受用戶端的請求,把網頁發給用戶端(如使用者計算機上的浏覽器),然後再等待下一個用戶端請求。
3.用戶端/伺服器網絡程式設計
在完成服務之前,伺服器必須要先完成一些設定。先要先建立一個通訊端點,讓伺服器能“監聽”請求。你可以把我們伺服器比作一個公司的接待員或回答公司總線電話的話務員,一旦電話和裝置安裝完成,話務員也就到位後,服務就開始了。
同樣一旦通信端點建立好之後,我們在“監聽”的伺服器就可以進入它那等待和處理客戶請求的無限循環中了。伺服器準備好之後,也要通知潛在的客戶,讓它們知道伺服器已經準備好處理服務了,否則沒人會提請求的。是以需要把公司電話公開給客戶。
而用戶端隻要建立一個通信端點,建立到伺服器的連接配接,然後用戶端就可以提出請求了。請求中也可以包含必要的資料互動。一旦請求處理完成,用戶端收到了結果,通信就結束了。這就是用戶端和伺服器的簡單網絡通信。
1.什麼是套接字
套接字是一種具有之前所說的“通信端點”概念的計算網絡資料結構。相當于電話插口,沒它無法通信,這個比喻非常形象。
套接字起源于20世紀70年代加州伯克利分校版本的unix,即bsd unix。又稱為“伯克利套接字”或“bsd套接字”。最初套接字被設計用在同一台主機上多個應用程式之間的通訊,這被稱為程序間通訊或ipc。
套接字分兩種:基于檔案型和基于網絡的
第一個套接字家族為af_unix,表示“位址家族:unix”。包括python在内的大多數流行平台上都使用術語“位址家族”及其縮寫af。由于兩個程序都運作在同一台機器上,而且這些套接字是基于檔案的,是以它們的底層結構是由檔案系統來支援的。可以了解為同一台電腦上,檔案系統确實是不同的程序都能進行通路的。
第二個套接字家族為af_inet,表示”位址家族:internet“。還有一種位址家族af_inet6被用于網際協定ipv6尋址。python 2.5中加入了一種linux套接字的支援:af_netlink(無連接配接)套接字家族,讓使用者代碼與核心代碼之間的ipc可以使用标準bsd套接字接口,這種方法更為精巧和安全。
python隻支援af_unix、af_netlink和af_inet家族。網絡程式設計關注af_inet。
如果把套接字比作電話的檢視——即通信的最底層結構,那主機與端口就相當于區号和電話号碼的一對組合。一個網際網路位址由網絡通信必須的主機與端口組成。
而且另一端一定要有人接聽才行,否則會提示”對不起,您撥打的電話是空号,請查詢後再撥“。同樣你也可能會遇到如”不能連接配接該伺服器、伺服器無法響應“等。合法的端口範圍是0~65535,其中小于1024端口号為系統保留端口。
2.面向連接配接與無連接配接
面向連接配接:通信之前一定要建立一條連接配接,這種通信方式也被成為”虛電路“或”流套接字“。面向連接配接的通信方式提供了順序的、可靠地、不會重複的資料傳輸,而且也不會被加上資料邊界。這意味着,每發送一份資訊,可能會被拆分成多份,每份都會不多不少地正确到達目的地,然後重新按順序拼裝起來,傳給正等待的應用程式。
實作這種連接配接的主要協定就是傳輸控制協定tcp。要建立tcp套接字就得建立時指定套接字類型為sock_stream。tcp套接字這個類型表示它作為流套接字的特點。由于這些套接字使用網際協定ip來查找網絡中的主機,是以這樣形成的整個系統,一般會由這兩個協定(tcp和ip)組合描述,即tcp/ip。
無連接配接:無需建立連接配接就可以通訊。但此時,資料到達的順序、可靠性及不重複性就無法保障了。資料報會保留資料邊界,這就表示資料是整個發送的,不會像面向連接配接的協定先拆分成小塊。它就相當于郵政服務一樣,郵件和包裹不一定按照發送順序達到,有的甚至可能根本到達不到。而且網絡中的封包可能會重複發送。
那麼這麼多缺點,為什麼還要使用它呢?由于面向連接配接套接字要提供一些保證,需要維護虛電路連接配接,這都是嚴重的額外負擔。資料報沒有這些負擔,所有它會更”便宜“,通常能提供更好的性能,更适合某些場合,如現場直播要求的實時資料講究快等。
實作這種連接配接的主要協定是使用者資料報協定udp。要建立udp套接字就得建立時指定套接字類型為sock_dgram。這個名字源于datagram(資料報),這些套接字使用網際協定來查找網絡主機,整個系統叫udp/ip。
3.socket()子產品函數
使用socket子產品的socket()函數來建立套接字。文法如下:
socket(socket_family, socket_type, protocol=0)
其中socket_family不是af_vnix就是af_inet,socket_type可以是sock_stream或者sock_dgram,protocol一般不填,預設值是0。
建立一個tcp/ip套接字的文法如下:
tcpsock = socket.socket(socket.af_inet, socket.sock_stream)
同樣建立一個udp/ip套接字的文法如下:
udpsock = socket.socket(socket.af_inet, socket.sock_dgram)
由于socket子產品中有太多屬性,是以使用"from socket import *"語句,把socket子產品裡面的所有屬性都帶到命名空間中,大幅縮短代碼。調用如下:
tcpsock = socket(af_inet, sock_stream)
4.套接字對象方法
下面是最常用的套接字對象方法:
伺服器端套接字函數
socket類型
描述
s.bind()
綁定位址(主機号 端口号對)到套接字
s.listen()
開始tcp監聽
s.accept()
被動接受tcp用戶端連接配接,(阻塞式)等待連續的到來
用戶端套接字函數
s.connect()
主動初始化tcp伺服器連接配接
s.connect_ex()
connect()函數擴充版本,出錯時傳回出錯碼而不是跑出異常
公共用途的套接字函數
s.recv()
接受tcp資料
s.send()
發送tcp資料
s.sendall()
完整發送tcp資料
s.recvfrom()
接受udp資料
s.sendto()
發送udp資料
s.getpeername()
連接配接到目前套接字的遠端位址(tcp連接配接)
s.getsockname()
擷取目前套接字的位址
s.getsockopt()
傳回指定套接字的參數
s.setsockopt()
設定指定套接字的參數
s.close()
關閉套接字
面向子產品的套接字函數
s.setblocking()
設定套接字的阻塞與非阻塞模式
s.settimeout()
設定阻塞套接字操作的逾時時間
s.gettimeout()
得到阻塞套接字操作的逾時時間
面向檔案的套接字函數
s.fileno()
套接字的檔案描述符
s.makefile()
建立一個與套接字關聯的檔案對象
提示:在運作網絡應用程式時,如果能夠使用在不同的電腦上運作伺服器和用戶端最好不過,它能讓你更好了解通信過程,而更多的是方位localhost或127.0.0.1.
1.伺服器 tcpsersock.py
核心操作如下:
ss = socket() # 建立伺服器套接字
ss.bind() # 位址綁定到套接字上
ss.listen() # 監聽連接配接
inf_loop: # 伺服器無限循環
cs = ss.accept() # 接受用戶端連接配接 阻塞式:程式連接配接之前處于挂起狀态
comm_loop: # 通信循環
cs.recv()/cs.send() # 對話 接受與發送資料
cs.close() # 關閉用戶端套接字
ss.close() # 關閉伺服器套接字 (可選)
2.用戶端 tcpclisock.py
cs = socket() # 建立用戶端套接字
cs.connect()
# 嘗試連接配接伺服器
comm_loop: # 通訊循環
cs.send()/cs.recv() # 對話 發送接受資料
cs.close()
# 關閉用戶端套接字
3.運作結果及注意
由于伺服器被動地無限循環等待連接配接,是以需要先運作伺服器,再開用戶端。又因為我的python總會無法響應,是以采用cmd運作伺服器server程式,python idle運作用戶端進行通信。運作結果如下圖所示:
如果出現錯誤[error] bad file descriptor表示伺服器關閉用戶端連接配接了,删除即可

建議:建立線程來處理用戶端請求。socketserver子產品是一個基于socket子產品的進階别的套接字通信子產品,支援新的線程或程序中處理用戶端請求。同時建議在退出和調用伺服器close()函數時使用try-except語句。
1.伺服器 udpsersock.py
ss = socket() #
建立伺服器套接字
ss.bind() # 綁定伺服器套接字
inf_loop: # 伺服器無限循環
cs = ss.recvfrom()/ss.sendto()
# 對話 接受與發送資料
ss.close() # 關閉伺服器套接字
2.用戶端 udpclisock.py
cs = socket() # 建立用戶端套接字
inf_loop: # 伺服器無限循環
cs.sendto()/cs.recvfrom() #
對話 接受與發送資料
cs.close() # 關閉用戶端套接字
udp伺服器不是面向連接配接的,是以不需要設定什麼東西,直接等待連接配接就好。同時由于資料報套接字是無連接配接的,是以無法把用戶端連接配接交給另外的套接字進行後續的通訊,這些伺服器隻是接受消息,需要的話加時間錯後傳回一個收到的結果給用戶端。
udp用戶端與tcp用戶端唯一差別就是不用去udp伺服器建立連接配接,而是直接把消息發送出去,然後等待伺服器回複即可。
運作結果如下圖所示:白色為用戶端輸入消息,黑色為伺服器收到消息并回複。當client輸入"hello, i am client"時,伺服器顯示該消息并傳回時間戳和收到的資訊給用戶端。
總結:
後面大家自己可以閱讀下socketserver子產品,它是标準庫中一個進階别的子產品,用于簡化實作網絡用戶端和伺服器所需的大量樣闆代碼。該子產品中已經實作了一些可供使用的類直接調用幾塊。
twisted架構是一個完全事件驅動的網絡架構。它允許你使用和開發完全異步的網絡應用程式和協定。
這些東西我更傾向于分享原理和底層的一些東西吧!同時最近考到的筆試題包括:tcp和udp的差別、socket其中的參數含義、tcp三次握手及傳遞的參數、寫個socket通訊僞代碼。
總之,希望文章對你有所幫助~