天天看點

day32網絡通信tcp

TCP簡介

TCP介紹

TCP協定,傳輸控制協定(英語:Transmission Control Protocol,縮寫為 TCP)是一種面向連接配接的、可靠的、基于位元組流的傳輸層通信協定,由IETF的RFC 793定義。

TCP通信需要經過建立連接配接、資料傳送、終止連接配接三個步驟。

TCP通信模型中,在通信開始之前,一定要先建立相關的連結,才能發送資料,類似于生活中,"打電話""

day32網絡通信tcp

TCP特點

1. 面向連接配接

通信雙方必須先建立連接配接才能進行資料的傳輸,雙方都必須為該連接配接配置設定必要的系統核心資源,以管理連接配接的狀态和連接配接上的傳輸。

雙方間的資料傳輸都可以通過這一個連接配接進行。

完成資料交換後,雙方必須斷開此連接配接,以釋放系統資源。

這種連接配接是一對一的,是以TCP不适用于廣播的應用程式,基于廣播的應用程式請使用UDP協定。

2. 可靠傳輸

1)TCP采用發送應答機制

TCP發送的每個封包段都必須得到接收方的應答才認為這個TCP封包段傳輸成功

2)逾時重傳

發送端發出一個封包段之後就啟動定時器,如果在定時時間内沒有收到應答就重新發送這個封包段。

TCP為了保證不發生丢包,就給每個包一個序号,同時序号也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的确認(ACK);如果發送端實體在合理的往返時延(RTT)内未收到确認,那麼對應的資料包就被假設為已丢失将會被進行重傳。

3)錯誤校驗

TCP用一個校驗和函數來檢驗資料是否有錯誤;在發送和接收時都要計算校驗和。

4) 流量控制和阻塞管理

流量控制用來避免主機發送得過快而使接收方來不及完全收下。

TCP與UDP的不同點

  • 面向連接配接(确認有建立三方交握,連接配接已建立才作傳輸。)
  • 有序資料傳輸
  • 重發丢失的資料包
  • 舍棄重複的資料包
  • 無差錯的資料傳輸
  • 阻塞/流量控制

udp通信模型

udp通信模型中,在通信開始之前,不需要建立相關的連結,隻需要發送資料即可,類似于生活中,"寫信""

day32網絡通信tcp

TCP通信模型

udp通信模型中,在通信開始之前,一定要先建立相關的連結,才能發送資料,類似于生活中,"打電話""

day32網絡通信tcp

tcp用戶端

tcp用戶端,并不是像之前一個段子:一個顧客去飯館吃飯,這個顧客要點菜,就問服務員咱們飯店有用戶端麼,然後這個服務員非常客氣的說道:先生 我們飯店不用用戶端,我們直接送到您的餐桌上

如果,不學習網絡的知識是不是 說不定也會發生那樣的笑話 ,哈哈

所謂的伺服器端:就是提供服務的一方,而用戶端,就是需要被服務的一方

tcp用戶端建構流程

tcp的用戶端要比伺服器端簡單很多,如果說伺服器端是需要自己買手機、查手機卡、設定鈴聲、等待别人打電話流程的話,那麼用戶端就隻需要找一個電話亭,拿起電話撥打即可,流程要少很多

示例代碼:

from socket import *

# 建立socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)

# 目的資訊
server_ip = input("請輸入伺服器ip:")
server_port = int(input("請輸入伺服器port:"))

# 連結伺服器
tcp_client_socket.connect((server_ip, server_port))

# 提示使用者輸入資料
send_data = input("請輸入要發送的資料:")

tcp_client_socket.send(send_data.encode("gbk"))

# 接收對方發送過來的資料,最大接收1024個位元組
recvData = tcp_client_socket.recv()
print('接收到的資料為:', recvData.decode('gbk'))

# 關閉套接字
tcp_client_socket.close()
           

運作流程:

<1>tcp用戶端

請輸入伺服器ip:
請輸入伺服器port:
請輸入要發送的資料:你好啊
接收到的資料為: 我很好,你呢
           

<2>網絡調試助手:

day32網絡通信tcp

tcp伺服器

生活中的電話機

如果想讓别人能更夠打通咱們的電話擷取相應服務的話,需要做以下幾件事情:

  1. 買個手機
  2. 插上手機卡
  3. 設計手機為正常接聽狀态(即能夠響鈴)
  4. 靜靜的等着别人撥打

tcp伺服器

如同上面的電話機過程一樣,在程式中,如果想要完成一個tcp伺服器的功能,需要的流程如下:

  1. socket建立一個套接字
  2. bind綁定ip和port
  3. listen使套接字變為可以被動連結
  4. accept等待用戶端的連結
  5. recv/send接收發送資料

一個很簡單的tcp伺服器如下:

from socket import *

# 建立socket
tcp_server_socket = socket(AF_INET, SOCK_STREAM)

# 本地資訊
address = ('', )

# 綁定
tcp_server_socket.bind(address)

# 使用socket建立的套接字預設的屬性是主動的,使用listen将其變為被動的,這樣就可以接收别人的連結了
tcp_server_socket.listen()

