# 解決粘包
**只有TCP有粘包現象,UDP永遠不會粘包**
**粘包原因** 本質是不知道要收多少
1.tcp一次收的過多,需要下次才接收完,造成粘包
2.tcp發到內核態內存是幾條內容較少的消息,TCP有Nigon算法,把多個內容較少的包合成一個,操作系統再發出去,所以客戶端只會收一次,就全收到
TCP:A端與B端有通信連接,A端send,B端就能收到
UDP:A端一次sendto,B端必須有一次recvfrom與之對應,B端才能真正收到
UDP 如果 一次發的>一次收的,收不了的部分全丟
**解決思路**
1. 第一次發數據量,第二次發數據
2. 選定前n個字節,包含長度,其余為數據
**jason解決辦法** 報頭為dict,可存取文件的額外信息,方便后續處理
服務端
```python
import socket
import subprocess
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1',8081))
server.listen(5)
while True:
conn, addr = server.accept()
print('連接成功')
while True:
try:
cmd = conn.recv(1024)
print('接收成功')
# tcp客戶端發空,會導致服務端夯住(udp不存在此問題,因自帶報頭,為地址和端口信息)
if len(cmd) == 0:break
cmd = cmd.decode('utf-8')
#利用subprocess開啟新進程,可接收命令,並調用shell去執行這個字符串
obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#res接收subprocess的shell標准化輸出和標准化報誤信息
res = obj.stdout.read() + obj.stderr.read()
#將模塊返回的信息的長度,和其他需要提供的信息,做出字典
d = {'file_size':len(res),'info':'xxxxxxx'}
#字典序列化
json_d = json.dumps(d)
# 1.先制作一個提示客戶端,將要發送的字典長度的報頭
# (struct模塊,將int類型的長度,轉化為二進制字符串,只占4字節)
header = struct.pack('i',len(json_d))
# header2=struct.pack('i',len(json_d.encode('utf8'))) 與上句效果相同,算的都是bytes長度
# 2.發送字典報頭
conn.send(header)
# 3.發送字典
conn.send(json_d.encode('utf-8'))
# 4.再發真實數據
conn.send(res)
# conn.send(obj.stdout.read())
# conn.send(obj.stderr.read())
except ConnectionResetError:
break
conn.close()
```
客戶端
```python
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1',8081))
print('連接成功')
while True:
msg = input('>>>:').encode('utf-8')
# tcp客戶端發空,會導致服務端夯住(udp不存在此問題,因為自帶報頭,為地址和端口的信息)
if len(msg) == 0:continue
client.send(msg)
# 1.先接收字典報頭
header_dict = client.recv(4)
# 2.解析報頭 獲取字典的長度
dict_size = struct.unpack('i',header_dict)[0] # 解包的時候一定要加上索引0
# 3.接收字典數據
dict_bytes = client.recv(dict_size)
dict_json = json.loads(dict_bytes.decode('utf-8'))
# 4.從字典中獲取信息
recv_size = 0
real_data = b''
# 必須是 < ,最后一次若本來可以剛好發完 即 recv_size = dict_json,大不了再接收一次
# 若改為 = ,最后一次本來收完,卻還滿足判斷條件,收到的就是空了,會夯住
while recv_size < dict_json.get('file_size'):
data = client.recv(1024)
real_data += data
recv_size += len(data)
print(real_data.decode('gbk'))
```
**egon解決辦法**
https://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
https://www.cnblogs.com/gala/archive/2011/09/22/2184801.html
服務端
```python
from socket import *
import subprocess
import struct
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,addr=tcp_server.accept()
print('新的客戶端鏈接',addr)
while True:
#收
try:
cmd=conn.recv(buffer_size)
if not cmd:break
print('收到客戶端的命令',cmd)
#執行命令,得到命令的運行結果cmd_res
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
err=res.stderr.read()
if err:
cmd_res=err
else:
cmd_res=res.stdout.read()
#發
if not cmd_res:
cmd_res='執行成功'.encode('gbk')
length=len(cmd_res)
# 按照給定的格式(fmt),把數據封裝成字符串
# 即直接把整型變成字符串
data_length=struct.pack('i',length)
# 這里粘包無所謂,客戶端知道代表長度是4個字節
conn.send(data_length)
conn.send(cmd_res)
except Exception as e:
print(e)
break
```
客戶端
```python
from socket import *
import struct
from functools import partial
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd=input('>>: ').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
#解決粘包
# int類型的數據,二進制標准長度就是4,所以先收4個
length_data=tcp_client.recv(4)
# 取[0]第一個值就是,解析就是“int”型
length=struct.unpack('i',length_data)[0]
recv_msg=''.join(iter(partial(tcp_client.recv, buffer_size), b''))
# 這一段出了問題,可學習研究下 應該是缺一個 減 的函數
#解析: 1.tcp_client.recv()函數接收信息,每次最多buffer_size個bytes,
# 利用偏函數把tcp_client.recv()函數的第一個參數buffer_size固定住
# 2.利用iter(),把偏函數變成可迭代對象
# 3.每次從內核態內存獲取一段數據,並拼接到'',直到tcp_client.recv內容為b''
print('命令的執行結果是 ',recv_msg.decode('gbk'))
tcp_client.close()
```
補充:
```python
#iter(object, sentinel)
#只寫一個參數:把第一個變成迭代器
#加第二個參數:sentinel,迭代到sentinel內容立即停下
#偏函數:固定函數的第一個參數
from functools import partial
def add(x,y):
return x+y
#指定add函數,把1傳給add第一個參數
func=partial(add,1)
print(func(1)) #2
print(func(1)) #3
```
pack(fmt, v1, v2, ...) 按照給定的格式(fmt),把數據封裝成字符串(實際上是類似於c結構體的字節流)
unpack(fmt, string) 按照給定的格式(fmt)解析字節流string,返回解析出來的tuple
socket里的sendall,基於send(),就是一個死循環在send,直到所有數據發送。
而send一次最多8k(8096bytes)較好,MTU網卡最大傳輸單元1500bytes,發送過大,數據就要分片重組,丟一個就失真了
**subprocess**
https://www.cnblogs.com/linhaifeng/articles/6129246.html#_label9
**避免通信循環中的報錯**
1.服務端在conn.recv()下一句
用if 為空 :break 解決conn斷開,服務端一直收空消息造成的死循環
2.服務端在通信循環 用處理客戶端進程非正常中斷造成的報錯(遠程主機強迫關閉了一個現有的連接)
try:
#通信循環
except Exception as e:
print(e)
break
傳輸文件-客戶端上傳至服務端
```python
import socket
import os
import json
import struct
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn,addr = server.accept()
while True:
try:
header_len = conn.recv(4)
# 解析字典報頭
header_len = struct.unpack('i',header_len)[0]
# 再接收字典數據
header_dic = conn.recv(header_len)
real_dic = json.loads(header_dic.decode('utf-8'))
# 獲取數據長度
total_size = real_dic.get('file_size')
# 循環接收並寫入文件
recv_size = 0
with open(real_dic.get('file_name'),'wb') as f:
while recv_size < total_size:
data = conn.recv(1024)
f.write(data)
recv_size += len(data)
print('上傳成功')
except ConnectionResetError as e:
print(e)
break
conn.close()
```
客戶端
```python
import socket
import json
import os
import struct
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
# 獲取電影列表 循環展示
MOVIE_DIR = r'D:\python脫產10期視頻\day25\視頻'
movie_list = os.listdir(MOVIE_DIR)
# print(movie_list)
for i,movie in enumerate(movie_list,1):
print(i,movie)
# 用戶選擇
choice = input('please choice movie to upload>>>:')
# 判斷是否是數字
if choice.isdigit():
# 將字符串數字轉為int
choice = int(choice) - 1
# 判斷用戶選擇在不在列表范圍內
if choice in range(0,len(movie_list)):
# 獲取到用戶想上傳的文件路徑
path = movie_list[choice]
# 拼接文件的絕對路徑
file_path = os.path.join(MOVIE_DIR,path)
# 獲取文件大小
file_size = os.path.getsize(file_path)
# 定義一個字典
res_d = {
'file_name':'性感荷官在線發牌.mp4',
'file_size':file_size,
'msg':'注意身體,多喝營養快線'
}
# 序列化字典
json_d = json.dumps(res_d)
json_bytes = json_d.encode('utf-8')
# 1.先制作字典格式的報頭
header = struct.pack('i',len(json_bytes))
# 2.發送字典的報頭
client.send(header)
# 3.再發字典
client.send(json_bytes)
# 4.再發文件數據(打開文件循環發送)
with open(file_path,'rb') as f:
for line in f:
client.send(line)
else:
print('not in range')
else:
print('must be a number')
```