1. 程式人生 > >網路程式設計 總結

網路程式設計 總結

目錄

  • 1.C/S B/S架構
  • 2.網路通訊原理
  • 3.osi七層協議
  • 4.UDP TCP 協議
  • 5.TCP協議的三次握手和四次揮手
  • 6.socket套接字
  • 7.基於TCP協議的socket簡單通訊
  • 8.基於TCP協議的socket迴圈通訊
  • 9.基於TCP協議的socket 連結+迴圈 通訊
  • 10.基於TCP協議的socket應用例項: 執行遠端命令
  • 11.粘包現象
  • 12.low版解決粘包現象
  • 13.recv工作原理
  • 14.高大上版解決粘包方式(自定製包頭)
  • 15.基於UDP協議的socket通訊
  • 16.socketserver(待講)

1.C/S B/S架構

C/S B/S架構
C: client端
B: browse 瀏覽器
S: server端
C/S架構: 基於客戶端與服務端之間的通訊
​   QQ, 遊戲,皮皮蝦, 快手,抖音.
​   優點: 個性化設定,響應速度快,
​   缺點: 開發成本,維護成本高,佔用空間,使用者固定.
B/S架構: 基於瀏覽器與服務端之間的通訊
​   谷歌瀏覽器,360瀏覽器,火狐瀏覽器等等.
​   優點: 開發維護成本低,佔用空間相對低,使用者不固定.
​   缺點: 功能單一,沒有個性化設定,響應速度相對慢一些.

2.網路通訊原理

80年代,固定電話聯絡,(還沒有推廣普通話)
1. 兩臺電話之間一堆物理連線介質連線.
2. 撥號,鎖定對方電話的位置.
由於當時沒有統一普通話,所以你如果和河南,山西,廣西,福建等朋友進行友好的溝通交流,你必須學當地的方言.

推廣普通話,統一交流方式.
1. 兩臺電話之間一堆物理連線介質連線.
2. 撥號,鎖定對方電話的位置.
3. 統一交流方式.

全球範圍內交流:
1. 兩臺電話之間一堆物理連線介質連線.
2. 撥號,鎖定對方電話的位置.
3. 統一交流方式.(英語)

話題轉回網際網路通訊:
​  我現在想和美國的一個girl聯絡.你如何利用計算機聯絡???
1. 兩臺計算機要有一堆物理連線介質連線.
2. 找到對方計算機軟體位置.
3. 遵循一攬子網際網路通訊協議.

