亚洲全黄无码一级在线看_国产剧情久久久性色_无码av一区二区三区无码_亚洲成a×人片在线观看

當(dāng)前位置: 首頁 > 科技新聞 >

Python 絕技 ―― TCP 服務(wù)器與客戶端

時間:2019-11-13 05:07來源:網(wǎng)絡(luò)整理 瀏覽:
0x00 前言 本文先介紹因

Python 絕技 ―― TCP 服務(wù)器與客戶端

0x00 前言

本文先介紹因特網(wǎng)的核心協(xié)議 TCP ,再以 Python 的 socket 模塊為例介紹網(wǎng)絡(luò)套接字,最后給出 TCP 服務(wù)器與客戶端的 Python 腳本,并演示兩者之間的通信過程。

0x01 TCP 協(xié)議

TCP(Transmission Control Protocol,傳輸控制協(xié)議)是一種面向連接、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。

TCP 協(xié)議的執(zhí)行過程分為連接創(chuàng)建(Connection Establishment)、數(shù)據(jù)傳送(Data Transfer)和連接終止(Connection Termination)三個階段,其中「連接創(chuàng)建」與「連接終止」分別是耳熟能詳?shù)?TCP 協(xié)議三次握手(TCP Three-way Handshake)與四次揮手(TCP Four-way Handshake),也是理解本文 TCP 服務(wù)器與客戶端通信過程的兩個核心階段。

為了能更好地理解下述過程,對 TCP 協(xié)議頭的關(guān)鍵區(qū)段做以下幾點說明:

  • 報文的功能在 TCP 協(xié)議頭的標(biāo)記符(Flags)區(qū)段中定義,該區(qū)段位于第 104~111 比特位,共占 8 比特,每個比特位對應(yīng)一種功能,置 1 代表開啟,置 0 代表關(guān)閉。例如,SYN 報文的標(biāo)記符為 00000010,ACK 報文的標(biāo)記符為 00010000,ACK + SYN 報文的標(biāo)記符為 00010010。
  • 報文的序列號在 TCP 協(xié)議頭的序列號(Sequence Number)區(qū)段中定義,該區(qū)段位于第 32~63 比特位,共占 32 比特。例如,在「三次握手」過程中,初始序列號 seq 由數(shù)據(jù)發(fā)送方隨機(jī)生成。
  • 報文的確認(rèn)號在 TCP 協(xié)議頭的確認(rèn)號(Acknowledgement Number)區(qū)段中定義,該區(qū)段位于第 64~95 比特位,共占 32 比特。例如,在「三次握手」過程中,確認(rèn)號ack 為前序接收報文的序列號加 1,代表下一次期望接收到的報文序列號。

連接創(chuàng)建

所謂的「三次握手」,即 TCP 服務(wù)器與客戶端成功建立通信連接必經(jīng)的三個步驟,共需通過三個報文完成。

一般而言,首先發(fā)送 SYN 報文的一方是客戶端,服務(wù)器則是監(jiān)聽來自客戶端的建立連接請求。

Handshake Step 1

客戶端向服務(wù)器發(fā)送 SYN 報文(SYN=1)請求建立連接。

此時報文的初始序列號為 seq = x ,確認(rèn)號為ack = 0,發(fā)送完畢后,客戶端進(jìn)入 SYN_SENT 狀態(tài)。

Handshake Step 2

服務(wù)器接收到客戶端的 SYN 報文后,發(fā)送 ACK + SYN 報文(ACK=1,SYN=1)確認(rèn)客戶端的建立連接請求,并也向其發(fā)起建立連接請求。

此時報文的序列號為 seq = y,確認(rèn)號為 ack = x+1,發(fā)送完畢后,服務(wù)器進(jìn)入 SYN_RCVD狀態(tài)。

Handshake Step 3

客戶端接收到服務(wù)器的 SYN 報文后,發(fā)送 ACK 報文(ACK=1)確認(rèn)服務(wù)器的建立連接請求。

