關註上方 浩道Linux ,回復 資料 ,即可獲取海量 L inux 、 Python 、 網路通訊、網路安全 等學習資料!
前言
大家好,這裏是 浩道Linux ,主要給大家分享 L inux 、 P ython 、 網路通訊、網路安全等 相關的IT知識平台。
今天浩道跟大家分享一個python指令碼,學完既可以讓你python編程能力得到提升,又可以讓你的TCP知識得到回顧。
文章來源:
https://ciphersaw.me/2018/05/23/python-trick-tcp-server-and-client/
一、前言
「網路」一直以來都是黑客最熱衷的競技場。數據在網路中肆意傳播:主機掃描、程式碼註入、網路嗅探、數據篡改重放、拒絕服務攻擊……黑客的功底越深厚,能做的就越多。
Python 作為一種解釋型手稿語言,自 1991 年問世以來,其簡潔、明確、可讀性強的語法深受黑客青睞,特別在網路工具的編寫上,避免了繁瑣的底層語法,沒有對執行速度的高效要求,使得 Python 成為安全工作者的必備殺手鐧。
本文先介紹因特網的核心協定 TCP ,再以 Python 的 socket 模組為例介紹網路套接字,最後給出 TCP 伺服器與客戶端的 Python 指令碼,並演示兩者之間的通訊過程。
二、TCP 協定
TCP(Transmission Control Protocol,傳輸控制協定)是一種面向連線、可靠的、基於字節流的傳輸層通訊協定。
TCP 協定的執行過程分為
連線建立
(Connection Establishment)、
數據傳送
(Data Transfer)和
連線終止
(Connection Termination)三個階段,其中「連線建立」與「連線終止」分別是耳熟能詳的 TCP 協定
三次握手
(TCP Three-way Handshake)與
四次揮手
(TCP Four-way Handshake),也是理解本文 TCP 伺服器與客戶端通訊過程的兩個核心階段。
為了能更好地理解下述過程,對 TCP 協定頭的關鍵區段做以下幾點說明:
報文的功能在 TCP 協定頭的
標記符
(Flags)區段中定義,該區段位於第 104~111 位元位,共占 8 位元,每個位元位對應一種功能,置 1 代表開啟,置 0 代表關閉。例如,SYN 報文的標記符為
00000010
,ACK 報文的標記符為
00010000
,ACK + SYN 報文的標記符為
00010010
。
報文的序列號在 TCP 協定頭的
序列號
(
Sequence Number
)區段中定義,該區段位於第 32~63 位元位,共占 32 位元。例如,在「三次握手」過程中,初始序列號由數據發送方隨機生成。
報文的確認號在 TCP 協定頭的
確認號
(Acknowledgement Number)區段中定義,該區段位於第 64~95 位元位,共占 32 位元。例如,在「三次握手」過程中,確認號為前序接收報文的序列號加 1,代表下一次期望接收到的報文序列號。
2.1 連線建立(Connection Establishment)
所謂的「三次握手」,即 TCP 伺服器與客戶端成功建立通訊連線必經的三個步驟,共需透過三個報文完成。
一般而言,首先發送 SYN 報文的一方是客戶端,伺服器則是監聽來自客戶端的建立連線請求。
Handshake Step 1
客戶端向伺服器發送 SYN 報文(SYN = 1)請求建立連線。
此時報文的初始序列號為seq = x ,確認號為 ack = 0。發送完畢後,客戶端進入
SYN_SENT
狀態。
Handshake Step 2
伺服器接收到客戶端的 SYN 報文後,發送 ACK + SYN 報文(ACK = 1,SYN = 1,)確認客戶端的建立連線請求,並也向其發起建立連線請求。
此時報文的序列號為seq = y ,確認號為ack = x + 1 。發送完畢後,伺服器進入
SYN_RCVD
狀態。
Handshake Step 3
,發送 ACK 報文(ACK = 1)確認伺服器的建立連線請求。
此時報文的序列號為seq = x + 1 ,確認號為 ack = y + 1 。發送完畢後,客戶端進入
ESTABLISHED
狀態;當伺服器接收該報文後,也進入了
ESTABLISHED
狀態。
至此,「三次握手」過程全部結束,TCP 通訊連線成功建立。
讀者可參照以下「三次握手」的示意圖進行理解:
2.2 連線終止(Connection Termination)
所謂的「四次揮手」,即 TCP 伺服器與客戶端完全終止通訊連線必經的四個步驟,共需透過四個報文完成。
由於 TCP 通訊連線是 全雙工 的,因此每個方向的連線可以單獨關閉,即可視為一對「二次揮手」,或一對單工連線。主動先發送 FIN 報文的一方,意味著想要關閉到另一方的通訊連線,即在此方向上不再傳輸數據,但仍可以接收來自另一方傳輸過來的數據,直到另一方也發送 FIN 報文,雙方的通訊連線才完全終止。
註意,首先發送 FIN 報文的一方,既可以是客戶端,也可以是伺服器 。下面以客戶端先發起關閉請求為例,對「四次揮手」的過程進行講解。
Handshake Step 1
當客戶端不再向伺服器傳輸數據時,則向其發送 FIN 報文(FIN = 1 )請求關閉連線。
此時報文的初始序列號為seq = u ,確認號為ack = 0 (若此報文中ACK = 1 ,則ack 的值與客戶端的前序接收報文有關)。發送完畢後,客戶端進入
FIN_WAIT_1
狀態。
Handshake Step 2
伺服器接收到客戶端的 FIN 報文後,發送 ACK 報文(ACK = 1 )確認客戶端的關閉連線請求。
此時報文的序列號為seq = v ,確認號為 ack = u + 1。發送完畢後,伺服器進入
CLOSE_WAIT
狀態;當客戶端接收該報文後,進入
FIN_WAIT_2
狀態。
註意,此時 TCP 通訊連線處於
半關閉
狀態,即客戶端不再向伺服器傳輸數據,但仍可以接收伺服器傳輸過來的數據。
Handshake Step 3
FIN + ACK
報文(FIN = 1,ACK = 1)請求關閉連線。此時報文的序列號為seq = w (若在半關閉狀態,伺服器沒有向客戶端傳輸過數據,則seq = v + 1 ),確認號為 ack = u + 1。發送完畢後,伺服器進入
LAST_ACK
狀態。
Handshake Step 4
客戶端接收到伺服器的
FIN + ACK
報文後,發送 ACK 報文(ACK = 1)確認伺服器的關閉連線請求。
此時報文的序列號為 seq = u + 1,確認號為 ack = w + 1。發送完畢後,客戶端進入
TIME_WAIT
狀態;當伺服器接收該報文後,進入
CLOSED
狀態;當客戶端等待了 2MSL 後,仍沒接到伺服器的響應,則認為伺服器已正常關閉,自己也進入
CLOSED
狀態。
至此,「四次揮手」過程全部結束,TCP 通訊連線成功關閉。
讀者可參照以下「四次揮手」的示意圖進行理解:
三、Network Socket
Network Socket
(網路套接字)是電腦網路中行程間通訊的數據流端點,廣義上也代表作業系統提供的一種行程間通訊機制。
行程間通訊
(Inter-Process Communication,IPC)的根本前提是
能夠唯一標示每個行程
。在本地主機的行程間通訊中,可以用 PID(行程 ID)唯一標示每個行程,但 PID 只在本地唯一,在網路中不同主機的 PID 則可能發生沖突,因此采用
「IP 地址 + 傳輸層協定 + 埠號」
的方式唯一標示網路中的一個行程。
小貼士:網路層的 IP 地址可以唯一標示主機,傳輸層的 TCP/UDP 協定和埠號可以唯一標示該主機的一個行程。註意,同一主機中 TCP 協定與 UDP 協定的可以使用相同的埠號。
所有支持網路通訊的程式語言都各自提供了一套 socket API,下面以 Python 3 為例,講解伺服器與客戶端建立 TCP 通訊連線的互動過程:
腦海中先對上述過程產生一定印象後,更易於理解下面兩節 TCP 伺服器與客戶端的 Python 實作。
四、TCP 伺服器
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
import threading
def tcplink(conn, addr):
print("Accept new connection from %s:%s" % addr)
conn.send(b"Welcome!\n")
while True:
conn.send(b"What's your name?")
data = conn.recv(1024)
if data == b"exit":
conn.send(b"Good bye!\n")
break
conn.send(b"Hello %s!\n" % data)
conn.close()
print("Connection from %s:%s is closed" % addr)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(("127.0.0.1", 6000))
s.listen(5)
print("Waiting for connection...")
while True:
conn, addr = s.accept()
t = threading.Thread(target = tcplink, args = (conn, addr))
t.start()
Line 6:定義一個 tcplink() 函式,第一個 conn 參數為伺服器與客戶端互動數據的套接字物件,第二個 addr 參數為客戶端的 IP 地址與埠號,用二元組 (host, port) 表示。
Line 8:連線成功後,向客戶端發送歡迎資訊 b"Welcome!\n"。
Line 9:進入與客戶端互動數據的迴圈階段。
Line 10:向客戶端發送詢問資訊 b"What's your name?"。
Line 11:接收客戶端發來的 bytes 物件。
Line 12:若 bytes 物件為 b"exit",則向客戶端發送結束響應資訊 b"Good bye!\n",並結束與客戶端互動數據的迴圈階段。
Line 15:若 bytes 物件不為 b"exit",則向客戶端發送問候響應資訊 b"Hello %s!\n",其中 %s 是客戶端發來的 bytes 物件。
Line 16:關閉套接字,不再向客戶端發送數據。
Line 19:建立 socket 物件,第一個參數為 socket.AF_INET,代表采用 IPv4 協定用於網路通訊,第二個參數為 socket.SOCK_STREAM,代表采用 TCP 協定用於面向連線的網路通訊。
Line 20:向 socket 物件繫結伺服器主機地址 (「127.0.0.1」, 6000),即本地主機的 TCP 6000 埠。
Line 21:開啟 socket 物件的監聽功能,等待客戶端的連線請求。
Line 24:進入監聽客戶端連線請求的迴圈階段。
Line 25:接收客戶端的連線請求,並獲得與客戶端互動數據的套接字物件 conn 與客戶端的 IP 地址與埠號 addr,其中 addr 為二元組 (host, port)。
Line 26:利用多執行緒技術,為每個請求連線的 TCP 客戶端建立一個新執行緒,實作了一台伺服器同時與多台客戶端進行通訊的功能。
Line 27:開啟新執行緒的活動。
五、TCP 客戶端
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 6000))
print(s.recv(1024).decode())
data = "client"
while True:
if data:
print(s.recv(1024).decode())
data = input("Please input your name: ")
if not data:
continue
s.send(data.encode())
print(s.recv(1024).decode())
if data == "exit":
break
s.close()
Line 5:建立 socket 物件,第一個參數為 socket.AF_INET,代表采用 IPv4 協定用於網路通訊,第二個參數為 socket.SOCK_STREAM,代表采用 TCP 協定用於面向連線的網路通訊。
Line 6:向 (「127.0.0.1」, 6000) 主機發起連線請求,即本地主機的 TCP 6000 埠。
Line 7:連線成功後,接收伺服器發來的歡迎資訊 b"Welcome!\n",並轉換為字串後打印輸出。
Line 9:建立一個非空字串變量 data,並賦初值為 "client"(只要是非空字串即可),用於判斷是否接收來自伺服器發來的詢問資訊 b"What's your name?"。
Line 10:進入與伺服器互動數據的迴圈階段。
Line 11:當變量 data 非空時,則接收伺服器發來的詢問資訊。
Line 13:要求使用者輸入名字。
Line 14:當使用者的輸入為空時,則重新開始迴圈,要求使用者重新輸入。
Line 16:當使用者的輸入非空時,則將字串轉換為 bytes 物件後發送至伺服器。
Line 17:接收伺服器的響應數據,並將響應的 bytes 物件轉換為字串後打印輸出。
Line 18:當使用者的輸入為 "exit" 時,則終止與伺服器互動數據的迴圈階段,即將關閉套接字。
Line 21:關閉套接字,不再向伺服器發送數據。
六、TCP 行程間通訊
將 TCP 伺服器與客戶端的指令碼分別命名為 tcp_server.py 與 tcp_client.py,然後存至桌面,筆者將在 Windows 10 系統下用 PowerShell 進行演示。
小貼士:讀者進行復現時,要確保本機已安裝 Python 3,註意筆者已將預設的啟動路徑名 python 改為了 python3。
單伺服器 VS 單客戶端
在其中一個 PowerShell 中執行命令 python3 ./tcp_server.py,伺服器顯示 Waiting for connection...,並監聽本地主機的 TCP 6000 埠,進入等待連線狀態;
在另一個 PowerShell 中執行命令 python3 ./tcp_client.py,伺服器顯示 Accept new connection from 127.0.0.1:42101,完成與本地主機的 TCP 42101 埠建立通訊連線,並向客戶端發送歡迎資訊與詢問資訊,客戶端接收到資訊後打印輸出;
若客戶端向伺服器發送字串 Alice 與 Bob,則收到伺服器的問候響應資訊;
若客戶端向伺服器發送空字串,則被要求重新輸入;
若客戶端向伺服器發送字串 exit,則收到伺服器的結束響應資訊;
客戶端與伺服器之間的通訊連線已關閉,伺服器顯示 Connection from 127.0.0.1:42101 is closed,並繼續監聽客戶端的連線請求。
單伺服器 VS 多客戶端
在其中一個 PowerShell 中執行命令 python3 ./tcp_server.py,伺服器顯示 Waiting for connection...,並監聽本地主機的 TCP 6000 埠,進入等待連線狀態;
在另三個 PowerShell 中分別執行命令 python3 ./tcp_client.py,伺服器同時與本地主機的 TCP 42719、42721、42722 埠建立通訊連線,並分別向客戶端發送歡迎資訊與詢問資訊,客戶端接收到資訊後打印輸出;
三台客戶端分別向伺服器發送字串 Client1、Client2、Client3,並收到伺服器的問候響應資訊;
所有客戶端分別向伺服器發送字串 exit,並收到伺服器的結束響應資訊;
所有客戶端與伺服器之間的通訊連線已關閉,伺服器繼續監聽客戶端的連線請求。
七、Python API Reference
socket 模組
本節介紹上述程式碼中用到的內建模組 socket,是 Python 網路編程的核心模組。
socket() 函式
socket() 函式用於建立網路通訊中的套接字物件。函式原型如下:
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
family 參數代表地址族(Address Family),預設值為 AF_INET,用於 IPv4 網路通訊,常用的還有 AF_INET6,用於 IPv6 網路通訊。family 參數的可選值取決於本機作業系統。
type 參數代表套接字的型別,預設值為 SOCK_STREAM,用於 TCP 協定(面向連線)的網路通訊,常用的還有 SOCK_DGRAM,用於 UDP 協定(無連線)的網路通訊。
proto 參數代表套接字的協定,預設值為 0,一般忽略該參數,除非 family 參數為 AF_CAN,則 proto 參數需設定為 CAN_RAW 或 CAN_BCM。
fileno 參數代表套接字的檔描述符,預設值為 None,若設定了該參數,則其他三個參數將會被忽略。建立完套接字物件後,需使用物件的內建函式完成網路通訊過程。註意,以下函式原型中的「socket」是指 socket 物件,而不是上述的 socket 模組。
bind() 函式
bind() 函式用於向套接字物件繫結 IP 地址與埠號。註意,套接字物件必須未被繫結,並且埠號未被占用,否則會報錯。函式原型如下:
socket.bind(address)
address 參數代表套接字要繫結的地址,其格式取決於套接字的 family 參數。若 family 參數為 AF_INET,則 address 參數列示為二元組 (host, port),其中 host 是用字串表示的主機地址,port 是用整型表示的埠號。
listen() 函式
listen() 函式用於 TCP 伺服器開啟套接字的監聽功能。函式原型如下:
socket.listen([backlog])
backlog 可選參數代表套接字在拒絕新連線之前,作業系統可以掛起的最大連線數。backlog 參數一般設定為 5,若未設定,系統會為其自動設定一個合理的值。
connect() 函式
connect() 函式用於 TCP 客戶端向 TCP 伺服器發起連線請求。函式原型如下:
socket.connect(address)
address 參數代表套接字要連線的地址,其格式取決於套接字的 family 參數。若 family 參數為 AF_INET,則 address 參數列示為二元組 (host, port),其中 host 是用字串表示的主機地址,port 是用整型表示的埠號。
accept() 函式
accept() 函式用於 TCP 伺服器接受 TCP 客戶端的連線請求。函式原型如下:
socket.accept()
accept() 函式的返回值是二元組 (conn, address),其中 conn 是伺服器用來與客戶端互動數據的套接字物件,address 是客戶端的 IP 地址與埠號,用二元組 (host, port) 表示。
send() 函式
send() 函式用於向遠端套接字物件發送數據。註意,本機套接字必須與遠端套接字成功連線後才能使用該函式,否則會報錯。可見,send() 函式只能用於 TCP 行程間通訊,而對於 UDP 行程間通訊應該用 sendto() 函式。函式原型如下:
socket.send(bytes[, flags])
bytes 參數代表即將發送的 bytes 物件數據。例如,對於字串 "hello world!" 而言,需要用 encode() 函式轉換為 bytes 物件 b"hello world!" 才能進行網路傳輸。
flags 可選參數用於設定 send() 函式的特殊功能,預設值為 0,也可由一個或多個預定義值組成,用位或操作符 | 隔開。詳情可參考 Unix 函式手冊中的 send(2),flags 參數的常見取值有 MSG_OOB、MSG_EOR 、MSG_DONTROUTE等。
send() 函式的返回值是發送數據的字節數。
recv() 函式
recv() 函式用於從遠端套接字物件接收數據。註意,與 send() 函式不同,recv() 函式既可用於 TCP 行程間通訊,也能用於 UDP 行程間通訊。函式原型如下:
socket.recv(bufsize[, flags])
bufsize 參數代表套接字可接收數據的最大字節數。註意,為了使硬體裝置與網路傳輸更好地匹配,bufsize 參數的值最好設定為 2 的冪次方,例如 4096。
flags 可選參數用於設定 recv() 函式的特殊功能,預設值為 0,也可由一個或多個預定義值組成,用位或操作符 | 隔開。詳情可參考 Unix 函式手冊中的 recv(2),flags 參數的常見取值有 MSG_OOB、MSG_PEEK、MSG_WAITALL 等。
recv() 函式的返回值是接收到的 bytes 物件數據。例如,接收到 bytes 物件 b"hello world!",最好用 decode() 函式轉換為字串 "hello world!" 再打印輸出。
close() 函式
close() 函式用於關閉本地套接字物件,釋放與該套接字連線的所有資源。
socket.close()
threading 模組
本節介紹上述程式碼中用到的內建模組 threading,是 Python 多執行緒的核心模組。
Thread() 類
Thread() 類可以建立執行緒物件,用於呼叫 start() 函式啟動新執行緒。類原型如下:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
group 參數作為以後實作 ThreadGroup () 類的保留參數,目前預設值為 None。
target 參數代表執行緒被 run() 函式啟用後呼叫的函式,預設值為 None,即沒有任何函式會被呼叫。
name 參數代表執行緒名,預設值為 None,則系統會自動為其命名,格式為「Thread-N」,N 是從 1 開始的十進制數。
args 參數代表 target 參數指向函式的普通參數,用元組(tuple)表示,預設值為空元組 ()。
kwargs 參數代表 target 參數指向函式的關鍵字參數,用字典(dict)表示,預設值為空字典 {}。
daemon 參數用於標示行程是否為守護行程。若設定為 True,則標示為守護行程;若設定為 False,則標示為非守護行程;若設定為 None,則繼承當前父執行緒的 daemon 參數值。
建立完執行緒物件後,需使用物件的內建函式控制多執行緒活動。
start() 函式
start() 函式用於開啟執行緒活動。函式原型如下:
Thread.start()
註意,每個執行緒物件只能呼叫一次 start() 函式,否則會導致 RuntimeError 錯誤。
八、總結
本文介紹了 TCP 協定與 socket 編程的基礎知識,再用 Python 3 實作並演示了 TCP 伺服器與客戶端的通訊過程,其中還運用了簡單的多執行緒技術,最後將指令碼中涉及到的 Python API 做成了的參考索引,有助於理解實作過程。
筆者水平有限,若文中出現不足或錯誤之處,還望大家不吝相告,多多包涵,歡迎讀者前來交流技術,感謝閱讀。
更多精彩
關註公眾號 「 浩道Linux 」
浩道Linux ,專註於 Linux系統 的相關知識、 網路通訊 、 網路安全 、 Python相關 知識以及涵蓋IT行業相關技能的學習, 理論與實戰結合,真正讓你在學習工作中真正去用到所學。同時也會分享一些面試經驗,助你找到高薪offer,讓我們一起去學習,一起去進步,一起去漲薪!期待您的加入~~~ 關註回復「資料」可 免費獲取學習資料 (含有電子書籍、視訊等)。
喜歡的話,記得 點「贊」 和 「在看」 哦