4.網路編程 總結
- 2019 年 10 月 3 日
- 筆記
目錄
1.C/S B/S架構
C/S B/S架構 C: client端 B: browse 瀏覽器 S: server端 C/S架構: 基於客戶端與服務端之間的通訊 QQ, 遊戲,皮皮蝦, 快手,抖音. 優點: 個性化設置,響應速度快, 缺點: 開發成本,維護成本高,佔用空間,用戶固定. B/S架構: 基於瀏覽器與服務端之間的通訊 Google瀏覽器,360瀏覽器,火狐瀏覽器等等. 優點: 開發維護成本低,佔用空間相對低,用戶不固定. 缺點: 功能單一,沒有個性化設置,響應速度相對慢一些.
2.網路通訊原理
80年代,固定電話聯繫,(還沒有推廣普通話) 1. 兩台電話之間一堆物理連接介質連接. 2. 撥號,鎖定對方電話的位置. 由於當時沒有統一普通話,所以你如果和河南,山西,廣西,福建等朋友進行友好的溝通交流,你必須學當地的方言. 推廣普通話,統一交流方式. 1. 兩台電話之間一堆物理連接介質連接. 2. 撥號,鎖定對方電話的位置. 3. 統一交流方式. 全球範圍內交流: 1. 兩台電話之間一堆物理連接介質連接. 2. 撥號,鎖定對方電話的位置. 3. 統一交流方式.(英語) 話題轉回互聯網通訊: 我現在想和美國的一個girl聯繫.你如何利用電腦聯繫??? 1. 兩台電腦要有一堆物理連接介質連接. 2. 找到對方電腦軟體位置. 3. 遵循一攬子互聯網通訊協議.
3.osi七層協議
-
簡單串聯五層協議以及作用
-
物理層
物理層指的就是網線,光纖,雙絞線等等物理連接介質 物理層發送的是比特流: 01010101010101010101隻是發送比特流有什麼問題??? 數據應該有規律的分組,分組是數據鏈路層做的事情.
-
數據鏈路層
數據鏈路層對比特流進行分組. 最開始從事互聯網企業的就是美國的幾家公司,各家有各家自定的分組標準.後來統一了標準: 對數據分組的標準. **乙太網協議**: 對比特流進行合理的分組. 一組數據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即可.
-
網路層
**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地址,然後在通訊. 如果不是在同一網段: ?
-
傳輸層
埠協議:確定軟體在電腦的位置
埠協議: UDP協議,TCP協議 65535埠 1~1024作業系統專門使用的埠 舉例: 3306 資料庫 自己開發軟體都是8080以後的埠號
-
應用層
自己定義的協議 廣播(區域網內) + mac地址(電腦位置) + ip(區域網的位置) + 埠(軟體在電腦的位置) 有了以上四個參數:你就可以確定世界上任何一個電腦的軟體的位置.
-
-
第二天回顧
單播:單獨聯繫某一個人 廣播:給所有人發送消息(群發) 比特流: 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版解決粘包現象
- 粘包第一種: 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'