此時報文的序列號為 seq=x+1,確認(rèn)號為 ack=y+1。發(fā)送完畢后,客戶端進(jìn)入 ESTABLISHED 狀態(tài);當(dāng)服務(wù)器接收該報文后,也進(jìn)入了 ESTABLISHED 狀態(tài)。

至此,「三次握手」過程全部結(jié)束,TCP 通信連接成功建立。

讀者可參照以下「三次握手」的示意圖進(jìn)行理解:

Python 絕技 ―― TCP 服務(wù)器與客戶端

連接終止(Connection Termination)

所謂的「四次揮手」,即 TCP 服務(wù)器與客戶端完全終止通信連接必經(jīng)的四個步驟,共需通過四個報文完成。

由于 TCP 通信連接是全雙工的,因此每個方向的連接可以單獨關(guān)閉,即可視為一對「二次揮手」,或一對單工連接。主動先發(fā)送 FIN 報文的一方,意味著想要關(guān)閉到另一方的通信連接,即在此方向上不再傳輸數(shù)據(jù),但仍可以接收來自另一方傳輸過來的數(shù)據(jù),直到另一方也發(fā)送 FIN 報文,雙方的通信連接才完全終止。

注意,首先發(fā)送 FIN 報文的一方,既可以是客戶端,也可以是服務(wù)器。下面以客戶端先發(fā)起關(guān)閉請求為例,對「四次揮手」的過程進(jìn)行講解。

Handshake Step 1

當(dāng)客戶端不再向服務(wù)器傳輸數(shù)據(jù)時,則向其發(fā)送 FIN 報文(FIN=1)請求關(guān)閉連接。

此時報文的初始序列號為 seq = u ,確認(rèn)號為ack = 0,(若此報文中 ACK=1,則 ACK 的值與客戶端的前序接收報文有關(guān))。發(fā)送完畢后,客戶端進(jìn)入 FIN_WAIT_1 狀態(tài)。

Handshake Step 2

服務(wù)器接收到客戶端的 FIN 報文后,發(fā)送 ACK 報文(ACK = 1)確認(rèn)客戶端的關(guān)閉連接請求。

此時報文的序列號為 seq = v, 確認(rèn)號為ack = u + 1,發(fā)送完畢后,服務(wù)器進(jìn)入 CLOSE_WAIT 狀態(tài);當(dāng)客戶端接收該報文后,進(jìn)入 FIN_WAIT_2 狀態(tài)。

注意,此時 TCP 通信連接處于半關(guān)閉狀態(tài),即客戶端不再向服務(wù)器傳輸數(shù)據(jù),但仍可以接收服務(wù)器傳輸過來的數(shù)據(jù)。

Handshake Step 3

當(dāng)服務(wù)器不再向客戶端傳輸數(shù)據(jù)時,則向其發(fā)送 FIN + ACK 報文(FIN=1,ACK=1)請求關(guān)閉連接。

此時報文的序列號為 seq = w(若在半關(guān)閉狀態(tài),服務(wù)器沒有向客戶端傳輸過數(shù)據(jù),則 seq = v+1 ),確認(rèn)號為 ack = u+1。發(fā)送完畢后,服務(wù)器進(jìn)入 LAST_ACK 狀態(tài)。

Handshake Step 4

客戶端接收到服務(wù)器的 FIN + ACK 報文后,發(fā)送 ACK 報文(ACK = 1)確認(rèn)服務(wù)器的關(guān)閉連接請求。

此時報文的序列號為 seq=u+1,確認(rèn)號為 ack=w+1。發(fā)送完畢后,客戶端進(jìn)入 TIME_WAIT 狀態(tài);當(dāng)服務(wù)器接收該報文后,進(jìn)入 CLOSED 狀態(tài);當(dāng)客戶端等待了 2MSL 后,仍沒接到服務(wù)器的響應(yīng),則認(rèn)為服務(wù)器已正常關(guān)閉,自己也進(jìn)入 CLOSED 狀態(tài)。

