本系列主要學(xué)習(xí)Python的基本使用和語法知識,后續(xù)可能會圍繞著AI學(xué)習(xí)展開。
Python3 (1) Python語言的簡介
Python3 (2) Python語法基礎(chǔ)
Python3 (3) Python函數(shù)
Python3 (4) Python高級特性
Python3 (5) Python 函數(shù)式編程
Python3 (6) Python 模塊
Python3 (7) Python 面向?qū)ο缶幊?/a>
Python3 (8) Python 面向?qū)ο蟾呒壘幊?/a>
Python3 (9) Python 錯誤、調(diào)試和測試
Python3 (10) Python IO編程
Python3 (11) Python 進(jìn)程和線程
Python3 (12) Python 常用內(nèi)建模塊
Python3 (13) Python 常用第三方模塊
Python3 (14) Python 網(wǎng)絡(luò)編程
首先說一下最近要忙別的事情,Python 學(xué)習(xí)可能會中斷一小段時間,不過基礎(chǔ)篇已經(jīng)差不多學(xué)習(xí)完了,再次開始學(xué)習(xí)的時候主要以項目的形式學(xué)習(xí),穿插剩余的知識點,Python (13) 常見的第三方模塊 還沒完成,之后會寫完發(fā)出,今天主要是學(xué)習(xí) Python 的網(wǎng)絡(luò)編程,順便復(fù)習(xí)一下網(wǎng)絡(luò)編程這塊的知識。
網(wǎng)絡(luò)編程
網(wǎng)絡(luò)編程就是如何在程序中實現(xiàn)兩臺計算機(jī)的通信,現(xiàn)在的應(yīng)用基本上都需要網(wǎng)絡(luò),單機(jī)應(yīng)用基本上很少,所以網(wǎng)絡(luò)編程是一個非常重要又非?;A(chǔ)的知識點,并且網(wǎng)絡(luò)編程對所有開發(fā)語言都是一樣的,Python也不例外。用Python進(jìn)行網(wǎng)絡(luò)編程,就是在Python程序本身這個進(jìn)程內(nèi),連接別的服務(wù)器進(jìn)程的通信端口進(jìn)行通信。
TCP/IP簡介
從互聯(lián)網(wǎng)說起,之所以現(xiàn)在互聯(lián)網(wǎng)無處不在,就是因為計算機(jī)網(wǎng)絡(luò)在發(fā)展過程中形成了一套全球通用的協(xié)議,互聯(lián)網(wǎng)協(xié)議簇(Internet Protocol Suite)就是通用協(xié)議標(biāo)準(zhǔn)。因為互聯(lián)網(wǎng)協(xié)議包含了上百種協(xié)議標(biāo)準(zhǔn),但是最重要的兩個協(xié)議是TCP和IP協(xié)議,所以,大家把互聯(lián)網(wǎng)的協(xié)議簡稱TCP/IP協(xié)議。
- IP地址:通信的時候,雙方必須知道對方的標(biāo)識,互聯(lián)網(wǎng)上每個計算機(jī)的唯一標(biāo)識就是IP地址,類似123.123.123.123。如果是路由器,它就會有兩個或多個IP地址,所以,IP地址對應(yīng)的實際上是計算機(jī)的網(wǎng)絡(luò)接口,通常是網(wǎng)卡,IP 協(xié)議位于 TCP/IP 協(xié)議的第三層——網(wǎng)絡(luò)層。與傳輸層協(xié)議相比,網(wǎng)絡(luò)層的責(zé)任是提供點到點(hop by hop)的服務(wù),而傳輸層(TCP/UDP)則提供端到端(end to end)的服務(wù)。。
- IP協(xié)議:負(fù)責(zé)把數(shù)據(jù)從一臺計算機(jī)通過網(wǎng)絡(luò)發(fā)送到另一臺計算機(jī)。數(shù)據(jù)被分割成一小塊一小塊,然后通過IP包發(fā)送出去。由于互聯(lián)網(wǎng)鏈路復(fù)雜,兩臺計算機(jī)之間經(jīng)常有多條線路,因此,路由器就負(fù)責(zé)決定如何把一個IP包轉(zhuǎn)發(fā)出去。IP包的特點是按塊發(fā)送,途徑多個路由,但不保證能到達(dá),也不保證順序到達(dá)。
- TCP協(xié)議:TCP協(xié)議則是建立在IP協(xié)議之上的。TCP協(xié)議負(fù)責(zé)在兩臺計算機(jī)之間建立可靠連接,保證數(shù)據(jù)包按順序到達(dá)。TCP協(xié)議會通過握手建立連接,然后,對每個IP包編號,確保對方按順序收到,如果包丟掉了,就自動重發(fā)。比如用于瀏覽器的HTTP協(xié)議、發(fā)送郵件的SMTP協(xié)議都是在TCP 協(xié)議基礎(chǔ)上定義的更高級的協(xié)議。一個TCP報文除了包含要傳輸?shù)臄?shù)據(jù)外,還包含源IP地址和目標(biāo)IP地址,源端口和目標(biāo)端口。
- Socket:Socket 是對 TCP/IP 協(xié)議族的一種封裝,是應(yīng)用層與TCP/IP協(xié)議族通信的中間軟件抽象層。從設(shè)計模式的角度看來,Socket其實就是一個門面模式,它把復(fù)雜的TCP/IP協(xié)議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數(shù)據(jù),以符合指定的協(xié)議;Socket 還可以認(rèn)為是一種網(wǎng)絡(luò)間不同計算機(jī)上的進(jìn)程通信的一種方法,利用三元組(ip地址,協(xié)議,端口)就可以唯一標(biāo)識網(wǎng)絡(luò)中的進(jìn)程,網(wǎng)絡(luò)中的進(jìn)程通信可以利用這個標(biāo)志與其它進(jìn)程進(jìn)行交互;Socket 起源于 Unix ,Unix/Linux 基本哲學(xué)之一就是“一切皆文件”,都可以用“打開(open) –> 讀寫(write/read) –> 關(guān)閉(close)”模式來進(jìn)行操作。因此 Socket 也被處理為一種特殊的文件。
TCP編程
Socket表示“打開了一個網(wǎng)絡(luò)鏈接”,而打開一個Socket需要知道目標(biāo)計算機(jī)的IP地址和端口號,再指定協(xié)議類型即可。 創(chuàng)建TCP連接時,主動發(fā)起連接的叫客戶端,被動響應(yīng)連接的叫服務(wù)器。
- 客戶端:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 導(dǎo)入socket庫:
import socket
# 創(chuàng)建一個socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接:
s.connect(('www.sina.com.cn', 80))
# 發(fā)送數(shù)據(jù):
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
# 接收數(shù)據(jù):
buffer = []
while True:
# 每次最多接收1k字節(jié):
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
# 關(guān)閉連接:
s.close()
# 分離HTTP頭和網(wǎng)頁
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的數(shù)據(jù)寫入文件:
with open('sina.html', 'wb') as f:
f.write(html)
輸出結(jié)果:
在目錄下生成 sina.html文件,打開就是新浪首頁。每一步操作都備注了,可以看出客戶端要發(fā)起一個網(wǎng)絡(luò)請求需要 5 步,創(chuàng)建 socket 時需要指定是那種協(xié)議,SOCK_STREAM指定使用面向流的TCP協(xié)議。接受完數(shù)據(jù)后要記得關(guān)閉 socket。
- 服務(wù)端:
#server
# -*- coding: utf-8 -*-
import socket, threading, time
#TCP socket based on ipv4
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#listening on port
s.bind(('127.0.0.1', 9999))
s.listen(5)
print("Waiting for connection...")
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)
while True:
# receive a new connect
sock, addr = s.accept()
# make a new thread dispose TCPlink
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
#client
# -*- coding:utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#establish connection
s.connect(('127.0.0.1', 9999))
#receive a welcome message
print(s.recv(1024).decode('utf-8'))
for data in [b'Lambda', b'Bond', b'alpha']:
#send data
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
輸出結(jié)果:
#server:
Waiting for connection...
Accept new connection from 127.0.0.1:64705...
Connection from 127.0.0.1:64705 closed.
#client:
Welcome!
Hello, Lambda!
Hello, Bond!
Hello, alpha!
以上完成了一個客戶端使用TCP協(xié)議發(fā)出請求,服務(wù)器接收處理,返回客戶端信息的完整過程。以下是TCP編程的特點:
- TCP 提供一種面向連接的、可靠的字節(jié)流服務(wù)
- 在一個 TCP 連接中,僅有兩方進(jìn)行彼此通信。廣播和多播不能用于 TCP
- TCP 使用校驗和,確認(rèn)和重傳機(jī)制來保證可靠傳輸
- TCP 給數(shù)據(jù)分節(jié)進(jìn)行排序,并使用累積確認(rèn)保證數(shù)據(jù)的順序不變和非重復(fù)
- TCP 使用滑動窗口機(jī)制來實現(xiàn)流量控制,通過動態(tài)改變窗口的大小進(jìn)行擁塞控制
注意:TCP 并不能保證數(shù)據(jù)一定會被對方接收到,因為這是不可能的。TCP 能夠做到的是,如果有可能,就把數(shù)據(jù)遞送到接收方,否則就(通過放棄重傳并且中斷連接這一手段)通知用戶。因此準(zhǔn)確說 TCP 也不是 100% 可靠的協(xié)議,它所能提供的是數(shù)據(jù)的可靠遞送或故障的可靠通知。
UDP編程
TCP是建立可靠連接,并且通信雙方都可以以流的形式發(fā)送數(shù)據(jù)。相對TCP,UDP則是面向無連接的協(xié)議。使用UDP協(xié)議時,不需要建立連接,只需要知道對方的IP地址和端口號,就可以直接發(fā)數(shù)據(jù)包。但是,能不能到達(dá)就不知道了。
- 缺點:UDP傳輸數(shù)據(jù)不可靠
- 優(yōu)點:和TCP比,速度快,對于不要求可靠到達(dá)的數(shù)據(jù),就可以使用UDP協(xié)議
直接上代碼:
#server
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定端口:
s.bind(('127.0.0.1', 9999))
print('Bind UDP on 9999...')
while True:
# 接收數(shù)據(jù):
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
reply = 'Hello, %s!' % data.decode('utf-8')
s.sendto(reply.encode('utf-8'), addr)
#Client
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Michael', b'Tracy', b'Sarah']:
# 發(fā)送數(shù)據(jù):
s.sendto(data, ('127.0.0.1', 9999))
# 接收數(shù)據(jù):
print(s.recv(1024).decode('utf-8'))
s.close()
輸出結(jié)果:
#server:
Bind UDP on 9999...
Received from 127.0.0.1:56838.
Received from 127.0.0.1:56838.
Received from 127.0.0.1:56838.
#client:
Hello, Michael!
Hello, Tracy!
Hello, Sarah!
以上就是UDP協(xié)議的使用,UDP的使用與TCP類似,但是不需要建立連接。此外,服務(wù)器綁定UDP端口和TCP端口互不沖突,也就是說,UDP的9999端口與TCP的9999端口可以各自綁定。以下是幾個UDP編程的特點:
- UDP 缺乏可靠性。UDP 本身不提供確認(rèn),序列號,超時重傳等機(jī)制。UDP 數(shù)據(jù)報可能在網(wǎng)絡(luò)中被復(fù)制,被重新排序。即 UDP 不保證數(shù)據(jù)報會到達(dá)其最終目的地,也不保證各個數(shù)據(jù)報的先后順序,也不保證每個數(shù)據(jù)報只到達(dá)一次
- UDP 數(shù)據(jù)報是有長度的。每個 UDP 數(shù)據(jù)報都有長度,如果一個數(shù)據(jù)報正確地到達(dá)目的地,那么該數(shù)據(jù)報的長度將隨數(shù)據(jù)一起傳遞給接收方。而 TCP 是一個字節(jié)流協(xié)議,沒有任何(協(xié)議上的)記錄邊界。
- UDP 是無連接的。UDP 客戶和服務(wù)器之前不必存在長期的關(guān)系。UDP 發(fā)送數(shù)據(jù)報之前也不需要經(jīng)過握手創(chuàng)建連接的過程。
- UDP 支持多播和廣播。
TCP三次握手與四次揮手機(jī)制
所謂三次握手(Three-way Handshake),是指建立一個 TCP 連接時,需要客戶端和服務(wù)器總共發(fā)送3個包。
三次握手的目的是連接服務(wù)器指定端口,建立 TCP 連接,并同步連接雙方的序列號和確認(rèn)號,交換 TCP 窗口大小信息。在 socket 編程中,客戶端執(zhí)行 connect() 時。將觸發(fā)三次握手。
第一次握手(SYN=1, seq=x):
客戶端發(fā)送一個 TCP 的 SYN 標(biāo)志位置1的包,指明客戶端打算連接的服務(wù)器的端口,以及初始序號 X,保存在包頭的序列號(Sequence Number)字段里。
發(fā)送完畢后,客戶端進(jìn)入 SYN_SEND 狀態(tài)。
第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):
服務(wù)器發(fā)回確認(rèn)包(ACK)應(yīng)答。即 SYN 標(biāo)志位和 ACK 標(biāo)志位均為1。服務(wù)器端選擇自己 ISN 序列號,放到 Seq 域里,同時將確認(rèn)序號(Acknowledgement Number)設(shè)置為客戶的 ISN 加1,即X+1。 發(fā)送完畢后,服務(wù)器端進(jìn)入 SYN_RCVD 狀態(tài)。
第三次握手(ACK=1,ACKnum=y+1)
客戶端再次發(fā)送確認(rèn)包(ACK),SYN 標(biāo)志位為0,ACK 標(biāo)志位為1,并且把服務(wù)器發(fā)來 ACK 的序號字段+1,放在確定字段中發(fā)送給對方,并且在數(shù)據(jù)段放寫ISN的+1
發(fā)送完畢后,客戶端進(jìn)入 ESTABLISHED 狀態(tài),當(dāng)服務(wù)器端接收到這個包時,也進(jìn)入 ESTABLISHED 狀態(tài),TCP 握手結(jié)束。
三次握手的過程的示意圖如下:

TCP 的連接的拆除需要發(fā)送四個包,因此稱為四次揮手(Four-way handshake),也叫做改進(jìn)的三次握手??蛻舳嘶蚍?wù)器均可主動發(fā)起揮手動作,在 socket 編程中,任何一方執(zhí)行 close() 操作即可產(chǎn)生揮手操作。
第一次揮手(FIN=1,seq=x)
假設(shè)客戶端想要關(guān)閉連接,客戶端發(fā)送一個 FIN 標(biāo)志位置為1的包,表示自己已經(jīng)沒有數(shù)據(jù)可以發(fā)送了,但是仍然可以接受數(shù)據(jù)。
發(fā)送完畢后,客戶端進(jìn)入 FIN_WAIT_1 狀態(tài)。
第二次揮手(ACK=1,ACKnum=x+1)
服務(wù)器端確認(rèn)客戶端的 FIN 包,發(fā)送一個確認(rèn)包,表明自己接受到了客戶端關(guān)閉連接的請求,但還沒有準(zhǔn)備好關(guān)閉連接。
發(fā)送完畢后,服務(wù)器端進(jìn)入 CLOSE_WAIT 狀態(tài),客戶端接收到這個確認(rèn)包之后,進(jìn)入 FIN_WAIT_2 狀態(tài),等待服務(wù)器端關(guān)閉連接。
第三次揮手(FIN=1,seq=y)
服務(wù)器端準(zhǔn)備好關(guān)閉連接時,向客戶端發(fā)送結(jié)束連接請求,F(xiàn)IN 置為1。
發(fā)送完畢后,服務(wù)器端進(jìn)入 LAST_ACK 狀態(tài),等待來自客戶端的最后一個ACK。
第四次揮手(ACK=1,ACKnum=y+1)
客戶端接收到來自服務(wù)器端的關(guān)閉請求,發(fā)送一個確認(rèn)包,并進(jìn)入 TIME_WAIT狀態(tài),等待可能出現(xiàn)的要求重傳的 ACK 包。
服務(wù)器端接收到這個確認(rèn)包之后,關(guān)閉連接,進(jìn)入 CLOSED 狀態(tài)。
客戶端等待了某個固定時間(兩個最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,沒有收到服務(wù)器端的 ACK ,認(rèn)為服務(wù)器端已經(jīng)正常關(guān)閉連接,于是自己也關(guān)閉連接,進(jìn)入 CLOSED 狀態(tài)。
四次揮手的示意圖如下:

搞清楚握手和揮手規(guī)則,我們就可以設(shè)計如何防止或者說如何減少 SYN 攻擊:
- 縮短超時(SYN Timeout)時間(TCP KeepAlive)
- 增加最大半連接數(shù)
- 過濾網(wǎng)關(guān)防護(hù)
- SYN cookies技術(shù)
參考
http://m.itdecent.cn/p/9968b16b607e(圖片來源)
https://hit-alibaba.github.io/interview/basic/network/HTTP.html
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014320037274136d31bd9979d648cd822375394e29a871000