3.osi七層協議

  1. 簡單串聯五層協議以及作用

    1. 物理層

      物理層指的就是網線,光纖,雙絞線等等物理連線介質
      
      物理層傳送的是位元流: 01010101010101010101只是傳送位元流有什麼問題???
      
      資料應該有規律的分組,分組是資料鏈路層做的事情.
    2. 資料鏈路層

      資料鏈路層對位元流進行分組.
      最開始從事網際網路企業的就是美國的幾家公司,各家有各家自定的分組標準.後來統一了標準: 對資料分組的標準.
      
      **乙太網協議**: 對位元流進行合理的分組.
      一組資料01010101 叫做一幀,資料報.
      ​ head  |  data(晚上約麼)
      
      head是固定的長度:18個位元組
      ​ 源地址: 6個位元組   
      ​ 目標地址: 6個位元組 
      ​ 資料型別: 6個位元組
      
      data: 最少是46個位元組,最大1500位元組.
      一幀資料: 最少64個位元組,最大1518個位元組.
      一幀資料|一幀資料......
      每個電腦上都有一個網絡卡,往卡上都記錄一個獨一無二的地址.
      
      **mac地址**: 就是你的計算機上網絡卡上標註的地址.
      12位16進位制陣列成 :前六位是廠商編號,後六位是流水線號.
      源mac地址 目標mac地址 資料型別 | data
      '1C-1B-0D-A4-E6-44'
      計算機的通訊方式:
      同一個區域網內,通過廣播的形式通訊.
      
      訊息一經廣播發出,村裡所有的人(區域網所有的計算機都能接收到訊息,分析訊息,是否是找我的,不是就丟棄),
      計算機只能在區域網內進行廣播: 範圍大了 廣播風暴,效率極低.
      還有兩個沒有解決:
      1. 不同區域網如何通訊?
      2. 軟體與軟體的通訊,而不是計算機之間的通訊.
      補充:
          同一個區域網通過廣播的形式傳送資料.
          交換機的mac地址學習功能:
          一個交換機的5個介面: 5個計算機.
           1: FF-FF-FF-FF-FF-FF
           2: FF-FF-FF-FF-FF-FF
           3: FF-FF-FF-FF-FF-FF
           4: FF-FF-FF-FF-FF-FF
           5: FF-FF-FF-FF-FF-FF
          介面1:  源mac 1C-1B-0D-A4-E6-44 目標1C-1C-0D-A4-E5-44 |資料 以廣播的形式發出
          2,3,4,5口都會接收到訊息,5口是最終的目標地址,交換機就會將5口與mac地址對應上.
           1: 1C-1B-0D-A4-E6-44
           2: FF-FF-FF-FF-FF-FF
           3: FF-FF-FF-FF-FF-FF
           4: FF-FF-FF-FF-FF-FF
           5: 1C-1C-0D-A4-E5-44
          當五個口都對應上具體的mac地址,2口再次發訊息,就不會廣播了,就會以單播發送.
          **我們的前提是什麼**? 你必須知道對方的mac地址你才可以以廣播的形式發訊息.實際上,網路通訊中,你只要知道對方的IP與自己的IP即可.
    3. 網路層

      **IP協議**: 確定區域網(子網)的位置
      找到具體軟體的位置,上一層的事情
          IP協議: 
              ip地址:四段分十進位制 192.168.0.12  
              取值範圍 0~255.0~255.0~255.0~255
              子網掩碼: C類子網掩碼: 255.255.255.0
              ip地址 + 子網掩碼 按位與運算 計算出是否在統一區域網(子網,網段).
              計算172.16.10.1 與 172.16.10.128
              ​    172.16.10.1:10101100.00010000.00001010.00000001
              255.255.255.0:   11111111.11111111.11111111.00000000
              從屬於的區域網: 172.16.10.0
              172.16.10.128:10101100.00010000.00001010.10000000
              255.255.255.0:   11111111.11111111.11111111.00000000
              從屬於的區域網: 172.16.10.0
              172.16.10.1 ~172.16.10.255
              C類子網掩碼 一個網段最多可以承載多個IP地址?
              172.16.10.0 被佔用.
              172.16.10.255 廣播地址 被佔用.
              172.16.10.1 被佔用.
              253臺計算機.
              如果你要想給另一個計算機發資料, 你一定要知道對方的ip地址.
              **ARP協議**:通過對方的ip地址獲取到對方的mac地址.
            原始碼mac  目標mac   源IP    目標IP    資料
              1C-1B-0D-A4-E6-44  FF:FF:FF:FF:FF:FF 172.16.10.13 172.16.10.156    資料
      
              第一次發訊息: 傳送到交換機 ---> 路由器  廣播的形式發出去
              目標計算機收到訊息:就要回訊息:
               原始碼mac  目標mac   源IP    目標IP    資料
              1B-1B-0D-A4-E6-54  1C-1B-0D-A4-E6-44 172.16.10.156 172.16.10.13    資料
      總結:
          前提:知道目標mac:
          ​ 計算機A 傳送一個訊息給 計算機B 
          ​     原始碼mac  目標mac   源IP    目標IP    資料
          ​ 單播的形式傳送到交換機,交換機會檢測自己的對照表有沒有目標mac,如果有,單播傳.如果沒有,交由上一層: 路由器:
          路由器收到訊息: 對訊息進行分析: 
          要確定目標計算機與本計算機是否在同一網段,
          ​ 如果在同一網段,直接傳送給對應的交換機,交換機在單播發給目標mac.
          ​ 如果不是在同一網段: ?
      
          前提:不知道目標mac:
          ​ 計算機A 傳送一個訊息給 計算機B 
          ​     原始碼mac  目標mac不知道   源IP    目標IP    資料
          ​ 單播的形式傳送到交換機,交換機交由上一層路由器:路由器收到訊息: 對訊息進行分析: 
          要確定目標計算機與本計算機是否在同一網段,
          ​ 如果在同一網段通過 IP以及ARP協議獲取到對方的mac地址,然後在通訊.
          ​ 如果不是在同一網段: ?
    4. 傳輸層

      埠協議:確定軟體在計算機的位置
      埠協議:  UDP協議,TCP協議
      65535埠
      1~1024作業系統專門使用的埠
      舉例: 3306 資料庫
      自己開發軟體都是8080以後的埠號
    5. 應用層

      自己定義的協議
      
      廣播(區域網內) + mac地址(計算機位置) + ip(區域網的位置) + 埠(軟體在計算機的位置)
      有了以上四個引數:你就可以確定世界上任何一個計算機的軟體的位置.
  2. 第二天回顧

    單播:單獨聯絡某一個人
    廣播:給所有人傳送訊息(群發)
    位元流: bit就是 0101 跟水流一樣的源源不斷的傳送010101001
    乙太網協議: 將資料進行分組:一組稱之為一幀,資料報.
    ​    head | data
    head: 18個位元組:  源mac地址 | 目標mac地址| 資料型別
    data: 最少46個位元組, 最多是1500個位元組
    mac地址: 就是計算機網絡卡上記錄的地址,世界上所有的計算機獨一無二的標識,用於區域網內廣播(單播)時查詢的計算機的位置
    交換機: 分流連線計算機的作用
    路由器: 家用路由器和企業版路由器
    
    
    交換機的mac學習功能:
    ​    第一次傳送訊息廣播的形式,當學習表記錄上埠與mac地址對應關係之後,在傳送訊息: 單播的形式傳送.
    ​    埠1:  1C-5F-4B-3E-35-2C
    ​    埠2:  1C-5F-4B-6E-35-2C
    
    廣播風暴: 所有的計算機都在廣播的形式傳送訊息.
    IP協議: 四段分十進位制
    ​    172.168.0.1
    
    子網掩碼:
    ​    A: 255.0.0.0
    ​    B: 255.255.0.0
    ​    C: 255.255.255.0
    
    路由器: 
    ​    外網(公網)IP, 
    ​    內網(區域網)IP 都是假的,DHCP協議: 路由器自動分發的IP地址,閘道器等等.
    
    
    埠: 0~1023系統的, 自己選取埠8080 以後都可以.
    ARP協議: 通過IP獲取計算機mac地址.
    TCP協議:  面向連結的協議,流式協議.安全可靠效率低的協議, 傳輸檔案,瀏覽器等.
    UDP協議: 使用者資料報協議,效率高,不可靠的協議, 微信
    
    三次握手和四次揮手:

