本文來自於千鋒教育在阿里雲開發者社區學習中心上線課程《Python入門2020最新大課》,主講人姜偉。
TCP協議
TCP協議,傳輸控制協議(英語:Transmission Control Protocol,縮寫為 TCP)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。
TCP通信需要經過創建連接、數據傳送、終止連接三個步驟。
TCP通信模型中,在通信開始之前,一定要先建立相關的鏈接,才能發送數據,類似於生活中,"打電話"。
TCP特點
- 面向連接
通信雙方必須先建立連接才能進行數據的傳輸,雙方都必須為該連接分配必要的系統內核資源,以管理連接的狀態和連接上的傳輸。
雙方間的數據傳輸都可以通過這一個連接進行。
完成數據交換後,雙方必須斷開此連接,以釋放系統資源。
這種連接是一對一的,因此TCP不適用於廣播的應用程序,基於廣播的應用程序請使用UDP協議。
- 可靠傳輸
1)TCP採用發送應答機制
TCP發送的每個報文段都必須得到接收方的應答才認為這個TCP報文段傳輸成功
2)超時重傳
發送端發出一個報文段之後就啟動定時器,如果在定時時間內沒有收到應答就重新發送這個報文段。
TCP為了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認(ACK);如果發送端實體在合理的往返時延(RTT)內未收到確認,那麼對應的數據包就被假設為已丟失將會被進行重傳。
3)錯誤校驗
TCP用一個校驗和函數來檢驗數據是否有錯誤;在發送和接收時都要計算校驗和。
4) 流量控制和阻塞管理
流量控制用來避免主機發送得過快而使接收方來不及完全收下。
- TCP與UDP的區別
面向連接(確認有創建三方交握,連接已創建才作傳輸。)
有序數據傳輸
重發丟失的數據包
捨棄重複的數據包
無差錯的數據傳輸
阻塞/流量控制
TCP通信模型
TCP通信模型中,在通信開始之前,一定要先建立相關的鏈接,才能發送數據
服務器和客戶端
服務器,也稱伺服器,是提供計算服務的設備。由於服務器需要響應服務請求,並進行處理,因此一般來說服務器應具備承擔服務並且保障服務的能力。 客戶端(Client)也被稱為用戶端,是指與服務器相對應,為客戶提供本地服務的程序。 客戶端服務器架構又被稱為主從式架構,簡稱C/S結構,是一種網絡架構,它把客戶端與服務器分開來,一個客戶端軟件的實例都可以向一個服務器或應用程序服務器發出請求。
TCP客戶端
相比較於TCP服務端,tcp的客戶端要簡單很多,如果說服務器端是需要自己買手機、查手機卡、設置鈴聲、等待別人打電話流程的話,那麼客戶端就只需要找一個電話亭,拿起電話撥打即可,流程要少很多。
import socket
# 基於tcp協議的socket連接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 在發送數據之前,必須要先和服務器建立連接
s.connect(('192.168.31.199', 9090)) # 調用connect 方法連接到服務器
s.send('hello'.encode('utf8'))
# udp 直接使用sendto發送數據
# s.sendto('hello'.encode('utf8'),('192.168.31.199',9090))
s.close()
TCP服務端
在程序中,如果想要完成一個tcp服務器的功能,需要的流程如下:
socket創建一個套接字
bind綁定ip和port
listen使套接字變為可以被動鏈接
accept等待客戶端的鏈接
recv/send接收發送數據
import socket
# 創建一個socket連接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('192.168.31.199', 9090))
s.listen(128) # 把socket變成一個被動監聽的socket
# (
# <socket.socket fd=512, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.31.199', 9090), raddr=('192.168.31.185', 38096)>,
# ('192.168.31.185', 38096)
# )
# 接收到的結果是一個元組,元組裡有兩個元素
# 第 0 個元素是客戶端的socket連接,第 1 個元素是客戶端的 ip 地址和端口號
# x = s.accept() # 接收客戶端的請求
client_socket, client_addr = s.accept()
# udp裡接收數據,使用的recvfrom
data = client_socket.recv(1024) # tcp裡使用recv獲取數據
print('接收到了{}客戶端{}端口號發送的數據,內容是:{}'.format(client_addr[0], client_addr[1], data.decode('utf8')))
TCP注意事項
- tcp服務器一般情況下都需要綁定,否則客戶端找不到這個服務器
- tcp客戶端一般不綁定,因為是主動鏈接服務器,所以只要確定好服務器的ip、port等信息就好,本地客戶端可以隨機
- tcp服務器中通過listen可以將socket創建出來的主動套接字變為被動的,這是做tcp服務器時必須要做的
- 當客戶端需要鏈接服務器時,就需要使用connect進行鏈接,udp是不需要鏈接的而是直接發送,但是tcp必須先鏈接,只有鏈接成功才能通信
- 當一個tcp客戶端連接服務器時,服務器端會有1個新的套接字,這個套接字用來標記這個客戶端,單獨為這個客戶端服務
- listen後的套接字是被動套接字,用來接收新的客戶端的鏈接請求的,而accept返回的新套接字是標記這個新客戶端的
- 關閉listen後的套接字意味著被動套接字關閉了,會導致新的客戶端不能夠鏈接服務器,但是之前已經鏈接成功的客戶端正常通信。
- 關閉accept返回的套接字意味著這個客戶端已經服務完畢
- 當客戶端的套接字調用close後,服務器端會recv解堵塞,並且返回的長度為0,因此服務器可以通過返回數據的長度來區別客戶端是否已經下線