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工作原理、展示收发问题、粘包现象的更多相关文章

  1. Python网络编程基础 ❷ 基于upd的socket服务 TCP黏包现象

    TCP的长连接 基于upd的socket服务 TCP黏包现象

  2. python网络编程调用recv函数完整接收数据的三种方法

    最近在使用python进行网络编程开发一个通用的tcpclient测试小工具.在使用socket进行网络编程中,如何判定对端发送一条报文是否接收完成,是进行socket网络开发必须要考虑的一个问题.这 ...

  3. python 之网络编程(基于TCP协议Socket通信的粘包问题及解决)

    8.4 粘包问题 粘包问题发生的原因: 1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包),这样接收端,就难于分辨出来了,必须提供科学的拆包机制. ...

  4. Linux 网络编程详解五(TCP/IP协议粘包解决方案二)

    ssize_t recv(int s, void *buf, size_t len, int flags); --与read相比,只能用于网络套接字文件描述符 --当flags参数的值设置为MSG_P ...

  5. 网络编程之模拟ssh远程执行命令、粘包问题 、解决粘包问题

    目录 模拟ssh远程执行命令 服务端 客户端 粘包问题 什么是粘包 TCP发送数据的四种情况 粘包的两种情况 解决粘包问题 struct模块 解决粘包问题 服务端 客户端 模拟ssh远程执行命令 服务 ...

  6. Linux 网络编程详解四(流协议与粘包)

    TCP/IP协议是一种流协议,流协议是字节流,只有开始和结束,包与包之间没有边界,所以容易产生粘包,但是不会丢包. UDP/IP协议是数据报,有边界,不存在粘包,但是可能丢包. 产生粘包问题的原因 . ...

  7. 网络编程基础【day09】:socket解决粘包问题之MD5(八)

    本节内容 1.概述 2.代码实现 一.概述 上一篇博客讲到的用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的一个笨方法.下面我们用另外一种方法:就是客户端已经 ...

  8. python网络编程——使用UDP、TCP协议收发信息

    UDP UDP是面向无连接的通讯协议,UDP数据包括目的端口号和源端口号信息,由于通讯不需要连接,所以可以实现广播发送. UDP传输数据时有大小限制,每个被传输的数据报必须限定在64KB之内. UDP ...

  9. 网络编程基础【day09】:解决socket粘包之大数据(七)

    本节内容 概述 linux下运行效果 sleep解决粘包 服务端插入交互解决粘包问题 一.概述 刚刚我们在window的操作系统上,很完美的解决了,大数据量的数据传输出现的问题,但是在Linux环境下 ...

随机推荐

  1. Divisors (求解组合数因子个数)【唯一分解定理】

    Divisors 题目链接(点击) Your task in this problem is to determine the number of divisors of Cnk. Just for ...

  2. 『图论』LCA 最近公共祖先

    概述篇 LCA (Least Common Ancestors) ,即最近公共祖先,是指这样的一个问题:在一棵有根树中,找出某两个节点 u 和 v 最近的公共祖先. LCA 可分为在线算法与离线算法 ...

  3. APP自动化1——Appium+pycharm自动化环境搭建全流程

    1. 安装python3,pycharm,可参考之前写的文档:https://www.cnblogs.com/chenweitoag/p/13154815.html 2. 准备以下必要工具: 基于wi ...

  4. C#实现模拟鼠标点击事件(点击桌面的其他程序 )

    注释感觉已经很清楚了,有不懂的欢迎评论 1 using System; using System.Collections.Generic; using System.ComponentModel; u ...

  5. 多线程集成设计模式--future模式

    多线程开发可以更好的发挥多核cpu性能,常用的多线程设计模式有:Future.Master-Worker.Guard Susperionsion 一.什么是Future模型: 该模型是将异步请求和代理 ...

  6. 2020/6/11 JavaScript高级程序设计 DOM

    DOM(文档对象模型)是针对HTML和XML文档的一个API(应用程序接口).他描绘了一个层次化的节点树,允许开发人员添加.移除和修改页面的某一部分. 10.1 节点层次 DOM将任何HTML和XML ...

  7. Maven搭建Spring MVC

    Maven搭建SpringMVC 点击Enable Auto Import 下面配置文件 配置两个文件 web.app-->Web-INF--web.xml <!DOCTYPE web-a ...

  8. python实现从文件夹随机拷贝出指定数量文件到目标文件夹

    为了方便倒腾数据,功能如题,该脚本和操作目录在同一根目录 实际运行时要手动修改程序中:cpfile_rand('img', 'outfile', 10) # 操作目录,输出目录,输出数量 import ...

  9. 如何使用ABP进行软件开发之基础概览

    ABP框架简述 1)简介 在.NET众多的技术框架中,ABP框架(本系列中指aspnetboilerplate项目)以其独特的魅力吸引了一群优秀开发者广泛的使用. 在该框架的赋能之下,开发者可根据需求 ...

  10. sharepoint 2010项目中,ashx页面获取SPContext.Current 为null的原因和解决方法

    //错误的写法 public void ProcessRequest(HttpContext context) { SPSecurity.RunWithElevatedPrivileges(deleg ...