4.UDP TCP 協議

TCP(Transmission Control Protocol)可靠的、面向連線的協議(eg:打電話)、流式協議, 傳輸效率低全雙工通訊(傳送快取&接收快取)、面向位元組流。使用TCP的應用:Web瀏覽器;檔案傳輸程式。

UDP(User Datagram Protocol)不可靠的、無連線的服務,傳輸效率高(傳送前時延小),一對一、一對多、多對一、多對多、面向報文(資料包),盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視訊流;IP語音(VoIP)。

5.TCP協議的三次握手和四次揮手

syn洪水攻擊:製造大量的假的無效的IP請求伺服器.致使正常的IP訪問不了伺服器.

6.socket套接字

socket套接字:
    1.socket是處於應用層與傳輸層之間的抽象層,他是一組操作起來非常簡單的介面(接受資料)此介面接受資料之後,交由作業系統.
    為什麼存在socket抽象層?
    如果直接與作業系統資料互動非常麻煩,繁瑣,socket對這些繁瑣的的操作高度的封裝,簡化.
    2.socket在python中就是一個模組.

7.基於TCP協議的socket簡單通訊

# 服務端

import socket

phone = socket.socket()

phone.bind(('192.168.14.230', 8849))

phone.listen(2)  # listen 允許幾個人連結,剩下的連結等待

conn, addr = phone.accept()  # 等待客戶端連線我,阻塞的狀態中
print(f'連結來了{conn,addr}')