至此,「四次揮手」過程全部結(jié)束,TCP 通信連接成功關(guān)閉。

讀者可參照以下「四次揮手」的示意圖進(jìn)行理解:

Python 絕技 ―― TCP 服務(wù)器與客戶端

0x02 Network Socket

Network Socket(網(wǎng)絡(luò)套接字)是計算機(jī)網(wǎng)絡(luò)中進(jìn)程間通信的數(shù)據(jù)流端點,廣義上也代表操作系統(tǒng)提供的一種進(jìn)程間通信機(jī)制。

進(jìn)程間通信(Inter-Process Communication,IPC)的根本前提是能夠唯一標(biāo)示每個進(jìn)程。在本地主機(jī)的進(jìn)程間通信中,可以用 PID(進(jìn)程 ID)唯一標(biāo)示每個進(jìn)程,但 PID 只在本地唯一,在網(wǎng)絡(luò)中不同主機(jī)的 PID 則可能發(fā)生沖突,因此采用「IP 地址 + 傳輸層協(xié)議 + 端口號」的方式唯一標(biāo)示網(wǎng)絡(luò)中的一個進(jìn)程。

小貼士:網(wǎng)絡(luò)層的 IP 地址可以唯一標(biāo)示主機(jī),傳輸層的 TCP/UDP 協(xié)議和端口號可以唯一標(biāo)示該主機(jī)的一個進(jìn)程。注意,同一主機(jī)中 TCP 協(xié)議與 UDP 協(xié)議的可以使用相同的端口號。

所有支持網(wǎng)絡(luò)通信的編程語言都各自提供了一套 socket API,下面以 Python 3 為例,講解服務(wù)器與客戶端建立 TCP 通信連接的交互過程:

Python 絕技 ―― TCP 服務(wù)器與客戶端

Python 絕技 ―― TCP 服務(wù)器與客戶端

腦海中先對上述過程產(chǎn)生一定印象后,更易于理解下面兩節(jié) TCP 服務(wù)器與客戶端的 Python 實現(xiàn)。

0x03 TCP 服務(wù)器

 
  • Line 6:定義一個 tcplink() 函數(shù),第一個 conn 參數(shù)為服務(wù)器與客戶端交互數(shù)據(jù)的套接字對象,第二個 addr 參數(shù)為客戶端的 IP 地址與端口號,用二元組 (host, port) 表示。
  • Line 8:連接成功后,向客戶端發(fā)送歡迎信息 b"Welcome!/n"。
  • Line 9:進(jìn)入與客戶端交互數(shù)據(jù)的循環(huán)階段。
  • Line 10:向客戶端發(fā)送詢問信息 b"What's your name?"。
  • Line 11:接收客戶端發(fā)來的 bytes 對象。
  • Line 12:若 bytes 對象為 b"exit",則向客戶端發(fā)送結(jié)束響應(yīng)信息 b"Good bye!/n",并結(jié)束與客戶端交互數(shù)據(jù)的循環(huán)階段。
  • Line 15:若 bytes 對象不為 b"exit",則向客戶端發(fā)送問候響應(yīng)信息 b"Hello %s!/n",其中 %s 是客戶端發(fā)來的 bytes 對象。
  • Line 16:關(guān)閉套接字,不再向客戶端發(fā)送數(shù)據(jù)。
  • Line 19:創(chuàng)建 socket 對象,第一個參數(shù)為 socket.AF_INET,代表采用 IPv4 協(xié)議用于網(wǎng)絡(luò)通信,第二個參數(shù)為 socket.SOCK_STREAM,代表采用 TCP 協(xié)議用于面向連接的網(wǎng)絡(luò)通信。
  • Line 20:向 socket 對象綁定服務(wù)器主機(jī)地址 (“127.0.0.1”, 6000),即本地主機(jī)的 TCP 6000 端口。
  • Line 21:開啟 socket 對象的監(jiān)聽功能,等待客戶端的連接請求。
  • Line 24:進(jìn)入監(jiān)聽客戶端連接請求的循環(huán)階段。
  • Line 25:接收客戶端的連接請求,并獲得與客戶端交互數(shù)據(jù)的套接字對象 conn 與客戶端的 IP 地址與端口號 addr,其中 addr 為二元組 (host, port)。
  • Line 26:利用多線程技術(shù),為每個請求連接的 TCP 客戶端創(chuàng)建一個新線程,實現(xiàn)了一臺服務(wù)器同時與多臺客戶端進(jìn)行通信的功能。
  • Line 27:開啟新線程的活動。

