【Python入門】39.網路程式設計
摘要:網路程式設計的概念介紹;TCP/IP協議的基本介紹;介紹Python的socket庫,在Python中進行TCP程式設計與UDP程式設計。
*寫在前面:為了更好的學習python,博主記錄下自己的學習路程。本學習筆記基於 ofollow,noindex">廖雪峰的Python教程 ,如有侵權,請告知刪除。歡迎與博主一起學習Pythonヽ( ̄▽ ̄)ノ *
摘要
本學習筆記基於廖雪峰的Python教程。歡迎與博主一起學習Pythonヽ( ̄▽ ̄)ノ
本節內容包括:網路程式設計的概念介紹;TCP/IP協議的基本介紹;介紹Python的socket庫,在Python中進行TCP程式設計與UDP程式設計。
目錄
網路程式設計
TCP/IP協議
TCP程式設計
客戶端
伺服器
UDP程式設計
網路程式設計
計算機網路就是把各個計算機連線在一起,讓各個計算機之間可以通訊。
網路程式設計就是要通過程式程式設計來實現兩個計算機之間的通訊。
實際上, 網路通訊就是兩個程序之間的通訊 。
比如我們用瀏覽器訪問百度網頁,這一過程就是瀏覽器程序與百度伺服器上的某個Web服務程序在通訊。
網路程式設計對所有開發語言都是一樣的。接下來將簡單介紹TCP/IP協議,然後介紹如何在Python中進行TCP程式設計與UDP程式設計。
TCP/IP協議
早期的 計算機網路 ,為了實現計算機之間的聯網,各廠商自己規定自己的協議,從而實現聯網。但是使用不同網路協議的計算機之間是不可以聯網的。
後來為了解決這個問題,就規定了一套全球通用的協議標準,稱網際網路協議族(Internet Protocol Suite)。只要支援符合這一協議標準的協議,就能進入 網際網路(Internet) 。
網際網路協議包含上百種協議,其中最重要的便是 TCP/IP協議 。
IP協議負責把傳輸資料,它會把資料分成一塊塊,然後通過IP包,由傳送端傳輸到接收端。由網際網路線路複雜,兩個計算機之間會有很多線路,所以IP協議並不能保證可以到達或者按順序到達接收端。
TCP協議是建立在IP協議之上,它負責建立兩個計算機之間的可靠連線,保證資料按量按順序到達。如果出現包丟了,就會重新發送。
在錯綜複雜的網際網路中,計算機用 IP地址 來標識自身的位置。
實際上IP地址是一個32位整數( IPv4 ),為了便於閱讀,把32位整數按8位分組後以十進位制表示,如: 192.168.1.1
而 IPv6 實際上是一個128位整數,把128位整數按16位分組後,以十六進位制表示。
如: abab:910c:2222:5498:5475:1111:39f0:2520
一臺計算機可以有多個IP地址,當一臺計算機接入兩個或多個網路時,如路由器,就會有兩個或多個IP地址。
兩個計算機之間通訊僅靠IP地址是不行的,還需要多個 埠 來對應不同的網路程式。每個網路程式在進行通訊時,會向作業系統申請唯一的埠號,當該程序需要多個連線時,就會申請多個埠。
一個完整的TCP報文需要包含 源IP地址、目標IP地址、源埠、目標埠 和需要傳輸的 資料 。
TCP程式設計
在介紹TCP程式設計之前,我們要引入一個計算機術語—— socket
網路上的兩個程式通過一個雙向的通訊連線實現資料的交換,這個連線的一端稱為一個socket。——百度百科
socket在英文釋義中是孔、插座的意思。在網路程式設計中,表示網路連線的一端。要實現網路連線,就要開啟socket。而要開啟socket,需要知道目標IP地址、埠號以及協議型別。
在Python中有 socket庫 ,可以用它來實現網路程式設計。
在建立TCP連線時,我們稱主動發起連線的為客戶端,被動響應連線的為伺服器。
客戶端
我們先進行客戶端的編寫。
- 首先,引入
socket
庫:
import socket
- 然後,建立一個
socket
:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
這裡的 AF_INET
表示指定使用IPv4協議,如果用IPv6,則為 AF_INET6
。
而 SOCK_STREAM
表示指定使用面向流的TCP協議。
- 然後,建立連線:
s.connect(('ai.taobao.com', 80))
需要注意的是,這裡的引數是一個 tuple
,裡面包含目標IP地址和目標埠。
這裡用域名 ai.taobao.com
可以自動轉到淘寶伺服器的IP地址。
80
指的是目標埠。伺服器所提供的每個服務會對應一個固定的埠號。而 80
埠就是Web服務的標準埠。還有其他如SMTP服務是 25
埠,FTP服務是 21
埠等。
小於1024的埠號是Internet標準服務的埠,大於1024的埠號可以任意使用
- 然後,向伺服器傳送請求:
建立TCP連線後,就可以向百度伺服器傳送請求了。我們要求返回首頁的內容:
s.send(b'GET / HTTP/1.1\r\nHost:ai.taobao.com\r\nConnection: close\r\n\r\n')
TCP連線建立的通道是雙向的,即雙方都可以給對方傳送資料。
HTTP協議則規定傳送資訊的順序——客戶端必須先給服務端傳送資訊,服務端收到資訊後才能給客戶端傳送資訊。
傳送的文字要符合HTTP格式。
- 接下來,編寫接收服務端資訊部分:
buffer = []# 建立一個緩衝區,用於暫存接收資料 while True: d = s.recv(1024)# 指定一次最多接收的資料位元組數 if d : buffer.append(d)# 若有接收資料,則新增到緩衝區 else: break# 直到資料接收完畢,退出迴圈 data = b''.join(buffer)# 把資料存放到data變數中
recv(max)
方法是指定一次接收的最大位元組數,使用 while
迴圈接收資料,直到資料接收完畢就會退出迴圈。
- 然後,關閉
socket
:
呼叫 close( )
來關閉 socket
,這樣就完成了網路通訊客戶端的編寫:
s.close()
- 最後,處理接收資料:
對接收的資料進行簡單的處理,把HTTP頭打印出來,把網頁內容儲存:
header, html = data.split(b'\r\n\r\n', 1)# 分離HTTP頭與網頁內容 print(header.decode('utf_8'))# 列印HTTP頭 with open('baidu.html', 'wb') as f:# 儲存網頁內容 f.write(html)
這樣,我們開啟指定路徑下的 taobao.html
就可以看到網頁的內容。
我們來看一下完整的程式碼以及執行結果吧。
客戶端:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('ai.taobao.com', 80)) s.send(b'GET / HTTP/1.1\r\nHost:ai.taobao.com\r\nConnection: close\r\n\r\n') buffer = []# 建立一個緩衝區,用於暫存接收資料 while True: d = s.recv(1024)# 指定一次最多接收的資料位元組數 if d : buffer.append(d)# 若有接收資料,則新增到緩衝區 else: break# 直到資料接收完畢,退出迴圈 data = b''.join(buffer)# 把資料存放到data變數中 header, html = data.split(b'\r\n\r\n', 1) print(header.decode('utf_8')) with open('taobao.html', 'wb') as f: f.write(html)
執行結果:
HTTP/1.1 200 OK Date: Mon, 10 Sep 2018 14:24:38 GMT Content-Type: text/html;charset=UTF-8 Transfer-Encoding: chunked Connection: close Vary: Accept-Encoding Vary: Accept-Encoding S: STATUS_NOT_EXISTED Set-Cookie: t=e4ad8c4112aa5205748b3bf8780e216d; Domain=.taobao.com; Expires=Sun, 09-Dec-2018 14:24:38 GMT; Path=/ Set-Cookie: cookie2=1e5f1cc4e8adf4b072d111c70933bea9;Domain=.taobao.com;Path=/;HttpOnly Set-Cookie: v=0; Domain=.taobao.com; Path=/ Set-Cookie: _tb_token_=e78b393e779e9; Domain=.taobao.com; Path=/ P3P: CP='CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR' Content-Language: zh-CN Server: Tengine/Aserver Timing-Allow-Origin: *
在指定的路徑下會生成一個 taobao.html
檔案。
伺服器
伺服器負責響應連線。
它通過繫結一個固定的埠,來監聽客戶端的連線。一旦接收到某個客戶端的連線,就建立一個socket,然後進行通訊。
由於一個伺服器埠可能會接收到多個客戶端的連線,所以每個連線都需要一個新的程序或執行緒來處理,否則一個服務埠一次只能服務一個客戶端。
下面我們編寫一個簡單的伺服器,它接收客戶端的連線,然後在接收的字串上新增“hello”,並返回資訊。
- 首先,建立一個基於IPv4和TCP協議的
socket
:
import socket s = socket.socket(socket.AF_INET, slcket.SOCK_STREAM)
- 然後,通過
bind()
方法繫結需要監聽的埠:
s.bind(('127.0.0.1', 9999))
需要注意的是,這裡的引數是一個 tuple
,含IP地址與埠號。
IP地址 127.0.0.1
是一個特殊地址,表示本機地址。若繫結這個地址,則客戶端必須同時在本機執行才能連線。
由於我們編寫的不是標準的服務埠,所以用 9999
埠號。
- 然後,呼叫
listen()
方法監聽繫結的埠,引數為最大的等待連線數:
s.listen(5) print('Waiting for connecting...')
- 接著,用
while
迴圈來永久接收客戶端的連線:
while True: sock, addr = s.accept()# 接收客戶端的連線和地址 t = threading.Thread(target=tcplink, args=(sock, addr)# 建立新執行緒來處理TCP連線 t.start()# 啟動執行緒
accept()
方法會接收客戶端的連線和地址。
通過 threading.Thread()
建立新執行緒來處理TCP連線。 target
為執行物件, args
為傳入引數。
- 接著,我們來編寫對響應連線部分:
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)# 休眠1s if not data or data.decode('utf-8') == 'exit':# 如果沒有接收到資料或收到'exit'則退出 break sock.send(('Hello,%s!' % data.decode('utf-8')).encode("utf-8"))# 返回客戶端的資訊 sock.close() print('Connection form %s:%s closed' % addr)
這樣我們就完成了伺服器的編寫,最後我們需要編寫一個簡單的客戶端來測試該伺服器的執行。
我們來看一下完整的程式碼以及執行結果吧。
伺服器:
import socket,threading,time s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('127.0.0.1', 9999)) s.listen(5) print('Waiting for connecting...') 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 form %s:%s closed' % addr) while True: sock, addr = s.accept()# 接收客戶端的連線和地址 t = threading.Thread(target=tcplink, args=(sock, addr))# 建立新執行緒來處理TCP連線 t.start()
客戶端:
import socket 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()
執行結果:

簡單TCP程式設計.png
UDP程式設計
TCP是通過建立可靠連線,以流的方式傳輸資料。
而UDP則是面向無連線的協議。只需要知道目標的IP地址和埠號就能傳輸資料,無需建立連線。
UDP的優點是速度快,缺點是不能保證資料能夠到達。
相比TCP,由於不用建立連線,UDP程式設計簡單很多,同樣從伺服器與客戶端入手。
伺服器:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(('127.0.0.1', 9999)) print('Bind UDP on 999...') while True: data, addr = s.recvfrom(1024) print('Receive from %s:%s.' % addr) s.sendto(b'Hello,%s!' % data, addr)
注意這裡 SOCK_DGRAM
指定socket型別是UDP。不用呼叫 listen()
方法,直接獲取資料。
客戶端:
import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for data in [b'Ming', b'Hong', b'Leo']: s.sendto(data, ('127.0.0.1', 9999)) print(s.recv(1024).decode('utf-8')) s.close()
執行結果:

簡單的UDP程式設計
可以發現,資料傳輸速度比TCP快很多。
一般要求速度快,但不要求可靠到達的資料,就可以用UDP協議。
還有一點是伺服器繫結的TCP埠與UDP埠不衝突,也就是說TCP的9999埠與UDP的9999埠是可以各自繫結的。
以上就是本節的全部內容,感謝你的閱讀。
下一節內容:電子郵件
有任何問題與想法,歡迎評論與吐槽。
和博主一起學習Python吧( ̄▽ ̄)~*