# 如果有新的用戶端來連結伺服器,那麼就産生一個新的套接字專門為這個用戶端服務
# client_socket用來為這個用戶端服務
# tcp_server_socket就可以省下來專門等待其他新用戶端的連結
client_socket, clientAddr = tcp_server_socket.accept()

# 接收對方發送過來的資料
recv_data = client_socket.recv()  # 接收1024個位元組
print('接收到的資料為:', recv_data.decode('gbk'))

# 發送一些資料到用戶端
client_socket.send("thank you !".encode('gbk'))

# 關閉為這個用戶端服務的套接字,隻要關閉了,就意味着為不能再為這個用戶端服務了,如果還需要服務,隻能再次重新連接配接
client_socket.close()
           

運作流程:

<1>tcp伺服器

接收到的資料為: 你在麼?
           

<2>網絡調試助手:

day32網絡通信tcp

tcp注意點

  1. tcp伺服器一般情況下都需要綁定,否則用戶端找不到這個伺服器
  2. tcp用戶端一般不綁定,因為是主動連結伺服器,是以隻要确定好伺服器的ip、port等資訊就好,本地用戶端可以随機
  3. tcp伺服器中通過listen可以将socket建立出來的主動套接字變為被動的,這是做tcp伺服器時必須要做的
  4. 當用戶端需要連結伺服器時,就需要使用connect進行連結,udp是不需要連結的而是直接發送,但是tcp必須先連結,隻有連結成功才能通信
  5. 當一個tcp用戶端連接配接伺服器時,伺服器端會有1個新的套接字,這個套接字用來标記這個用戶端,單獨為這個用戶端服務
  6. listen後的套接字是被動套接字,用來接收新的用戶端的連結請求的,而accept傳回的新套接字是标記這個新用戶端的
  7. 關閉listen後的套接字意味着被動套接字關閉了,會導緻新的用戶端不能夠連結伺服器,但是之前已經連結成功的用戶端正常通信。
  8. 關閉accept傳回的套接字意味着這個用戶端已經服務完畢
  9. 當用戶端的套接字調用close後,伺服器端會recv解堵塞,并且傳回的長度為0,是以伺服器可以通過傳回資料的長度來差別用戶端是否已經下線

tcp的3次握手

day32網絡通信tcp

tcp的4次揮手

day32網絡通信tcp

tcp長連接配接和短連接配接

TCP在真正的讀寫操作之前,server與client之間必須建立一個連接配接,

當讀寫操作完成後,雙方不再需要這個連接配接時它們可以釋放這個連接配接,

連接配接的建立通過三次握手,釋放則需要四次握手,

是以說每個連接配接的建立都是需要資源消耗和時間消耗的。

TCP通信的整個過程,如下圖:

day32網絡通信tcp

1. TCP短連接配接

模拟一種TCP短連接配接的情況:

  1. client 向 server 發起連接配接請求
  2. server 接到請求,雙方建立連接配接
  3. client 向 server 發送消息
  4. server 回應 client
  5. 一次讀寫完成,此時雙方任何一個都可以發起 close 操作

在步驟5中,一般都是 client 先發起 close 操作。當然也不排除有特殊的情況。

從上面的描述看,短連接配接一般隻會在 client/server 間傳遞一次讀寫操作!

2. TCP長連接配接

再模拟一種長連接配接的情況:

  1. client 向 server 發起連接配接
  2. server 接到請求,雙方建立連接配接
  3. client 向 server 發送消息
  4. server 回應 client
  5. 一次讀寫完成,連接配接不關閉
  6. 後續讀寫操作...
  7. 長時間操作之後client發起關閉請求

3. TCP長/短連接配接操作過程

3.1 短連接配接的操作步驟是:

建立連接配接——資料傳輸——關閉連接配接...建立連接配接——資料傳輸——關閉連接配接

day32網絡通信tcp

3.2 長連接配接的操作步驟是:

建立連接配接——資料傳輸...(保持連接配接)...資料傳輸——關閉連接配接

day32網絡通信tcp

4. TCP長/短連接配接的優點和缺點

  • 長連接配接可以省去較多的TCP建立和關閉的操作,減少浪費,節約時間。

    對于頻繁請求資源的客戶來說,較适用長連接配接。

  • client與server之間的連接配接如果一直不關閉的話,會存在一個問題,

    随着用戶端連接配接越來越多,server早晚有扛不住的時候,這時候server端需要采取一些政策,

    如關閉一些長時間沒有讀寫事件發生的連接配接,這樣可以避免一些惡意連接配接導緻server端服務受損;

    如果條件再允許就可以以用戶端機器為顆粒度,限制每個用戶端的最大長連接配接數,

    這樣可以完全避免某個蛋疼的用戶端連累後端服務。

  • 短連接配接對于伺服器來說管理較為簡單,存在的連接配接都是有用的連接配接,不需要額外的控制手段。
  • 但如果客戶請求頻繁,将在TCP的建立和關閉操作上浪費時間和帶寬。