0x04 TCP 客戶端

 
  • Line 5:創(chuàng)建 socket 對象,第一個參數(shù)為 socket.AF_INET,代表采用 IPv4 協(xié)議用于網(wǎng)絡(luò)通信,第二個參數(shù)為 socket.SOCK_STREAM,代表采用 TCP 協(xié)議用于面向連接的網(wǎng)絡(luò)通信。
  • Line 6:向 (“127.0.0.1”, 6000) 主機(jī)發(fā)起連接請求,即本地主機(jī)的 TCP 6000 端口。
  • Line 7:連接成功后,接收服務(wù)器發(fā)來的歡迎信息 b"Welcome!/n",并轉(zhuǎn)換為字符串后打印輸出。
  • Line 9:創(chuàng)建一個非空字符串變量 data,并賦初值為 "client"(只要是非空字符串即可),用于判斷是否接收來自服務(wù)器發(fā)來的詢問信息 b"What's your name?"。
  • Line 10:進(jìn)入與服務(wù)器交互數(shù)據(jù)的循環(huán)階段。
  • Line 11:當(dāng)變量 data 非空時,則接收服務(wù)器發(fā)來的詢問信息。
  • Line 13:要求用戶輸入名字。
  • Line 14:當(dāng)用戶的輸入為空時,則重新開始循環(huán),要求用戶重新輸入。
  • Line 16:當(dāng)用戶的輸入非空時,則將字符串轉(zhuǎn)換為 bytes 對象后發(fā)送至服務(wù)器。
  • Line 17:接收服務(wù)器的響應(yīng)數(shù)據(jù),并將響應(yīng)的 bytes 對象轉(zhuǎn)換為字符串后打印輸出。
  • Line 18:當(dāng)用戶的輸入為 "exit" 時,則終止與服務(wù)器交互數(shù)據(jù)的循環(huán)階段,即將關(guān)閉套接字。
  • Line 21:關(guān)閉套接字,不再向服務(wù)器發(fā)送數(shù)據(jù)。

0x05 TCP 進(jìn)程間通信

將 TCP 服務(wù)器與客戶端的腳本分別命名為 tcp_server.py 與 tcp_client.py,然后存至桌面,筆者將在 Windows 10 系統(tǒng)下用 PowerShell 進(jìn)行演示。

小貼士:讀者進(jìn)行復(fù)現(xiàn)時,要確保本機(jī)已安裝 Python 3,注意筆者已將默認(rèn)的啟動路徑名 python 改為了 python3。

單服務(wù)器 VS 單客戶端

Python 絕技 ―― TCP 服務(wù)器與客戶端

單服務(wù)器 VS 多客戶端

Python 絕技 ―― TCP 服務(wù)器與客戶端

0x06 Python API Reference

socket 模塊

本節(jié)介紹上述代碼中用到的內(nèi)建模塊 socket,是 Python 網(wǎng)絡(luò)編程的核心模塊。

socket() 函數(shù)

