「網絡」一直以來都是黑客最熱衷的競技場。數據在網絡中肆意傳播:主機掃描、代碼注入、網絡嗅探、數據篡改重放、拒絕服務攻擊……黑客的功底越深厚,能作的就越多。html
Python 做爲一種解釋型腳本語言,自 1991 年問世以來,其簡潔、明確、可讀性強的語法深受黑客青睞,特別在網絡工具的編寫上,避免了繁瑣的底層語法,沒有對運行速度的高效要求,使得 Python 成爲安全工做者的必備殺手鐗。python
本文做爲「Python 絕技」系列工具文章的開篇,先介紹因特網的核心協議——TCP 協議,再以 Python 的 socket 模塊爲例介紹網絡套接字,最後給出 TCP 服務器與客戶端的 Python 腳本,並演示二者之間的通訊過程。linux
TCP(Transmission Control Protocol,傳輸控制協議)是一種面向鏈接、可靠的、基於字節流的傳輸層通訊協議。編程
TCP 協議的運行分爲鏈接建立(Connection Establishment)、數據傳送(Data Transfer)和鏈接終止(Connection Termination)三個階段,其中「鏈接建立」階段是耳熟能詳的 TCP 協議三次握手(TCP Three-way Handshake),也是理解本文 TCP 服務器與客戶端通訊過程的階段。安全
所謂的「三次握手」,即 TCP 服務器與客戶端成功創建通訊鏈接必經的三個步驟,共需經過三個報文完成。服務器
客戶端向服務器發送 SYN 報文(SYN = 1)請求鏈接。此時報文的初始序列號爲 seq = x,確認號爲 ack = 0。網絡
服務器接收到客戶端的 SYN 報文後,發送 ACK + SYN 報文(ACK = 1,SYN = 1)確認客戶端的鏈接請求,並也向其發起鏈接請求。此時報文的序列號爲 seq = y,確認號爲 ack = x + 1。多線程
客戶端接收到服務器的 SYN 報文後,發送 ACK 報文(ACK = 1)確認服務器的鏈接請求。此時報文的序列號爲 seq = x + 1,確認號爲 ack = y + 1。socket
對於上述過程的理解,須要注意如下幾點:
00000010
,ACK + SYN 報文的標記符爲 00010010
。爲了更方便地理解,下面給出一張 TCP 協議三次握手的示意圖:
Network Socket(網絡套接字)是計算機網絡中進程間通訊的數據流端點,廣義上也表明操做系統提供的一種進程間通訊機制。
進程間通訊(Inter-Process Communication,IPC)的根本前提是可以惟一標示每一個進程。在本地主機的進程間通訊中,能夠用 PID(進程 ID)惟一標示每一個進程,但 PID 只在本地惟一,在網絡中不一樣主機的 PID 則可能發生衝突,所以採用「IP 地址 + 傳輸層協議 + 端口號」的方式惟一標示網絡中的一個進程。
小貼士:網絡層的 IP 地址能夠惟一標示主機,傳輸層的 TCP/UDP 協議和端口號能夠惟一標示該主機的一個進程。注意,同一主機中 TCP 協議與 UDP 協議的可使用相同的端口號。
全部支持網絡通訊的編程語言都各自提供了一套 socket API,下面以 Python 3 爲例,講解服務器與客戶端創建 TCP 通訊鏈接的交互過程:
腦海中先對上述過程產生必定印象後,更易於理解下面兩節 TCP 服務器與客戶端的 Python 實現。
#!/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).decode() if data == "exit": conn.send(b"Good bye!\n") break conn.send(b"Hello %s!\n" % data.encode()) 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()
"Welcome!\n"
。"What's your name?"
。"exit"
,則向客戶端發送結束信息 "Good bye!\n"
,並結束與客戶端交互數據的循環階段。"exit"
,則向客戶端發送問候信息 "Hello %s!\n"
,其中 %s
是客戶端發來的非空字符串。#!/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()
"Welcome!\n"
。"client"
(只要是非空字符串便可),用於判斷是否接收來自服務器發來的詢問信息 "What's your name?"
。"exit"
(記爲非法字符串)時,則接收服務器發來的詢問信息。"exit"
時,則結束與服務器交互數據的循環階段,即將關閉套接字。將 TCP 服務器與客戶端的腳本分別命名爲 tcp_server.py
與 tcp_client.py
,而後存至桌面,筆者將在 Windows 10 系統下用 PowerShell 進行演示。
小貼士:讀者進行復現時,要確保本機已安裝 Python 3,注意筆者已將默認的啓動路徑名
python
改成了python3
。
python3 ./tcp_server.py
,服務器顯示 Waiting for connection...
,並監聽本地主機的 TCP 6000 端口,進入等待鏈接狀態;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
,並繼續監聽客戶端的鏈接請求。python3 ./tcp_server.py
,服務器顯示 Waiting for connection...
,並監聽本地主機的 TCP 6000 端口,進入等待鏈接狀態;python3 ./tcp_client.py
,服務器同時與本地主機的 TCP 4271九、4272一、42722 端口創建通訊鏈接,並分別向客戶端發送問候信息與詢問信息,客戶端接收到信息後打印輸出;Client1
、Client2
、Client3
,並收到服務器的問候響應信息;exit
,並收到服務器的結束響應信息;此小節介紹上述代碼中用到的 socket 模塊內置函數,也是 socket 編程的核心函數。
socket() 函數用於建立網絡通訊中的套接字對象。函數原型以下:
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
此小節介紹上述代碼中用到的 socket 對象內置函數,也是 socket 編程的常見函數。注意,如下函數原型中的「socket」是指 socket 對象,而不是 socket 模塊。
bind() 函數用於向套接字對象綁定 IP 地址與端口號。注意,套接字對象必須未被綁定,而且端口號未被佔用,不然會報錯。函數原型以下:
socket.bind(address)
listen() 函數用於 TCP 服務器開啓套接字的監聽功能。函數原型以下:
socket.listen([backlog])
connect() 函數用於 TCP 客戶端向 TCP 服務器發起鏈接請求。函數原型以下:
socket.connect(address)
accept() 函數用於 TCP 服務器接受 TCP 客戶端的鏈接請求。函數原型以下:
socket.accept()
accept() 函數的返回值是一個二元組 (conn, address),其中 conn 是服務器用來與客戶端交互數據的套接字對象,address 是客戶端的 IP 地址與端口號,用二元組 (host, port) 表示。
send() 函數用於向遠程套接字對象發送數據。注意,本機的套接字必須與遠程的套接字成功鏈接後才能使用該函數,不然會報錯。可見,send() 函數只能用於 TCP 進程間通訊,而對於 UDP 進程間通訊應該用 sendto() 函數。函數原型以下:
socket.send(bytes[, flags])
"hello world!"
而言,須要用 encode() 函數轉換爲 bytes 對象 b"hello world!"
才能進行網絡傳輸。|
隔開。詳情可參考 Unix 函數手冊中的 send(2),flags 參數的常見取值有 MSG_OOB、MSG_PEEK、MSG_WAITALL 等。send() 函數的返回值是發送數據的字節數。
recv() 函數用於從遠程套接字對象接收數據。注意,與 send() 函數不一樣,recv() 函數既可用於 TCP 進程間通訊,也能用於 UDP 進程間通訊。函數原型以下:
socket.recv(bufsize[, flags])
|
隔開。詳情可參考 Unix 函數手冊中的 recv(2),flags 參數的常見取值有 MSG_OOB、MSG_PEEK、MSG_WAITALL 等。recv() 函數的返回值是接收到的 bytes 對象數據。例如,接收到 bytes 對象 b"hello world!"
,最好用 decode() 函數轉換爲字符串 "hello world!"
再打印輸出。
close() 函數用於關閉本地套接字對象,釋放與該套接字鏈接的全部資源。
socket.close()
此小節介紹上述代碼中用到的 threading 模塊內置類,也是 Python 多線程編程的核心。
Thread() 類能夠建立線程對象,用於調用 start() 函數啓動新線程。類原型以下:
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
()
。{}
。此小節介紹上述代碼中用到的 threading 對象內置函數,也是多線程編程的必用函數。注意,如下函數原型中的「threading」是指 threading 對象,而不是 threading 模塊。
start() 函數用於開啓線程活動。函數原型以下:
threading.start()
注意,每一個線程對象只能調用一次 start() 函數,不然會致使 RuntimeError 錯誤。
本文介紹了 TCP 協議與 socket 編程的基礎知識,再用 Python 3 實現並演示了 TCP 服務器與客戶端的通訊過程,其中還運用了簡單的多線程技術,最後將腳本中涉及到的 Python API 作成了的參考索引,有助於理解實現過程。
筆者水平有限,若文中出現不足或錯誤之處,還望你們不吝相告,多多包涵,歡迎讀者前來交流技術,感謝閱讀。
本文的相關參考請移步至:
有問題你們能夠留言哦,也歡迎你們到春秋論壇中來耍一耍 >>>點擊跳轉