1. 程式人生 > >python基礎網路程式設計--轉 python之網路程式設計

python基礎網路程式設計--轉 python之網路程式設計

python之網路程式設計

本地的程序間通訊(IPC)有很多種方式,但可以總結為下面4類:

  • 訊息傳遞(管道、FIFO、訊息佇列)
  • 同步(互斥量、條件變數、讀寫鎖、檔案和寫記錄鎖、訊號量)
  • 共享記憶體(匿名的和具名的)
  • 遠端過程呼叫(Solaris門和Sun RPC)

但這些都不是本文的主題!我們要討論的是網路中程序之間如何通訊?首要解決的問題是如何唯一標識一個程序,否則通訊無從談起!在本地可以通過程序PID來唯一標識一個程序,但是在網路中這是行不通的。其實TCP/IP協議族已經幫我們解決了這個問題,網路層的“ip地址

”可以唯一標識網路中的主機,而傳輸層的“協議+埠”可以唯一標識主機中的應用程式(程序)。這樣利用三元組(ip地址,協議,埠)就可以標識網路的程序了,網路中的程序通訊就可以利用這個標誌與其它程序進行互動。

使用TCP/IP協議的應用程式通常採用應用程式設計介面:UNIX  BSD的套接字(socket)和UNIX System V的TLI(已經被淘汰),來實現網路程序之間的通訊。就目前而言,幾乎所有的應用程式都是採用socket,而現在又是網路時代,網路中程序通訊是無處不在,這就是我為什麼說“一切皆socket”。


網路通訊就是兩個程序在通訊

網路程式設計對所有開發語言都是一樣的,Python也不例外。用Python進行網路程式設計,就是在Python程式本身這個程序內,連線別的伺服器程序的通訊埠進行通訊。

 

   (1) IP、TCP和UDP
    當您編寫socket應用程式的時候,您可以在使用TCP還是使用UDP之間做出選擇。它們都有各自的優點和缺點。
    TCP是流協議,而UDP是資料報協議。換句話說,TCP在客戶機和伺服器之間建立持續的開放連線,在該連線的生命期內,位元組可以通過該連線寫出(並且保證順序正確)。然而,通過 TCP 寫出的位元組沒有內建的結構,所以需要高層協議在被傳輸的位元組流內部分隔資料記錄和欄位。
    另一方面,UDP不需要在客戶機和伺服器之間建立連線,它只是在地址之間傳輸報文。

UDP的一個很好特性在於它的包是自分隔的(self-delimiting),也就是一個數據報都準確地指出它的開始和結束位置。然而,UDP的一個可能的缺點在於,它不保證包將會按順序到達,甚至根本就不保證。當然,建立在UDP之上的高層協議可能會提供握手和確認功能。
    對於理解TCP和UDP之間的區別來說,一個有用的類比就是電話呼叫和郵寄信件之間的區別。在呼叫者用鈴聲通知接收者,並且接收者拿起聽筒之前,電話呼叫不是活動的。只要沒有一方結束通話,該電話通道就保持活動,但是在通話期間,他們可以自由地想說多少就說多少。來自任何一方的談話都按臨時的順序發生。另一方面,當你發一封信的時候,郵局在投遞時既不對接收方是否存在作任何保證,也不對信件投遞將花多長時間做出有力保證。接收方可能按與信件的傳送順序不同的順序接收不同的信件,並且傳送方也可能在他們傳送信件是交替地接收郵件。與(理想的)郵政服務不同,無法送達的信件總是被送到死信辦公室處理,而不再返回給傳送。

    (2)對等方、埠、名稱和地址
    除了TCP和UDP協議以外,通訊一方(客戶機或者伺服器)還需要知道的關於與之通訊的對方機器的兩件事情:IP地址或者埠。IP地址是一個32位的資料值,為了人們好記,一般用圓點分開的4組數字的形式來表示,比如:64.41.64.172。埠是一個16位的資料值,通常被簡單地表示為一個小於65536的數字。大多數情況下,該值介於10到100的範圍內。一個IP地址獲取送到某臺機器的一個數據包,而一個埠讓機器決定將該資料包交給哪個程序/服務(如果有的話)。這種解釋略顯簡單,但基本思路是正確的。
    上面的描述幾乎都是正確的,但它也遺漏了一些東西。大多數時候,當人們考慮Internet主機(對等方)時,我們都不會記憶諸如64.41.64.172這樣的數字,而是記憶諸如gnosis.cx這樣的名稱。為了找到與某個特定主機名稱相關聯的IP地址,一般都使用域名伺服器(DNS),但是有時會首先使用本地查詢(經常是通過/etc/hosts的內容)。對於本教程,我們將一般地假設有一個IP地址可用,不過下面討論編寫名稱查詢程式碼。

    (3)主機名稱解析
    命令列實用程式nslookup可以被用來根據符號名稱查詢主機IP地址。實際上,許多常見的實用程式,比如ping或者網路配置工具,也會順便做同樣的事情。但是以程式設計方式做這樣的事情很簡單。