5. TCP長/短連接配接的應用場景

  • 長連接配接多用于操作頻繁,點對點的通訊,而且連接配接數不能太多情況。

    每個TCP連接配接都需要三次握手,這需要時間,如果每個操作都是先連接配接,

    再操作的話那麼處理速度會降低很多,是以每個操作完後都不斷開,

    再次處理時直接發送資料包就OK了,不用建立TCP連接配接。

    例如:資料庫的連接配接用長連接配接,如果用短連接配接頻繁的通信會造成socket錯誤,

    而且頻繁的socket 建立也是對資源的浪費。

  • 而像WEB網站的http服務一般都用短連結,因為長連接配接對于服務端來說會耗費一定的資源,

    而像WEB網站這麼頻繁的成千上萬甚至上億用戶端的連接配接用短連接配接會更省一些資源,

    如果用長連接配接,而且同時有成千上萬的使用者,如果每個使用者都占用一個連接配接的話,

    那可想而知吧。是以并發量大,但每個使用者無需頻繁操作情況下需用短連好。

tcp-ip簡介

作為新時代标杆的我們,已經離不開手機、離不開網絡,對于網際網路大家可能耳熟能詳,但是計算機網絡的出現比網際網路要早很多

1. 什麼是協定

day32網絡通信tcp

有的說英語,有的說中文,有的說德語,說同一種語言的人可以交流,不同的語言之間就不行了

為了解決不同種族人之間的語言溝通障礙,現規定國際通用語言是英語,這就是一個規定,這就是協定

2. 計算機網絡溝通用什麼

現在的生活中,不同的計算機隻需要能夠聯網(有線無線都可以)那麼就可以互相進行傳遞資料

day32網絡通信tcp

那麼不同種類之間的計算機到底是怎麼進行資料傳遞的呢?

就像說不同語言的人溝通一樣,隻要有一種大家都認可都遵守的協定即可,那麼這個計算機都遵守的網絡通信協定叫做

TCP/IP協定

3. TCP/IP協定(族)

早期的計算機網絡,都是由各廠商自己規定一套協定,IBM、Apple和Microsoft都有各自的網絡協定,互不相容

為了把全世界的所有不同類型的計算機都連接配接起來,就必須規定一套全球通用的協定,為了實作網際網路這個目标,網際網路協定族(Internet Protocol Suite)就是通用協定标準。

因為網際網路協定包含了上百種協定标準,但是最重要的兩個協定是TCP和IP協定,是以,大家把網際網路的協定簡稱TCP/IP協定(族)

常用的網絡協定如下圖所示:

day32網絡通信tcp
day32網絡通信tcp

說明:

網際層也稱為:網絡層
網絡接口層也稱為:鍊路層
           

另外一套标準

day32網絡通信tcp

案例:檔案下載下傳器

伺服器 參考代碼如下:

from socket import *
import sys


def get_file_content(file_name):
    """擷取檔案的内容"""
    try:
        with open(file_name, "rb") as f:
            content = f.read()
        return content
    except:
        print("沒有下載下傳的檔案:%s" % file_name)


def main():

    if len(sys.argv) != :
        print("請按照如下方式運作:python3 xxx.py 7890")
        return
    else:
        # 運作方式為python3 xxx.py 7890
        port = int(sys.argv[])


    # 建立socket
    tcp_server_socket = socket(AF_INET, SOCK_STREAM)
    # 本地資訊
    address = ('', port)
    # 綁定本地資訊
    tcp_server_socket.bind(address)
    # 将主動套接字變為被動套接字
    tcp_server_socket.listen()

    while True:
        # 等待用戶端的連結,即為這個用戶端發送檔案
        client_socket, clientAddr = tcp_server_socket.accept()
        # 接收對方發送過來的資料
        recv_data = client_socket.recv()  # 接收1024個位元組
        file_name = recv_data.decode("utf-8")
        print("對方請求下載下傳的檔案名為:%s" % file_name)
        file_content = get_file_content(file_name)
        # 發送檔案的資料給用戶端
        # 因為擷取打開檔案時是以rb方式打開,是以file_content中的資料已經是二進制的格式,是以不需要encode編碼
        if file_content:
            client_socket.send(file_content)
        # 關閉這個套接字
        client_socket.close()

    # 關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()
           

用戶端 參考代碼如下:

from socket import *


def main():

    # 建立socket
    tcp_client_socket = socket(AF_INET, SOCK_STREAM)

    # 目的資訊
    server_ip = input("請輸入伺服器ip:")
    server_port = int(input("請輸入伺服器port:"))

    # 連結伺服器
    tcp_client_socket.connect((server_ip, server_port))

    # 輸入需要下載下傳的檔案名
    file_name = input("請輸入要下載下傳的檔案名:")

    # 發送檔案下載下傳請求
    tcp_client_socket.send(file_name.encode("utf-8"))

    # 接收對方發送過來的資料,最大接收1024個位元組(1K)
    recv_data = tcp_client_socket.recv()
    # print('接收到的資料為:', recv_data.decode('utf-8'))
    # 如果接收到資料再建立檔案,否則不建立
    if recv_data:
        with open("[接收]"+file_name, "wb") as f:
            f.write(recv_data)

    # 關閉套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()