Python基礎之網絡併發編程
目錄
- Python基礎之網絡併發編程
- 一、黏包現象
- 1、什麼是黏包
- 2、產生黏包的原因
- 3、如何避免黏包
- 二、struct模塊
- 1、模塊簡介
- 2、常用方法
- 3、常見格式
- 三、黏包問題實戰
- 1、解決思路
- 2、代碼實戰
- 四、UDP協議
- 1、UDP協議特點
- 2、代碼實現
一、黏包現象
1、什麼是黏包
黏包是指,當我們基於TCP協議,客户端可服務端進行數據傳輸時,會自動將多個小部份的數據打包成一個大的數據進行發送,例如,在客户端給服務端發送數據時,我們分開發送了 ABC , 123 這兩段信息,按照常理來説,客户端收到的消息也應該兩段分開的消息,而服務端實際收到的消息是 ABC123,可是這並不是我們想要的結果,因為在實際中,我們收到這種消息後很難將多條連接在一起的消息區分開來
代碼表現
1、客户端
# 導入socket
import socket
# 產生socket對象
server = socket.socket()
# 連接主機地址
server.connect(('192.168.1.188', 8899))
# 向主機發送信息
server.send(b'hello tom')
server.send(b'hello jason')
server.send(b'hello jerry')
2、服務端
# 產生socket對象
server = socket.socket()
# 設置主機IP地址
server.bind(('192.168.1.188', 8899))
# 設置半連接池
server.listen(5)
# 獲取客户端接入
sock, addre = server.accept()
sock = sock.recv(1024).decode('utf8')
# 打印客户端信息
print(sock)
---------------------------------------------------------------
hello tomhello jasonhello jerry
2、產生黏包的原因
- 1、在使用socket的模塊,接收消息時 需要設置 recv 也就是接收的字節數,可是我們並不知道對方發送來的消息具體大小是多少,所以就會默認接收recv設置的字節以內的多段的消息
- 2、TCP也稱為流式協議:數據像水流一樣綿綿不絕沒有間隔(TCP會針對數據量較小且發送間隔較短的多條數據一次性合併打包發送)
3、如何避免黏包
核心思路:
1、明確即將接收的消息具體有多大
2、針對消息的大小設置recv的字節數
解決辦法:
1、藉助struct模塊
二、struct模塊
1、模塊簡介
可以將一組數據打包為固定的大小,也可將打包的數據進行解包
2、常用方法
1、struct
功能:導入模塊
1、pack('模式', '待打包的數據')
功能:將一組數據按照指定模式進行打包,打包後的數據為Bytes類型
2、unpack(bytes_data)
功能:解析打包後的數據
3、常見格式
|
格式
|
python類型
|
標準尺寸
|
|
c
|
1個字節長度
|
1
|
|
ns
|
n個字符
|
n
|
|
i
|
整數
|
4
|
|
f
|
浮點
|
4
|
三、黏包問題實戰
1、解決思路
上述,我們瞭解了產生黏包的原因,並且知道了大致解決思路,下面我們將針對黏包問題指定詳細解決辦法
第一步:
分別在客户端和服務端導入struct模塊,通過了解struct模塊,我們知道在pack方法的‘i’模式下,會固定加打包的數據大小壓縮為4個字節
第二步:
產生一個‘字典’類型,在字典內存入我們需要發送的目標文件詳細信息,並將目標文件的大小存入鍵值對,將信息設置為鍵值對格式
第三步:
使用‘len’方法,獲得字典編碼後的字節個數,將字典的個數用'struct'模塊的下'pack'方法進行打包,接收端將'recv'設置為4個字節,並接收打包後的‘字典字節個數’,將‘recv’設置為字典的大小,接收字典後,獲取字典內目標文件大小,再將‘recv’設置為目標文件大小,進行接收目標文件
第四步:
將打包的'字典'使用'unpack'方法解析,並讀取字典信息,獲取到需要接收的數據詳細大小,在將'recv'設置為需要接收的文件大小,進行接收數據
2、代碼實戰
模擬客户端向服務端發送信息,將存有數據的'txt'格式文本發送給服務端
1、服務端
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
sock, addr = server.accept()
# 1.接收固定長度的字典報頭
data_dict_head = sock.recv(4)
# 2.根據報頭解析出字典數據的長度
data_dict_len = struct.unpack('i', data_dict_head)[0]
# 3.接收字典數據
data_dict_bytes = sock.recv(data_dict_len)
data_dict = json.loads(data_dict_bytes) # 自動解碼再反序列化
# 4.獲取真實數據的各項信息
# total_size = data_dict.get('file_size')
# with open(data_dict.get('file_name'), 'wb') as f:
# f.write(sock.recv(total_size))
'''接收真實數據的時候 如果數據量非常大 recv括號內直接填寫該數據量 不太合適 我們可以每次接收一點點 反正知道總長度'''
# total_size = data_dict.get('file_size')
# recv_size = 0
# with open(data_dict.get('file_name'), 'wb') as f:
# while recv_size < total_size:
# data = sock.recv(1024)
# f.write(data)
# recv_size += len(data)
# print(recv_size)
2、客户端
import socket
import os
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8081))
'''任何文件都是下列思路 圖片 視頻 文本 ...'''
# 1.獲取真實數據大小
file_size = os.path.getsize(r'/Users/jiboyuan/PycharmProjects/day36/xx老師合集.txt')
# 2.製作真實數據的字典數據
data_dict = {
'file_name': '有你好看.txt',
'file_size': file_size,
'file_desc': '內容很長 準備好吃喝 我覺得營養快線挺好喝',
'file_info': '這是我的私人珍藏'
}
# 3.製作字典報頭
data_dict_bytes = json.dumps(data_dict).encode('utf8')
data_dict_len = struct.pack('i', len(data_dict_bytes))
# 4.發送字典報頭
client.send(data_dict_len) # 報頭本身也是bytes類型 我們在看的時候用len長度是4
# 5.發送字典
client.send(data_dict_bytes)
# 6.最後發送真實數據
with open(r'/Users/jiboyuan/PycharmProjects/day36/xx老師合集.txt', 'rb') as f:
for line in f: # 一行行發送 和直接一起發效果一樣 因為TCP流式協議的特性
client.send(line)
import time
time.sleep(10)
四、UDP協議
1、UDP協議特點
UDP協議是面向無連接的通訊協議,UDP是不可靠協議,發送方所發送的數據對方不一定能夠接收到
2、代碼實現
服務端:
import socket
# UDP客户端、創建一個服務器端的Socket
socket_server = socket(AF_INET, SOCK_DGRAM)
# 2、定義服務器端的ip地址和端口號,元組形式
host_port = ('192.168.108.43', 8090)
# 3、服務器端的Socket來綁定地址和端口,只有綁定了地址和端口,才能稱為服務器的Socket
socket_server.bind(host_port)
# 4、接收客户端發送過來的數據,每次接收1kb的數據,如果一直沒收到數據會阻塞
# 收到的每一個數據報,裏面是一個元組,第一個值是數據內容,第二個值是源地址
data = socket_server.recvfrom(1024)
# 解碼
print(data[0].decode('utf-8'))
print(data)
# 5、關閉套接字、釋放資源
socket_server.close()
服務器端的結構:
(1) 使用函數socket(),生成套接字描述符;
(2) 通過host_post 結構設置服務器地址和監聽端口;
(3)使用bind()函數綁定監聽端口,將套接字文件描述符和地址類型變(host_post) 進行綁定;
(4)接收客户端的數據,使用recvfrom()函數接收客户端的網絡數據;
(5)關閉套接字,使用close()函數釋放資源;
客户端:
impo
# 客户端發送一個請求也需要端口,端口是隨機分配的
# 創建一個UDP協議的套接字,然後發送一條數據到網絡上的另外一個進程
# UDP客户端、創建套接字
client_socket = socket(AF_INET, SOCK_DGRAM) # SOCK_DGRAM:UDP協議
# 2、定義一個接收消息的目標,8080是一個目標服務器的端口,127.0.0.1是目標服務器地址
server_host_port = ('192.168.108.43', 8080)
# 3、準備即將發送的數據,encode表示按照一種編碼格式把數據變成字節數組bytes
# 數據一定是字節數據才能發送
datas = input('請輸入:').encode('utf-8')
# 4、發送數據,標識一個進程是通過ip+端口+協議
client_socket.sendto(datas, server_host_port)
print('發送完成')
# 5、關閉套接字,其實就是釋放了系統資源
client_socket.close()