======================TCP/IP======================
應用層: 它只負責產生相應格式的資料 ssh ftp nfs cifs dns http smtp pop3
-----------------------------------
傳輸層: 定義資料傳輸的兩種模式:
TCP(傳輸控制協議:面向連線,可靠的,效率相對不高)
UDP(使用者資料報協議:非面向連線,不可靠的,但效率高)
-----------------------------------
網路層: 連線不同的網路如乙太網、令牌環網
IP (路由,分片) 、ICMP、 IGMP
ARP ( 地址解析協議,作用是將IP解析成MAC )
-----------------------------------
資料鏈路層: 乙太網傳輸
-----------------------------------
物理層: 主要任務是規定各種傳輸介質和介面與傳輸訊號相關的一些特性
-----------------------------------



TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是為廣域網(WANs)設計的。

TCP socket 由於在通向前需要建立連線,所以其模式較 UDP socket 負責些。

 

 

UDP(User Data Protocol,使用者資料報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。如圖:




UDP Socket圖:

 

UDP socket server 端程式碼在進行bind後,無需呼叫listen方法。

TCP/IP協議族包括運輸層、網路層、鏈路層,
而socket所在位置如圖,Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層。


 

Socket是什麼

 

socket起源於Unix,而Unix/Linux基本哲學之一就是“一切皆檔案”,都可以用“開啟open –> 讀寫write/read –> 關閉close”模式來操作。Socket就是該模式的一個實現,socket即是一種特殊的檔案,一些socket函式就是對其進行的操作(讀/寫IO、開啟、關閉).
說白了Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。

 

注意:其實socket也沒有層的概念,它只是一個facade設計模式的應用,讓程式設計變的更簡單。是一個軟體抽象層。在網路程式設計中,我們大量用的都是通過socket實現的。

Socket是網路程式設計的一個抽象概念。通常我們用一個Socket表示“打開了一個網路連結”,而開啟一個Socket需要知道目標計算機的IP地址和埠號,再指定協議型別即可。

http://images2015.cnblogs.com/blog/720333/201611/720333-20161126215744846-706023541.png

TCP程式設計

Socket是網路程式設計的一個抽象概念。通常我們用一個Socket表示“打開了一個網路連結”,而開啟一個Socket需要知道目標計算機的IP地址和埠號,再指定協議型別即可。 TCP連線簡圖: 三次握手,資料傳輸,四次揮手

socket中TCP的三次握手建立連線詳解

我們知道tcp建立連線要進行“三次握手”,即交換三個分組。大致流程如下:

  • 客戶端向伺服器傳送一個SYN J
  • 伺服器向客戶端響應一個SYN K,並對SYN J進行確認ACK J+1
  • 客戶端再想伺服器發一個確認ACK K+1

只有就完了三次握手,但是這個三次握手發生在socket的那幾個函式中呢?請看下圖:

image

圖1、socket中傳送的TCP三次握手

從圖中可以看出,當客戶端呼叫connect時,觸發了連線請求,向伺服器傳送了SYN J包,這時connect進入阻塞狀態;伺服器監聽到連線請求,即收到SYN J包,呼叫accept函式接收請求向客戶端傳送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到伺服器的SYN K ,ACK J+1之後,這時connect返回,並對SYN K進行確認;伺服器收到ACK K+1時,accept返回,至此三次握手完畢,連線建立。

總結:客戶端的connect在三次握手的第二個次返回,而伺服器端的accept在三次握手的第三次返回。

5、socket中TCP的四次握手釋放連線詳解

上面介紹了socket中TCP的三次握手建立過程,及其涉及的socket函式。現在我們介紹socket中的四次握手釋放連線的過程,請看下圖:

image

圖2、socket中傳送的TCP四次握手

圖示過程如下:

  • 某個應用程序首先呼叫 close主動關閉連線,這時TCP傳送一個FIN M;
  • 另一端接收到FIN M之後,執行被動關閉,對這個FIN進行確認。它的接收也作為檔案結束符傳遞給應用程序,因為FIN的接收意味著應用程序在相應的連線上再也接收不到額外資料;
  • 一段時間之後,接收到檔案結束符的應用程序呼叫 close關閉它的socket。這導致它的TCP也傳送一個FIN N;
  • 接收到這個FIN的源傳送端TCP對它進行確認。

這樣每個方向上都有一個FIN和ACK。

Python3 網路程式設計

Python 提供了兩個級別訪問的網路服務。:

  • 低級別的網路服務支援基本的 Socket,它提供了標準的 BSD Sockets API,可以訪問底層作業系統Socket介面的全部方法。
  • 高級別的網路服務模組 SocketServer, 它提供了伺服器中心類,可以簡化網路伺服器的開發。

什麼是 Socket?

Socket又稱"套接字",應用程式通常通過"套接字"向網路發出請求或者應答網路請求,使主機間或者一臺計算機上的程序間可以通訊。

socket和file的區別:

  • file模組是針對某個指定檔案進行【開啟】【讀寫】【關閉】
  • socket模組是針對 伺服器端 和 客戶端Socket 進行【開啟】【讀寫】【關閉】

 

 伺服器端先初始化Socket,然後與埠繫結(bind),對埠進行監聽(listen),呼叫accept阻塞,等待客戶端連線。在這時如果有個客戶端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時客戶端與伺服器端的連線就建立了。客戶端傳送資料請求,伺服器端接收請求並處理請求,然後把迴應資料傳送給客戶端,客戶端讀取資料,最後關閉連線,一次互動結束。


socket()函式

Python 中,我們用 socket()函式來建立套接字,語法格式如下:

1 socket.socket([family[, type [, proto]]])

引數

  • family: 套接字家族可以使AF_UNIX或者AF_INET
  • type: 套接字型別可以根據是面向連線的還是非連線分為SOCK_STREAMSOCK_DGRAM
  • protocol: 一般不填預設為0.

簡單例項

服務端

我們使用 socket 模組的 socket 函式來建立一個 socket 物件。socket 物件可以通過呼叫其他函式來設定一個 socket 服務。

現在我們可以通過呼叫 bind(hostname, port) 函式來指定服務的 port(埠)

接著,我們呼叫 socket 物件的 accept 方法。該方法等待客戶端的連線,並返回 connection 物件,表示已連線到客戶端。

完整程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #!/usr/bin/python3 # 檔名:server.py   # 匯入 socket、sys 模組 import socket import sys   # 建立 socket 物件 serversocket = socket.socket(              socket.AF_INET, socket.SOCK_STREAM)   # 獲取本地主機名 host = socket.gethostname()   port = 9999   # 繫結埠 serversocket.bind((host, port))   # 設定最大連線數,超過後排隊 serversocket.listen( 5 )   while True :      # 建立客戶端連線      clientsocket,addr = serversocket.accept()             print ( "連線地址: %s" % str (addr))            msg = '歡迎訪問python教程!' + "\r\n"      clientsocket.send(msg.encode( 'utf-8' ))      clientsocket.close()

 

客戶端

接下來我們寫一個簡單的客戶端例項連線到以上建立的服務。埠號為 12345。

socket.connect(hosname, port ) 方法開啟一個 TCP 連線到主機為 hostname 埠為 port 的服務商。連線後我們就可以從服務端後期資料,記住,操作完成後需要關閉連線。

完整程式碼如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #!/usr/bin/python3 # 檔名:client.py   # 匯入 socket、sys 模組 import socket import sys   # 建立 socket 物件 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # 獲取本地主機名 host = socket.gethostname()   # 設定埠好 port = 9999   # 連線服務,指定主機和埠 s.connect((host, port))   # 接收小於 1024 位元組的資料 msg = s.recv( 1024 )   s.close()   print (msg.decode( 'utf-8' ))

 先執行server端,然後開啟client端就能看到結果

 

客戶端

大多數連線都是可靠的TCP連線。建立TCP連線時,主動發起連線的叫客戶端,被動響應連線的叫伺服器。

舉個例子,當我們在瀏覽器中訪問新浪時,我們自己的計算機就是客戶端,瀏覽器會主動向新浪的伺服器發起連線。如果一切順利,新浪的伺服器接受了我們的連線,一個TCP連線就建立起來的,後面的通訊就是傳送網頁內容了。

所以,我們要建立一個基於TCP連線的Socket,可以這樣做:

1 2 3 4 5 6 7 # 匯入socket庫: import socket   # 建立一個socket: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立連線: s.connect(( 'www.sina.com.cn' , 80 ))

 

建立Socket時,AF_INET指定使用IPv4協議,如果要用更先進的IPv6,就指定為AF_INET6SOCK_STREAM指定使用面向流的TCP協議,這樣,一個Socket物件就建立成功,但是還沒有建立連線。

客戶端要主動發起TCP連線,必須知道伺服器的IP地址和埠號。新浪網站的IP地址可以用域名www.sina.com.cn自動轉換到IP地址,但是怎麼知道新浪伺服器的埠號呢?

答案是作為伺服器,提供什麼樣的服務,埠號就必須固定下來。由於我們想要訪問網頁,因此新浪提供網頁服務的伺服器必須把埠號固定在80埠,因為80埠是Web服務的標準埠。其他服務都有對應的標準埠號,例如SMTP服務是25埠,FTP服務是21埠,等等。埠號小於1024的是Internet標準服務的埠,埠號大於1024的,可以任意使用。

因此,我們連線新浪伺服器的程式碼如下:

1 s.connect(( 'www.sina.com.cn' , 80 ))

 

注意引數是一個tuple,包含地址和埠號。

建立TCP連線後,我們就可以向新浪伺服器傳送請求,要求返回首頁的內容:

1 2 # 傳送資料: s.send(b 'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n' )

 

TCP連線建立的是雙向通道,雙方都可以同時給對方發資料。但是誰先發誰後發,怎麼協調,要根據具體的協議來決定。例如,HTTP協議規定客戶端必須先發請求給伺服器,伺服器收到後才發資料給客戶端。

傳送的文字格式必須符合HTTP標準,如果格式沒問題,接下來就可以接收新浪伺服器返回的資料了:

接收資料時,呼叫recv(max)方法,一次最多接收指定的位元組數,因此,在一個while迴圈中反覆接收,直到recv()返回空資料,表示接收完畢,退出迴圈。

當我們接收完資料後,呼叫close()方法關閉Socket,這樣,一次完整的網路通訊就結束了:

1 2 # 關閉連線: s.close()

 

接收到的資料包括HTTP頭和網頁本身,我們只需要把HTTP頭和網頁分離一下,把HTTP頭打印出來,網頁內容儲存到檔案:

1 2 3 4 5 header, html = data.split(b '\r\n\r\n' , 1 ) print (header.decode( 'utf-8' )) # 把接收的資料寫入檔案: with open ( 'sina.html' , 'wb' ) as f:      f.write(html)

 

現在,只需要在瀏覽器中開啟這個sina.html檔案,就可以看到新浪的首頁了。

伺服器

和客戶端程式設計相比,伺服器程式設計就要複雜一些。

伺服器程序首先要繫結一個埠並監聽來自其他客戶端的連線。如果某個客戶端連線過來了,伺服器就與該客戶端建立Socket連線,隨後的通訊就靠這個Socket連線了。

所以,伺服器會開啟固定埠(比如80)監聽,每來一個客戶端連線,就建立該Socket連線。由於伺服器會有大量來自客戶端的連線,所以,伺服器要能夠區分一個Socket連線是和哪個客戶端繫結的。一個Socket依賴4項:伺服器地址、伺服器埠、客戶端地址、客戶端埠來唯一確定一個Socket。

但是伺服器還需要同時響應多個客戶端的請求,所以,每個連線都需要一個新的程序或者新的執行緒來處理,否則,伺服器一次就只能服務一個客戶端了。

我們來編寫一個簡單的伺服器程式,它接收客戶端連線,把客戶端發過來的字串加上Hello再發回去。

首先,建立一個基於IPv4和TCP協議的Socket:

1 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

 

然後,我們要繫結監聽的地址和埠。伺服器可能有多塊網絡卡,可以繫結到某一塊網絡卡的IP地址上,也可以用0.0.0.0繫結到所有的網路地址,還可以用127.0.0.1繫結到本機地址。127.0.0.1是一個特殊的IP地址,表示本機地址,如果繫結到這個地址,客戶端必須同時在本機執行才能連線,也就是說,外部的計算機無法連線進來。

埠號需要預先指定。因為我們寫的這個服務不是標準服務,所以用9999這個埠號。請注意,小於1024的埠號必須要有管理員許可權才能繫結:

1 2 # 監聽埠: s.bind(( '127.0.0.1' , 9999 ))

 

緊接著,呼叫listen()方法開始監聽埠,傳入的引數指定等待連線的最大數量:

1 2 s.listen( 5 ) print ( 'Waiting for connection...' )

 

接下來,伺服器程式通過一個永久迴圈來接受來自客戶端的連線,accept()會等待並返回一個客戶端的連線:

1 2 3 4 5 6 while True :      # 接受一個新連線:      sock, addr = s.accept()      # 建立新執行緒來處理TCP連線:      t = threading.Thread(target = tcplink, args = (sock, addr))      t.start()

 

每個連線都必須建立新執行緒(或程序)來處理,否則,單執行緒在處理連線的過程中,無法接受其他客戶端的連線:

1 2 3 4 5 6 7 8 9 10 11 def tcplink(sock, addr):      print ( 'Accept new connection from %s:%s...' % addr)      sock.send(b 'Welcome!' )      while True :          data = sock.recv( 1024 )          time.sleep( 1 )          if not data or data.decode( 'utf-8' ) = = 'exit' :              break          sock.send(( 'Hello, %s!' % data.decode( 'utf-8' )).encode( 'utf-8' ))      sock.close()      print ( 'Connection from %s:%s closed.' % addr)

 

連線建立後,伺服器首先發一條歡迎訊息,然後等待客戶端資料,並加上Hello再發送給客戶端。如果客戶端傳送了exit字串,就直接關閉連線。

要測試這個伺服器程式,我們還需要編寫一個客戶端程式:

1 2 3 4 5 6 7 8 9 10 11 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立連線: s.connect(( '127.0.0.1' , 9999 )) # 接收歡迎訊息: print (s.recv( 1024 ).decode( 'utf-8' )) for data in [b 'Michael' , b 'Tracy' , b 'Sarah' ]:      # 傳送資料:      s.send(data)      print (s.recv( 1024 ).decode( 'utf-8' )) s.send(b 'exit' ) s.close()

 

我們需要開啟兩個命令列視窗,一個執行伺服器程式,另一個執行客戶端程式,就可以看到效果了:

UDP程式設計

TCP是建立可靠連線,並且通訊雙方都可以以流的形式傳送資料。相對TCP,UDP則是面向無連線的協議。

使用UDP協議時,不需要建立連線,只需要知道對方的IP地址和埠