計算機網絡就是把各個計算機連接到一起,讓網絡中的計算機可以相互通信。網絡編程就是如何在程序中實現兩台計算機的通信。本文將講解網絡的基礎知識,包括比較常見的TCP協議和UDP協議,以及如何使用TCP編程和UDP編程。
當今的時代是一個網絡的時代,網絡無處不在。而我們前面學習編程的程序都是單機的,即不能和其他電腦上的程序進行通信。為了實現不同電腦之間的通信,就需要使用網絡編程。下面我們來了解一下網絡相關的基礎知識。
計算機為了聯網,就必須規定通信協議,早期的計算機網絡,都是由各廠商自己規定一套協議,IBM、Apple和Microsoft都有各自的網絡協議,互不兼容,這就好比一群人說英語,有的說中文,有的說德語,說同一種語言的人可以交流,不同的語言之間就不行了,如圖所示:
為了把全世界的所有不同類型的計算機都連接起來,就必須規定一套全球通用的協議,為了實現互聯網這個目標,互聯網協議簇(Internet Protocol Suite)即通用協議標准出現了。Internet是由inter和net兩個單詞組合起來的,願意就是連接“網絡”的網絡,有了Internet,任何私有網絡,只要支持這個協議,就可以接入互聯網。
因為互聯網協議包含了上百種協議標准,但是最重要的兩個協議是TCP和IP協議,所以,大家把互聯網協議簡稱TCP/IP協議。
在通信時,通信雙方必須知道對方標識,好比發送快遞必須知道對方的地址。互聯網上每個計算機的唯一標識就是IP地址。IP地址實際上是一個32位整數(稱為IPv4),它是以字符串表示的IP地址,如172.16.254.1,實際上是把32位整數按8位分組後的數字表示,目的是便於閱讀,如圖所示:
IP協議負責把數據從一台計算機通過網絡發送到另一台計算機。數據被分割成一小塊一小塊,類似於將一個大包裹拆分成幾個小包裹,然後通過IP包發送出去。由於互聯網鏈路復雜,兩台計算機之間經常有多條線路,因此,路由器就負責決定如何把一個IP包轉發出去。IP包的特點是按塊發送,途徑多個路由。但是不保證都能到達,也不能保障順序到達。
TCP協議則是建立在IP協議之上的。TCP協議負責在兩台計算機之間建立可靠連接,保證數據包按順序到達。TCP協議通過三次握手建立可靠連接,如圖所示:
然後需要對每個IP包進行編號,確保對方按順序收到,如果包丟了,就自動重發。如圖所示:
許多常用的更高級的協議都是建立在TCP協議基礎上的,比如用於浏覽器的HTTP協議、發送郵件的SMTP協議等。一個TCP報文除了包含要傳輸的數據外,還包含源IP地址和目標IP地址、源端口和目標端口。
端口有什麼作用?在兩台計算機通信時,只要發送IP地址是不夠的,因為同一台計算機上運行著多個網絡程序。一個TCP報文來了之後,到底是交給浏覽器還是QQ,就需要端口號來區分。每個網絡程序都向操作系統申請唯一的端口號,這樣,兩個進程在兩台計算機之間建立網絡連接就需要各自的IP地址和各自的端口號。
一個進程也可能同時與多個計算機建立連接,因此它會申請很多端口,端口號不是隨意使用的,而是按照一定的規則進行分配。例如,80端口分配給HTTP服務,21端口分配給FTP服務。
相對TCP協議,UDP協議則是面向無連接的協議。使用UDP協議時,不需要建立連接,只需要知道對方的IP地址和端口號,就可以直接發數據包。但是,數據無法保障一定到達。雖然用UDP傳輸數據不可靠,但它的優點是比TCP協議的速度快。對於不要求可靠到達的數據而言,就可以使用UDP協議。TCP協議和UDP協議的區別如圖所示:
為了讓兩個程序通過網絡進行通信,二者均必須使用 Socket 套接字。Socket 的英文原義是“孔”或“插座”,通常也稱為“套接字”,用於描述IP和端口,它是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信,如圖所示,在Internet上的主機上一般運行了多個服務軟件,同時提供幾種服務,每種服務打開一個Socket,並綁定到一個端口上,不同的端口對應不同的服務。
Socket 正如其英文原義那樣,像多孔插座。一台主機猶如布滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電,有的提供110伏交流電,有的則提供有線電視節目。客戶軟件將插頭插到不同編號的插座,就可以得到不同的服務。
在Python中使用socket模塊的socket()函數就可以完成,語法格式如下:
s = socket.socket(AddressFamily, Type)
函數socket.socket創建一個socket,返回該socket的描述符,該函數帶有兩個參數:
例如,為了創建TCP/IP套接字,可以用下面的方式調用socket.socket():
tcpSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
同樣,為了創建UDP/IP套接字,需要執行以下語句:
udpSock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
創建完成後,生成一個socket對象,socket對象的主要方法如下表所示:
getsockname()
返回套接字自己的地址。返回值通常是元組(ipaddr, port)setsockopt(level, optname, value)設置給定套接字選項的值getsockopt(level, optname[,buflen])返回套接字選項的值settimeout(timeout)設置套接字操作的超時時間,timeout 是一個浮點數,單位是秒。值為 None 表示沒有超時時間。一般,超時時間應該在剛創建套接字時設置,因為它們可能用於連接的操作(如connect())gettimeout()返回當前超時時間的值,單位是秒,如果沒有設置超時時間,則返回 Nonefileno()返回套接字的文件描述符setblocking(flag)如果 flag 為 0,則將套接字設為非阻塞模式;否則,將套接字設為阻塞模式(默認值)。在非阻塞模式下,如果調用 recv() 沒有發現任何數據,或 send() 調用無法立即發送數據,那麼將引起 socket.error 異常makefile()創建一個與套接字相關聯的文件