4.網絡編程 總結

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. 簡單串聯五層協議以及做用shell

    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. 網絡層json

      **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. 傳輸層windows

      端口協議:肯定軟件在計算機的位置
      端口協議:  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)不可靠的、無鏈接的服務,傳輸效率高(發送前時延小),一對1、一對多、多對1、多對多、面向報文(數據包),盡最大努力服務,無擁塞控制。使用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(待講)

相關文章
相關標籤/搜索