目录
- Python网络编程04/recv原理/高大上版解决粘包方式
- 1.昨日内容回顾
- 2.recv工作原理
- 3.高大上版解决粘包方式(自定制报头)
- 3.1 解决思路:
- 3.2 服务端
- 3.3客户端
- 4.基于UDP协议的socket通信
- 4.1服务端
- 4.2客户端
Python网络编程04/recv原理/高大上版解决粘包方式
1.昨日内容回顾
1. 通信循环
2. 链接通信循环
3. 远程执行命令: subprocess.Popen()
# bytes: 网络传输, 文件存储时.
4. 粘包现象
1. 对方发来的数据大于自己recv的上线,下一次在recv会读取剩余的数据.
2. 连续多次send数据量较小的数据,这些数据会粘在一起发送出去.
5. 缓冲区: 输入缓冲区,输出缓冲区. 存储少量数据,避免网络不稳,造成你传输数据时的卡顿,保持相对平稳,稳定.
#6. 收发的本质:
不一定是一收一发.
7. 如何解决粘包?
low版: 制作一个固定的报头.
获取总数据的长度. 7878
利用struct模块将int 7878 ---> ret = 4个字节
send(ret)
send(总数据)
客户端:
head_bytes = recv(4)
head = struct.unpack('i',head_bytes)[0] 7878
利用while循环判断:
循环recv.
2.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.
关闭远程端并读取所有数据后,返回空字符串。
1,验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。(服务端)
# import socket
#
# phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#
# phone.bind(('127.0.0.1',8080))
#
# phone.listen(5)
#
# conn, client_addr = phone.accept()
# from_client_data1 = conn.recv(2)
# print(from_client_data1)
# from_client_data2 = conn.recv(2)
# print(from_client_data2)
# from_client_data3 = conn.recv(1)
# print(from_client_data3)
# conn.close()
# phone.close()
验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。(客户端)
# import socket
# phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# phone.connect(('127.0.0.1',8080))
# phone.send('hello'.encode('utf-8'))
# phone.close()
2,验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。(服务端)
#
# import socket
#
# phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#
# phone.bind(('127.0.0.1',8080))
#
# phone.listen(5)
#
# conn, client_addr = phone.accept()
# from_client_data = conn.recv(1024)
# print(from_client_data)
# print(111)
# conn.recv(1024) # 此时程序阻塞20秒左右,因为缓冲区的数据取完了,并且20秒内,客户端没有关闭。
# print(222)
#
# conn.close()
# phone.close()
验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态(客户端)
# import socket
# import time
# phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# phone.connect(('127.0.0.1',8080))
# phone.send('hello'.encode('utf-8'))
# time.sleep(20)
#
# phone.close()
#
3 验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
#
# import socket
#
# phone =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#
# phone.bind(('127.0.0.1',8080))
#
# phone.listen(5)
#
# conn, client_addr = phone.accept()
# from_client_data1 = conn.recv(1024)
# print(from_client_data1)
# from_client_data2 = conn.recv(1024)
# print(from_client_data2)
# from_client_data3 = conn.recv(1024)
# print(from_client_data3)
# conn.close()
# phone.close()
验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
# import socket
# import time
# phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# phone.connect(('127.0.0.1',8080))
# phone.send('hello'.encode('utf-8'))
# phone.close()
recv空字符串: 对方客户端关闭了,且服务端的缓冲区没有数据了,我再recv取到空bytes.
3.高大上版解决粘包方式(自定制报头)
3.1 解决思路:
我们要制作固定的报头
你现在有两段不固定长度的bytes类型,我们要固定的报头,所以
1. 你获取不固定报头的长度
2. 利用struct模块将不固定的长度转化成固定的字节数 4个字节
3. 先发4个字节,在发报头数据,在发总数据
3.2 服务端
# FTP 应用层自定义协议
# '''
# 1. 高大上版: 自定制报头
# dic = {'filename': XX, 'md5': 654654676576776, 'total_size': 26743}
# 2. 高大上版:可以解决文件过大的问题.
#
#
# '''
# # import struct
#
# # ret = struct.pack('Q',21321432423544354365563543543543)
# # print(ret)
#
# import socket
# import subprocess
# import struct
# import json
# phone = socket.socket()
#
# phone.bind(('127.0.0.1',8848))
#
# 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,
# stdout=subprocess.PIPE,
# stderr=subprocess.PIPE,
#
# )
# result = obj.stdout.read() + obj.stderr.read()
# total_size = len(result)
#
# # 1. 自定义报头
# head_dic = {
# 'file_name': 'test1',
# 'md5': 6567657678678,
# '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)
#
# except ConnectionResetError:
# print('客户端链接中断了')
# break
# conn.close()
# phone.close()
#
#
#
# import json
# import struct
# dic = {'filename': 'test1',
# 'md5': 654654676576776,
# 'total_size': 1024*1024*1024*1024*1024*1024*1024}
#
# dic_json_bytes = json.dumps(dic).encode('utf-8')
# # print(dic_json_bytes)
# len_dic_json_bytes = len(dic_json_bytes)
# print(len_dic_json_bytes)
# print(struct.pack('i',len_dic_json_bytes))
# print(struct.pack('Q',1024*1024*1024*1024*1024*1024*1024)
3.3客户端
# import socket
# import struct
# import json
# phone = socket.socket()
#
# phone.connect(('127.0.0.1',8848))
# while 1:
# to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
# if not to_server_data:
# # 服务端如果接受到了空的内容,服务端就会一直阻塞中,所以无论哪一端发送内容时,都不能为空发送
# print('发送内容不能为空')
# continue
# phone.send(to_server_data)
# if to_server_data.upper() == b'Q':
# break
#
# # 1. 接收固定长度的4个字节
# 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)
# '''
# head_dic = {
# 'file_name': 'test1',
# 'md5': 6567657678678,
# 'total_size': total_size,
#
# }
# '''
# total_data = b''
# while len(total_data) < head_dic['total_size']:
# total_data += phone.recv(1024)
#
# # print(len(total_data))
# print(total_data.decode('gbk'))
#
# phone.close()
4.基于UDP协议的socket通信
4.1服务端
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])
# 1. 基于udp协议的socket无须建立管道,先开启服务端或者客户端都行.
# 2. 基于udp协议的socket接收一个消息,与发送一个消息都是无连接的.
# 3. 只要拿到我的ip地址和端口就都可以给我发消息,我按照顺序接收消息.
4.2客户端
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")}')
原文地址:https://www.cnblogs.com/liubing8/p/11366648.html
时间: 2024-10-16 17:39:44