socket() 函數(shù)用于創(chuàng)建網(wǎng)絡(luò)通信中的套接字對象。函數(shù)原型如下:

 
  • family 參數(shù)代表地址族(Address Family),默認(rèn)值為 AF_INET,用于 IPv4 網(wǎng)絡(luò)通信,常用的還有 AF_INET6,用于 IPv6 網(wǎng)絡(luò)通信。family 參數(shù)的可選值取決于本機(jī)操作系統(tǒng)。
  • type 參數(shù)代表套接字的類型,默認(rèn)值為 SOCK_STREAM,用于 TCP 協(xié)議(面向連接)的網(wǎng)絡(luò)通信,常用的還有 SOCK_DGRAM,用于 UDP 協(xié)議(無連接)的網(wǎng)絡(luò)通信。
  • proto 參數(shù)代表套接字的協(xié)議,默認(rèn)值為 0,一般忽略該參數(shù),除非 family 參數(shù)為 AF_CAN,則 proto 參數(shù)需設(shè)置為 CAN_RAW 或 CAN_BCM。
  • fileno 參數(shù)代表套接字的文件描述符,默認(rèn)值為 None,若設(shè)置了該參數(shù),則其他三個參數(shù)將會被忽略。

創(chuàng)建完套接字對象后,需使用對象的內(nèi)置函數(shù)完成網(wǎng)絡(luò)通信過程。注意,以下函數(shù)原型中的「socket」是指 socket 對象,而不是上述的 socket 模塊。

bind() 函數(shù)

bind() 函數(shù)用于向套接字對象綁定 IP 地址與端口號。注意,套接字對象必須未被綁定,并且端口號未被占用,否則會報錯。函數(shù)原型如下:

 
  • address 參數(shù)代表套接字要綁定的地址,其格式取決于套接字的 family 參數(shù)。若 family 參數(shù)為 AF_INET,則 address 參數(shù)表示為二元組 (host, port),其中 host 是用字符串表示的主機(jī)地址,port 是用整型表示的端口號。

listen() 函數(shù)

listen() 函數(shù)用于 TCP 服務(wù)器開啟套接字的監(jiān)聽功能。函數(shù)原型如下:

 
  • backlog 可選參數(shù)代表套接字在拒絕新連接之前,操作系統(tǒng)可以掛起的最大連接數(shù)。backlog 參數(shù)一般設(shè)置為 5,若未設(shè)置,系統(tǒng)會為其自動設(shè)置一個合理的值。

connect() 函數(shù)

connect() 函數(shù)用于 TCP 客戶端向 TCP 服務(wù)器發(fā)起連接請求。函數(shù)原型如下:

 

address 參數(shù)代表套接字要連接的地址,其格式取決于套接字的 family 參數(shù)。若 family 參數(shù)為 AF_INET,則 address 參數(shù)表示為二元組 (host, port),其中 host 是用字符串表示的主機(jī)地址,port 是用整型表示的端口號。

accept() 函數(shù)

accept() 函數(shù)用于 TCP 服務(wù)器接受 TCP 客戶端的連接請求。函數(shù)原型如下:

 

accept() 函數(shù)的返回值是二元組 (conn, address),其中 conn 是服務(wù)器用來與客戶端交互數(shù)據(jù)的套接字對象,address 是客戶端的 IP 地址與端口號,用二元組 (host, port) 表示。

send() 函數(shù)

send() 函數(shù)用于向遠(yuǎn)程套接字對象發(fā)送數(shù)據(jù)。注意,本機(jī)套接字必須與遠(yuǎn)程套接字成功連接后才能使用該函數(shù),否則會報錯??梢?,send() 函數(shù)只能用于 TCP 進(jìn)程間通信,而對于 UDP 進(jìn)程間通信應(yīng)該用 sendto() 函數(shù)。函數(shù)原型如下:

 

bytes 參數(shù)代表即將發(fā)送的 bytes 對象數(shù)據(jù)。例如,對于字符串 "hello world!" 而言,需要用 encode() 函數(shù)轉(zhuǎn)換為 bytes 對象 b"hello world!" 才能進(jìn)行網(wǎng)絡(luò)傳輸。

flags 可選參數(shù)用于設(shè)置 send() 函數(shù)的特殊功能,默認(rèn)值為 0,也可由一個或多個預(yù)定義值組成,用位或操作符 | 隔開。詳情可參考 Unix 函數(shù)手冊中的 send(2),flags 參數(shù)的常見取值有 MSG_OOB、MSG_EOR 、MSG_DONTROUTE等。