from_client_data = conn.recv(1024)
print(f'來自客戶端{addr}訊息:{from_client_data.decode("utf-8")}')

to_client_data = input('>>>').strip().encode('utf-8')
conn.send(to_client_data)
conn.close()
phone.close()
# 客戶端
import socket
phone = socket.socket()

phone.connect(('192.168.14.230', 8849))

to_server_data = input('>>>').strip().encode('utf-8')
phone.send(to_server_data)

from_server_data = phone.recv(1024)
print(f'來自伺服器的訊息:{from_server_data}')

8.基於TCP協議的socket迴圈通訊

總結:
    服務端和客戶端都加迴圈,如果正常退出雙方都直接break,設定判斷資訊
    服務端在客戶等待連線的後面加while迴圈,客戶端在連結地址之後加迴圈
    服務端需要加一個異常退出的異常處理,提示異常退出
# 服務端
import socket

phone = socket.socket()

phone.bind(('192.168.14.230', 8849))

phone.listen(2)  # listen 允許幾個人連結,剩下的連結等待

conn, addr = phone.accept()  # 等待客戶端連線我,阻塞的狀態中
print(f'連結來了{conn,addr}')

while 1:
    try:
        from_client_data = conn.recv(1024)

        if from_client_data.upper() == b'Q':  # 正常退出 服務端跟著關閉
            print('客戶正常退出聊天了')
            break

        print(f'來自客戶端{addr}訊息:{from_client_data.decode("utf-8")}')
        to_client_data = input('>>>').strip().encode('utf-8')
        conn.send(to_client_data)
    except ConnectionResetError:  # 異常退出 會報錯 寫提示內容
        print('客戶端連結中斷了')
        break

conn.close()
phone.close()
# 客戶端
import socket
phone = socket.socket()

phone.connect(('192.168.14.230', 8849))

while 1:
    to_server_data = input('>>>').strip().encode('utf-8')
    if not to_server_data:  # 服務端如果收到了空的內容,服務端就會一直阻塞中.無論是那一端傳送,都不能為空
        print('傳送內容不能為空')
        continue

    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':  # 判斷如果是Q的話就退出,正常退出
        break

    from_server_data = phone.recv(1024)
    print(f'來自伺服器的訊息:{from_server_data}')
phone.close()

9.基於TCP協議的socket 連結+迴圈 通訊

總結:
    服務端在客戶端連結之前再加一層while迴圈,並且把關閉此次通話加到迴圈最下面
    listen(2) 允許2個人連結,剩下的連結等待 (實際上三個人鏈接),超過就會報錯
    如果第一個連結時,第二個發了資訊,當第一個關閉的時候自動接收第二個傳送的資訊
# 服務端
import socket

phone = socket.socket()  # 買電話

phone.bind(('192.168.14.230', 8849))  # 0-65535  1024之前系統分配好的埠 繫結電話卡

phone.listen(2)  # listen 允許2個人連結,剩下的連結等待 (實際上三個人鏈接)

while 1:
    conn, addr = phone.accept()  # 等待客戶端連線我,阻塞的狀態中
    print(f'連結來了{conn,addr}')

    while 1:
        try:
            from_client_data = conn.recv(1024)

            if from_client_data.upper() == b'Q':  # 正常退出 客戶端通道跟著關閉
                print('客戶正常退出聊天了')
                break

            print(f'來自客戶端{addr}訊息:{from_client_data.decode("utf-8")}')
            to_client_data = input('>>>').strip().encode('utf-8')
            conn.send(to_client_data)
        except ConnectionResetError:  # 異常退出 會報錯 寫提示內容
            print('客戶端連結中斷了')
            break
    conn.close()
phone.close()
# 客戶端
import socket

phone = socket.socket()

phone.connect(('192.168.14.230', 8849))

while 1:
    to_server_data = input('>>>').strip().encode('utf-8')
    if not to_server_data:  # 服務端如果收到了空的內容,服務端就會一直阻塞中.無論是那一端傳送,都不能為空
        print('傳送內容不能為空')
        continue

    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':  # 判斷如果是Q的話就退出,正常退出
        break

    from_server_data = phone.recv(1024)  # 最多接受的位元組數量
    print(f'來自伺服器的訊息:{from_server_data}')

