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七層協議
-
簡單串聯五層協議以及作用
-
物理層
物理層指的就是網線,光纖,雙絞線等等物理連接介質 物理層發送的是比特流: 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'\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")}')
最后如果不注釋,回復一次必須接收一次才能再次回復
兩行如果注釋,只發送不接收,可以無限發送.