send() 函數(shù)的返回值是發(fā)送數(shù)據(jù)的字節(jié)數(shù)。

recv() 函數(shù)

recv() 函數(shù)用于從遠(yuǎn)程套接字對象接收數(shù)據(jù)。注意,與 send() 函數(shù)不同,recv() 函數(shù)既可用于 TCP 進(jìn)程間通信,也能用于 UDP 進(jìn)程間通信。函數(shù)原型如下:

 

bufsize 參數(shù)代表套接字可接收數(shù)據(jù)的最大字節(jié)數(shù)。注意,為了使硬件設(shè)備與網(wǎng)絡(luò)傳輸更好地匹配,bufsize 參數(shù)的值最好設(shè)置為 2 的冪次方,例如 4096。

flags 可選參數(shù)用于設(shè)置 recv() 函數(shù)的特殊功能,默認(rèn)值為 0,也可由一個或多個預(yù)定義值組成,用位或操作符 |隔開。詳情可參考 Unix 函數(shù)手冊中的 recv(2),flags 參數(shù)的常見取值有 MSG_OOB、MSG_PEEK、MSG_WAITALL 等。

recv() 函數(shù)的返回值是接收到的 bytes 對象數(shù)據(jù)。例如,接收到 bytes 對象 b"hello world!",最好用 decode() 函數(shù)轉(zhuǎn)換為字符串 "hello world!" 再打印輸出。

close() 函數(shù)

close() 函數(shù)用于關(guān)閉本地套接字對象,釋放與該套接字連接的所有資源。

 

threading 模塊

本節(jié)介紹上述代碼中用到的內(nèi)建模塊 threading,是 Python 多線程的核心模塊。

Thread() 類

Thread() 類可以創(chuàng)建線程對象,用于調(diào)用 start() 函數(shù)啟動新線程。類原型如下:

 
  • group 參數(shù)作為以后實現(xiàn) ThreadGroup() 類的保留參數(shù),目前默認(rèn)值為 None。
  • target 參數(shù)代表線程被 run() 函數(shù)激活后調(diào)用的函數(shù),默認(rèn)值為 None,即沒有任何函數(shù)會被調(diào)用。
  • name 參數(shù)代表線程名,默認(rèn)值為 None,則系統(tǒng)會自動為其命名,格式為「Thread-N」,N 是從 1 開始的十進(jìn)制數(shù)。
  • args 參數(shù)代表 target 參數(shù)指向函數(shù)的普通參數(shù),用元組(tuple)表示,默認(rèn)值為空元組 ()。
  • kwargs 參數(shù)代表 target 參數(shù)指向函數(shù)的關(guān)鍵字參數(shù),用字典(dict)表示,默認(rèn)值為空字典 {}。
  • daemon 參數(shù)用于標(biāo)示進(jìn)程是否為守護(hù)進(jìn)程。若設(shè)置為 True,則標(biāo)示為守護(hù)進(jìn)程;若設(shè)置為 False,則標(biāo)示為非守護(hù)進(jìn)程;若設(shè)置為 None,則繼承當(dāng)前父線程的 daemon 參數(shù)值。

創(chuàng)建完線程對象后,需使用對象的內(nèi)置函數(shù)控制多線程活動。

start() 函數(shù)

start() 函數(shù)用于開啟線程活動。函數(shù)原型如下:

 

注意,每個線程對象只能調(diào)用一次 start() 函數(shù),否則會導(dǎo)致 RuntimeError 錯誤。

0x07 總結(jié)

本文介紹了 TCP 協(xié)議與 socket 編程的基礎(chǔ)知識,再用 Python 3 實現(xiàn)并演示了 TCP 服務(wù)器與客戶端的通信過程,其中還運用了簡單的多線程技術(shù),最后將腳本中涉及到的 Python API 做成了的參考索引,有助于理解實現(xiàn)過程。

【責(zé)任編輯:武曉燕 TEL:(010)68476606】

推薦內(nèi)容