phone.close()

10.基於TCP協議的socket應用例項: 執行遠端命令

總結:
    服務端先匯入subprocess模組,作用是可以執行命令,
    然後修改接收內容,改成操作命令的固定程式碼
    客戶端接收內容需要改成gbk編碼,因為windows作業系統的預設編碼是gbk編碼,蘋果系統不需要改
    """
    shell: 命令直譯器,相當於呼叫cmd 執行指定的命令。
    stdout:正確結果丟到管道中。
    stderr:錯了丟到另一個管道中。
    windows作業系統的預設編碼是gbk編碼。
    """
# 服務端
import socket
import subprocess

phone = socket.socket()

phone.bind(('192.168.14.230', 8849))

phone.listen(2)  # listen 允許2個人連結,剩下的連結等待

while 1:
    conn, addr = phone.accept()  # 等待客戶端連線我,阻塞的狀態中
    print(f'連結來了{conn,addr}')

    while 1:
        try:
            from_client_data = conn.recv(1024)

            if from_client_data.upper() == b'Q':  # 正常退出 客戶端通道跟著關閉
                print('客戶正常退出聊天了')
                break

            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                   shell=True,  # shell: 命令直譯器,相當於呼叫cmd 執行指定的命令。
                                   stdout=subprocess.PIPE,  # stdout:正確結果丟到管道中。
                                   stderr=subprocess.PIPE,  # stderr:錯了丟到另一個管道中。
                                   )
            result = obj.stdout.read() + obj.stderr.read()

            conn.send(result)

        except ConnectionResetError:  # 異常退出 會報錯 寫提示內容
            print('客戶端連結中斷了')
            break
    conn.close()
phone.close()
# 客戶端
import socket

phone = socket.socket()

phone.connect(('192.168.14.230', 8849))

while 1:
    to_server_data = input('>>>').strip().encode('utf-8')
    if not to_server_data:  # 服務端如果收到了空的內容,服務端就會一直阻塞中.無論是那一端傳送,都不能為空
        print('傳送內容不能為空')
        continue

    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':  # 判斷如果是Q的話就退出,正常退出
        break

    from_server_data = phone.recv(1024)  # 最多接受的位元組數量
    print(f'來自伺服器的訊息:{from_server_data.decode("gbk")}')

phone.close()
作業系統的快取區:
    1. 為什麼存在緩衝區??
        1. 暫時儲存一些資料.
        2. 緩衝區存在如果你的網路波動,保證資料的收發穩定,勻速.
        缺點: 造成了粘包現象之一.

11.粘包現象

第一個粘包現象:
    同時多次接收send每次資料太少會形成粘包現象,因為太快多次合併成一次傳送
    連續短暫的send多次(資料量很小),你的資料會統一發送出去,
第二個粘包現象:
    一次接收send資料量太大,導致一次接收不完,第二次再次接收還是第一次剩餘內容.
    深入研究收發解決方法
如何解決粘包現象:
    解決粘包現象的思路:
    服務端發一次資料 10000位元組, 
    客戶端接收資料時,迴圈接收,每次(至多)接收1024個位元組,直至將所有的位元組全部接收完畢,將接收的資料拼接在一起,最後解碼.
    1. 遇到的問題: recv的次數無法確定
        你傳送總具體資料之前,先給我發一個總資料的長
        度:5000個位元組。然後在傳送總資料。
        客戶端: 先接收一個長度。 5000個位元組。
        然後我再迴圈recv 控制迴圈的條件就是隻要你接受的資料< 5000 一直接收。
        
    2. 遇到的問題: 總資料的長度轉化成的位元組數不固定
        >>>服務端:
        conn.send(total_size)
        conn.send(result)
        total_size int型別
        >>>客戶端:
        total_size_bytes = phone.recv(4)
        total_size
        data = b''
        while len(data) < total_size:
            data = data + phone.recv(1024)
            
            
你要將total_size int型別轉化成bytes型別才可以傳送
    387 ---- > str(387) '387' ---->bytes b'387' 長度 3bytes
    4185 ----> str(4185) '4185' ---->bytes b'4185' 長度 4bytes
    18000------------------------------------------------------> 長度 5bytes
