Python网络编程04 /recv工作原理、展示收发问题、粘包现象
Python网络编程04 /recv工作原理、展示收发问题、粘包现象
1. 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工作原理
1.验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值
# server服务端
import socket server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5) conn, client_addr = server.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()
server.close()
# client客户端
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
client.send('hello'.encode('utf-8'))
client.close()
2.验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态
# server服务端
import socket server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5) conn, client_addr = server.accept()
from_client_data = conn.recv(1024)
print(from_client_data)
print(111)
conn.recv(1024) # 此时程序阻塞20秒左右,因为缓冲区的数据取完了,并且20秒内,客户端没有关闭。
print(222) conn.close()
server.close() # client客户端
import socket
import time
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
client.send('hello'.encode('utf-8'))
time.sleep(20) client.close()
3.验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串
# server服务端
import socket server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('127.0.0.1',8080))
server.listen(5) conn, client_addr = server.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()
server.close() # client客户端
import socket
import time
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
client.send('hello'.encode('utf-8'))
client.close() # recv空字符串: 对方客户端关闭了,且服务端的缓冲区没有数据了,我再recv取到空bytes.
2. 展示收发问题示例
发多次收一次
# server服务端
import socket server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(5) conn,addr = server.accept() from_client_data = conn.recv(1024)
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') conn.close()
server.close() # client客户端
import socket phone = socket.socket()
phone.connect(('127.0.0.1',8848)) phone.send(b'he')
phone.send(b'llo') phone.close()
发一次收多次
# server服务端
import socket server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(5) conn,addr = server.accept() from_client_data = conn.recv(3)
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') from_client_data = conn.recv(3)
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') from_client_data = conn.recv(3)
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') from_client_data = conn.recv(3)
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}') conn.close()
server.close() # client客户端
import socket client = socket.socket()
client.connect(('127.0.0.1',8848)) client.send(b'hello world') client.close()
3. 粘包现象
粘包现象概述:
发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾
粘包第一种:
send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。
# server服务端
import socket
import subprocess server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(2) while 1:
server,addr = server.accept() # 等待客户端链接我,阻塞状态中 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()
print(f'总字节数:{len(result)}')
conn.send(result)
except ConnectionResetError:
print('客户端链接中断了')
break
conn.close()
server.close() # client客户端
import socket client = socket.socket()
client.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
client.send(to_server_data) if to_server_data.upper() == b'Q':
break from_server_data = client.recv(300) # 最多接受1024字节
# 这里可能出现一个字符没有接受完整,进而在解码的时候会报错
# print(f'{from_server_data.decode("gbk")}')
print(len(from_server_data)) client.close()
粘包第二种:
连续短暂的send多次(数据量很小),的数据会统一发送出去.
# server服务端
import socket server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(5) conn,addr = server.accept() from_client_data = conn.recv(1024)
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
conn.close()
server.close() # client客户端
import socket client = socket.socket()
client.connect(('127.0.0.1',8848)) client.send(b'he')
client.send(b'll')
client.send(b'o') client.close() # Nagle算法:就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
# Nagle算法的规则:
# 1.如果包长度达到MSS,则允许发送;
# 2.如果该包含有FIN,则允许发送;
# 3.设置了TCP_NODELAY选项,则允许发送;
# 4.未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
# 5.上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
3. 解决粘包现象
解决粘包现象的思路:
服务端发一次数据 5000字节,
客户端接收数据时,循环接收,每次(至多)接收1024个字节,直至将所有的字节全部接收完毕.将接收的数据拼接在一起,最后解码. 1. 遇到的问题: recv的次数无法确定.
发送总具体数据之前,先发一个总数据的长度:5000个字节。然后在发送总数据。
客户端: 先接收一个总数据的长度,再根据总数据的长度接收相应长度的字节。
然后再循环recv 控制循环的条件就是只要接收的数据< 5000 一直接收。 2. 遇到的问题: 如何将总数据的长度转化成固定的字节数 3.将不固定长度的int类型转化成固定长度的bytes并且还可以翻转回来:struct模块
instruct模块的使用:
import struct # 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 183346)
print(ret, type(ret), len(ret))
# 结果:b'2\xcc\x02\x00' <class 'bytes'> 4 # 通过unpack反解回来
ret1 = struct.unpack('i',ret)[0]
print(ret1, type(ret1))
# 结果:183346 <class 'int'> # 但是通过struct 处理不能处理太大
ret = struct.pack('l', 4323241232132324)
print(ret, type(ret), len(ret)) # 报错
# struct.error: argument out of range
4. low版解决粘包现象
server服务端
import socket
import subprocess
import struct server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(2) while 1:
conn,addr = server.accept()
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) print(f'总字节数:{total_size}') # 1. 制作固定长度的报头
head_bytes = struct.pack('i',total_size) # 2. 发送固定长度的报头
conn.send(head_bytes) # 3. 发送总数据
conn.send(result)
except ConnectionResetError:
print('客户端链接中断了')
break
conn.close()
server.close()
client客户端
import socket
import struct 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. 接收报头
head_bytes = phone.recv(4) # 2. 反解报头
total_size = struct.unpack('i',head_bytes)[0] total_data = b''
while len(total_data) < total_size:
total_data += phone.recv(1024) print(len(total_data))
print(total_data.decode('gbk')) phone.close()
5. 高级版解决粘包方式(自定制报头)
解决思路
制作固定的报头,现在有两段不固定长度的bytes类型,需要固定的报头,所以
1. 获取不固定报头的长度,总数据的长度放在报头中(字典)
2. 利用struct模块将不固定的长度转化成固定的字节数 4个字节
3. 先发4个字节,再发报头数据,再发总数据
server服务端
import socket
import subprocess
import struct
import json server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen(2) while 1:
conn,addr = server.accept() 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()
server.close()
client客户端
import socket
import struct
import json client = socket.socket()
client.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
client.send(to_server_data) if to_server_data.upper() == b'Q':
break # 1. 接收固定长度的4个字节
head_bytes = client.recv(4) # 2. 获得bytes类型字典的总字节数
len_head_dic_json_bytes = struct.unpack('i',head_bytes)[0] # 3. 接收bytes形式的dic数据
head_dic_json_bytes = client.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 += client.recv(1024) # print(len(total_data))
print(total_data.decode('gbk')) client.close()
总结:
1. 高大上版: 自定制报头
dic = {'filename': XX, 'md5': 654654676576776, 'total_size': 26743}
2. 高大上版:可以解决文件过大的问题.
Python网络编程04 /recv工作原理、展示收发问题、粘包现象的更多相关文章
- Python网络编程基础 ❷ 基于upd的socket服务 TCP黏包现象
TCP的长连接 基于upd的socket服务 TCP黏包现象
- python网络编程调用recv函数完整接收数据的三种方法
最近在使用python进行网络编程开发一个通用的tcpclient测试小工具.在使用socket进行网络编程中,如何判定对端发送一条报文是否接收完成,是进行socket网络开发必须要考虑的一个问题.这 ...
- python 之网络编程(基于TCP协议Socket通信的粘包问题及解决)
8.4 粘包问题 粘包问题发生的原因: 1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包),这样接收端,就难于分辨出来了,必须提供科学的拆包机制. ...
- Linux 网络编程详解五(TCP/IP协议粘包解决方案二)
ssize_t recv(int s, void *buf, size_t len, int flags); --与read相比,只能用于网络套接字文件描述符 --当flags参数的值设置为MSG_P ...
- 网络编程之模拟ssh远程执行命令、粘包问题 、解决粘包问题
目录 模拟ssh远程执行命令 服务端 客户端 粘包问题 什么是粘包 TCP发送数据的四种情况 粘包的两种情况 解决粘包问题 struct模块 解决粘包问题 服务端 客户端 模拟ssh远程执行命令 服务 ...
- Linux 网络编程详解四(流协议与粘包)
TCP/IP协议是一种流协议,流协议是字节流,只有开始和结束,包与包之间没有边界,所以容易产生粘包,但是不会丢包. UDP/IP协议是数据报,有边界,不存在粘包,但是可能丢包. 产生粘包问题的原因 . ...
- 网络编程基础【day09】:socket解决粘包问题之MD5(八)
本节内容 1.概述 2.代码实现 一.概述 上一篇博客讲到的用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的一个笨方法.下面我们用另外一种方法:就是客户端已经 ...
- python网络编程——使用UDP、TCP协议收发信息
UDP UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送. UDP传输数据时有大小限制,每个被传输的数据报必须限定在64KB之内. UDP ...
- 网络编程基础【day09】:解决socket粘包之大数据(七)
本节内容 概述 linux下运行效果 sleep解决粘包 服务端插入交互解决粘包问题 一.概述 刚刚我们在window的操作系统上,很完美的解决了,大数据量的数据传输出现的问题,但是在Linux环境下 ...
随机推荐
- Ultra-QuickSort (求逆序数+离散化处理)、Cows、Stars【树状数组】
一.Ultra-QuickSort(树状数组求逆序数) 题目链接(点击) Ultra-QuickSort Time Limit: 7000MS Memory Limit: 65536K Total ...
- 我深爱的Java,对不起,我出轨了!!!呸!渣男!
我对Java情有独钟 大学三年来,我主学的编程语言一直是Java,为了学好它,我付出了很多心血.现在回想,确实是Java改变了我,造就了我. 因为Java,我自愿在学校组织学弟学妹,给他们讲解Java ...
- python_lesson1 数学与随机数 (math包,random包)
math包 math包主要处理数学相关的运算.math包定义了两个常数: math.e # 自然常数e math.pi # 圆周率pi 此外,math包还有各种运算函数 (下面函数的功能可以 ...
- 记录一次Flink作业异常的排查过程
最近2周开始接手apache flink全链路监控数据的作业,包括指标统计,业务规则匹配等逻辑,计算结果实时写入elasticsearch. 昨天遇到生产环境有作业无法正常重启的问题,我负责对这个问题 ...
- cb46a_c++_STL_算法_逆转和旋转reverse_rotate函数advance
cb46a_c++_STL_算法_逆转和旋转reverse_rotateSTL算法--变序性算法reverse() 逆转reverse_copy()一边复制一般逆转rotate()旋转,某个位置开始前 ...
- 虚拟机 VMware 设置VMWARE通过桥接方式使用主机无线网卡上网
环境:WIN7旗舰版,台式机,U盘无线上网卡. 虚拟软件:VMware9.0,虚拟系统:CentOS6.4 需要实现虚拟机以独立机形式工作和上网. 先介绍一下VMware网络设置的三种方式 1 Hos ...
- Hystrix总结
Hystrix 能使你的系统在出现依赖服务失效的时候,通过隔离系统所依赖的服务,防止服务级联失败,同时提供失败回退机制,更优雅地应对失效,并使你的系统能更快地从异常中恢复. Hystrix能做什么? ...
- netty解决TCP的拆包和粘包的解决办法
TCP粘包.拆包问题 熟悉tcp编程的可能知道,无论是服务端还是客户端,当我们读取或者发送数据的时候,都需要考虑TCP底层的粘包个拆包机制. tcp是一个“流”协议,所谓流就是没有界限的传输数据,在业 ...
- 多线程集成设计模式--future模式
多线程开发可以更好的发挥多核cpu性能,常用的多线程设计模式有:Future.Master-Worker.Guard Susperionsion 一.什么是Future模型: 该模型是将异步请求和代理 ...
- 线性dp 打鼹鼠
鼹鼠是一种很喜欢挖洞的动物,但每过一定的时间,它还是喜欢把头探出到地面上来透透气的.根据这个特点阿Q编写了一个打鼹鼠的游戏:在一个n*n 的网格中,在某些时刻鼹鼠会在某一个网格探出头来透透气.你可以控 ...