我們要解決:
    將不固定長度的int型別轉化成固定長度的bytes並且還可以翻轉回來。
多次接收解決粘包現象,但不是根本解決:
    
    from_client_data = conn.recv(3)  # 最多接受1024位元組
    print(f'來自客戶端{addr}訊息:{from_client_data.decode("utf-8")}')

    from_client_data = conn.recv(3)  # 最多接受1024位元組
    print(f'來自客戶端{addr}訊息:{from_client_data.decode("utf-8")}')

    from_client_data = conn.recv(3)  # 最多接受1024位元組
    print(f'來自客戶端{addr}訊息:{from_client_data.decode("utf-8")}')

    from_client_data = conn.recv(3)  # 最多接受1024位元組
    print(f'來自客戶端{addr}訊息:{from_client_data.decode("utf-8")}')

12.low版解決粘包現象

  1. 粘包第一種: send的資料過大,大於對方recv的上限時,對方第二次recv時,會接收上一次沒有recv完的剩餘的資料。
匯入struct模組:
    服務端製作固定長度的報頭使用
    客戶端反解報頭使用
程式碼實驗有效作用:
    服務端:
        total_size = len(result)  # 檢視位元組
        print(f'總位元組數:{total_size}')
        head_bytes = struct.pack('i', total_size)  # 1. 製作固定長度的報頭  'i'固定四個報頭
        conn.send(head_bytes)  # 2. 傳送固定長度的報頭
        conn.send(result)  # 3. 傳送總資料
    客戶端:
        head_bytes = phone.recv(4)  # 1. 接收報頭 
        total_size = struct.unpack('i', head_bytes)[0]  # 2. 反解報頭 'i'固定四個報頭
        total_data = b''  # 接收內容,依次相加bytes型別,如果只是英文可以不加ASCII碼
        while len(total_data) < total_size:  # 接收的內容長度不會超過反解包頭的長度,所以用判斷
            total_data += phone.recv(1024)  # 本來就是反解報頭,然後直接全部接收,然後每1024處理一次,直到結束
# 服務端
import socket
import subprocess
import struct

phone = socket.socket()

phone.bind(('192.168.14.230', 8849))

phone.listen(2)  # listen 允許2個人連結,剩下的連結等待

while 1:
    conn, addr = phone.accept()  # 等待客戶端連線我,阻塞的狀態中
    # print(f'連結來了{conn,addr}')

    while 1:
        try:
            from_client_data = conn.recv(1024)

            if from_client_data.upper() == b'Q':  # 正常退出 服務端跟著關閉
                print('客戶正常退出聊天了')
                break

            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                   shell=True,  # shell: 命令直譯器,相當於呼叫cmd 執行指定的命令。
                                   stdout=subprocess.PIPE,  # stdout:正確結果丟到管道中。
                                   stderr=subprocess.PIPE,  # stderr:錯了丟到另一個管道中。
                                   )
            result = obj.stdout.read() + obj.stderr.read()  # 接收正確或者錯誤的命令

            total_size = len(result)  # 檢視位元組
            print(f'總位元組數:{total_size}')

            head_bytes = struct.pack('i', total_size)  # 1. 製作固定長度的報頭  'i'固定四個報頭

            conn.send(head_bytes)  # 2. 傳送固定長度的報頭

            conn.send(result)  # 3. 傳送總資料

        except ConnectionResetError:  # 異常退出 會報錯 寫提示內容
            print('客戶端連結中斷了')
            break
    conn.close()
phone.close()
# 客戶端
import socket
import struct

phone = socket.socket()

phone.connect(('192.168.14.230', 8849))

while 1:
    to_server_data = input('>>>').strip().encode('utf-8')
    if not to_server_data:  # 服務端如果收到了空的內容,服務端就會一直阻塞中.無論是那一端傳送,都不能為空
        print('傳送內容不能為空')
        continue

    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':  # 判斷如果是Q的話就退出,正常退出
        break

    head_bytes = phone.recv(4)  # 1. 接收報頭

    total_size = struct.unpack('i', head_bytes)[0]  # 2. 反解報頭 'i'固定四個報頭

    total_data = b''  # 接收內容,依次相加bytes型別,如果只是英文可以不加ASCII碼

    while len(total_data) < total_size:
        total_data += phone.recv(1024)

    print(len(total_data))
    print(total_data.decode('gbk'))

phone.close()

13.recv工作原理

原始碼解釋:
Receive up to buffersize bytes from the socket.接收來自socket緩衝區的位元組資料,

For the optional flags argument, see the Unix manual.對於這些設定的引數,可以檢視Unix手冊。

When no data is available, block untilat least one byte is available or until the remote end is closed.當緩衝區沒有資料可取時,recv會一直處於阻塞狀態,直到緩衝區至少有一個位元組資料可取,或者遠端端關閉。

When the remote end is closed and all data is read, return the empty string.關閉遠端端並讀取所有資料後,返回空字串。
理解:
    recv空字串: 對方客戶端關閉了,且服務端的緩衝區沒有資料了,我再recv取到空bytes.
    1 驗證服務端緩衝區資料沒有取完,又執行了recv執行,recv會繼續取值。
    2 驗證服務端緩衝區取完了,又執行了recv執行,此時客戶端20秒內不關閉的前提下,recv處於阻塞狀態。
    3 驗證服務端緩衝區取完了,又執行了recv執行,此時客戶端處於關閉狀態,則recv會取到空字串。

14.高大上版解決粘包方式(自定製包頭)

服務端:
    1.自定製報頭
        head_dic = {  
        'file_name': 'test1',  # 需要操作的檔名.使用變數
        'md5': 987654321,  # 檔案位元組的md5加密,校驗使用.變數
        'total_size': total_size,  # 位元組總長度
        }
    2.json形式的報頭
            head_dic_json = json.dumps(head_dic)
    3.bytes形式報頭
            head_dic_json_bytes = head_dic_json.encode('utf-8')
    4.獲取bytes形式的報頭的總位元組數
            len_head_dic_json_bytes = len(head_dic_json_bytes)
    5.將不固定的int總位元組數程式設計固定長度的4個位元組
            four_head_bytes = struct.pack('i', len_head_dic_json_bytes)
    6.傳送固定的4個位元組
            conn.send(four_head_bytes)
    7.傳送報頭資料
            conn.send(head_dic_json_bytes)  
    8.傳送總資料
            conn.send(result)

客戶端:
    1.接收報頭
        head_bytes = phone.recv(4)
    2.獲得bytes型別字典的總位元組數
        len_head_dic_json_bytes = struct.unpack('i', head_bytes)[0]
    3.接收bytes型別的dic資料
        head_dic_json_bytes = phone.recv(len_head_dic_json_bytes)
    4.轉化成json型別dic
        head_dic_json = head_dic_json_bytes.decode('utf-8')
    5.轉化成字典形式的報頭
        head_dic = json.loads(head_dic_json)
# 服務端
import socket
import subprocess
import struct
import json

phone = socket.socket()

phone.bind(('192.168.14.230', 8849))

phone.listen(2)  # listen 允許2個人連結,剩下的連結等待

while 1:
    conn, addr = phone.accept()  # 等待客戶端連線我,阻塞的狀態中
    # print(f'連結來了{conn,addr}')

    while 1:
        try:
            from_client_data = conn.recv(1024)

            if from_client_data.upper() == b'Q':  # 正常退出 服務端跟著關閉
                print('客戶正常退出聊天了')
                break

            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                                   shell=True,  # shell: 命令直譯器,相當於呼叫cmd 執行指定的命令。
                                   stdout=subprocess.PIPE,  # stdout:正確結果丟到管道中。
                                   stderr=subprocess.PIPE,  # stderr:錯誤丟到另一個管道中。
                                   )
            result = obj.stdout.read() + obj.stderr.read()  # 接收正確或者錯誤的命令

            total_size = len(result)  # 位元組
            print(f'總位元組數:{total_size}')  # 檢視位元組

            head_dic = {  # 1 自定義報頭
                'file_name': 'test1',  # 需要操作的檔名.使用變數
                'md5': 987654321,  # 檔案位元組的md5加密,校驗使用.變數
                'total_size': total_size,  # 位元組總長度
            }

            head_dic_json = json.dumps(head_dic)  # 2 json形式的報頭

            head_dic_json_bytes = head_dic_json.encode('utf-8')  # 3 bytes形式報頭

            len_head_dic_json_bytes = len(head_dic_json_bytes)  # 4 獲取bytes形式的報頭的總位元組數

            four_head_bytes = struct.pack('i', len_head_dic_json_bytes)  # 5 將不固定的int總位元組數程式設計固定長度的4個位元組

            conn.send(four_head_bytes)  # 6 傳送固定的4個位元組

            conn.send(head_dic_json_bytes)  # 7 傳送報頭資料

            conn.send(result)  # 8 傳送總資料

        except ConnectionResetError:  # 異常退出 會報錯 寫提示內容
            print('客戶端連結中斷了')
            break
    conn.close()
phone.close()
# 客戶端
import socket
import struct
import json

phone = socket.socket()

phone.connect(('192.168.14.230', 8849))

while 1:
    to_server_data = input('>>>').strip().encode('utf-8')
    if not to_server_data:  # 服務端如果收到了空的內容,服務端就會一直阻塞中.無論是那一端傳送,都不能為空
        print('傳送內容不能為空')
        continue

    phone.send(to_server_data)
    if to_server_data.upper() == b'Q':  # 判斷如果是Q的話就退出,正常退出
        break

    head_bytes = phone.recv(4)  # 1. 接收報頭

    len_head_dic_json_bytes = struct.unpack('i', head_bytes)[0]  # 2 獲得bytes型別字典的總位元組數

    head_dic_json_bytes = phone.recv(len_head_dic_json_bytes)  # 3 接收bytes型別的dic資料

    head_dic_json = head_dic_json_bytes.decode('utf-8')  # 4 轉化成json型別dic

    head_dic = json.loads(head_dic_json)  # 5 轉化成字典形式的報頭

    '''
    head_dic = {
            head_dic = {  # 1 自定義報頭
                'file_name': 'test1',  # 需要操作的檔名.使用變數
                'md5': 987654321,  # 檔案位元組的md5加密,校驗使用.變數
                'total_size': total_size,  # 位元組總長度
            }
    '''

    total_data = b''  # 接收內容,依次相加bytes型別,如果只是英文可以不加ASCII碼

    while len(total_data) < head_dic['total_size']:  # 接收的內容長度不會超過反解包頭的長度,所以用判斷
        total_data += phone.recv(1024)  # 本來就是反解報頭,然後直接全部接收,然後每1024處理一次,直到結束

    print(len(total_data))
    print(total_data.decode('gbk'))

phone.close()

15.基於UDP協議的socket通訊

1. 基於udp協議的socket無須建立管道,先開啟服務端或者客戶端都行.
2. 基於udp協議的socket接收一個訊息,與傳送一個訊息都是無連線的.
3. 只要拿到我的ip地址和埠就都可以給我發訊息,我按照順序接收訊息.

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  
基於網路的UDP協議的socket  socket.SOCK_DGRAM
# 服務端
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 基於網路的UDP協議的socket
server.bind(('192.168.14.198', 9000))

while 1:
    from_client_data = server.recvfrom(1024)  # 阻塞,等待客戶來訊息
    print(f'\033[1;35;0m來自客戶端{from_client_data[1]}: {from_client_data[0].decode("utf-8")} \033[0m')
    to_client_data = input('>>>').strip()
    server.sendto(to_client_data.encode('utf-8'), from_client_data[1])
    
    最後如果不註釋,接收一次必須回覆一次才能繼續接收
    兩行如果註釋,只接受不傳送,可以無限接收.
import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 基於網路的UDP協議的socket

while 1:
    to_server_data = input('>>>:').strip()
    client.sendto(to_server_data.encode('utf-8'), ('127.0.0.1', 9000))
    data,addr = client.recvfrom(1024)
    print(f'來自服務端{addr}訊息:{data.decode("utf-8")}')
    
    最後如果不註釋,回覆一次必須接收一次才能再次回覆
    兩行如果註釋,只發送不接收,可以無限傳送.

16.socketserver(待