Python网络编程04 /recv工作原理、展示收发问题、粘包现象

1. recv工作原理

  • 源码解释:

    1. Receive up to buffersize bytes from the socket.
    2. # 接收来自socket缓冲区的字节数据,
    3. For the optional flags argument, see the Unix manual.
    4. # 对于这些设置的参数,可以查看Unix手册。
    5. When no data is available, block untilat least one byte is available or until the remote end is closed.
    6. # 当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
    7. When the remote end is closed and all data is read, return the empty string.
    8. # 关闭远程端并读取所有数据后,返回空字符串。
  • 验证recv工作原理

    1.验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值

    1. # server服务端
    2. import socket
    3. server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    4. server.bind(('127.0.0.1',8080))
    5. server.listen(5)
    6. conn, client_addr = server.accept()
    7. from_client_data1 = conn.recv(2)
    8. print(from_client_data1)
    9. from_client_data2 = conn.recv(2)
    10. print(from_client_data2)
    11. from_client_data3 = conn.recv(1)
    12. print(from_client_data3)
    13. conn.close()
    14. server.close()
    15. # client客户端
    16. import socket
    17. client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    18. client.connect(('127.0.0.1',8080))
    19. client.send('hello'.encode('utf-8'))
    20. client.close()

    2.验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态

    1. # server服务端
    2. import socket
    3. server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    4. server.bind(('127.0.0.1',8080))
    5. server.listen(5)
    6. conn, client_addr = server.accept()
    7. from_client_data = conn.recv(1024)
    8. print(from_client_data)
    9. print(111)
    10. conn.recv(1024) # 此时程序阻塞20秒左右,因为缓冲区的数据取完了,并且20秒内,客户端没有关闭。
    11. print(222)
    12. conn.close()
    13. server.close()
    14. # client客户端
    15. import socket
    16. import time
    17. client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    18. client.connect(('127.0.0.1',8080))
    19. client.send('hello'.encode('utf-8'))
    20. time.sleep(20)
    21. client.close()

    3.验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串

    1. # server服务端
    2. import socket
    3. server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    4. server.bind(('127.0.0.1',8080))
    5. server.listen(5)
    6. conn, client_addr = server.accept()
    7. from_client_data1 = conn.recv(1024)
    8. print(from_client_data1)
    9. from_client_data2 = conn.recv(1024)
    10. print(from_client_data2)
    11. from_client_data3 = conn.recv(1024)
    12. print(from_client_data3)
    13. conn.close()
    14. server.close()
    15. # client客户端
    16. import socket
    17. import time
    18. client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    19. client.connect(('127.0.0.1',8080))
    20. client.send('hello'.encode('utf-8'))
    21. client.close()
    22. # recv空字符串: 对方客户端关闭了,且服务端的缓冲区没有数据了,我再recv取到空bytes.

2. 展示收发问题示例

  • 发多次收一次

    1. # server服务端
    2. import socket
    3. server = socket.socket()
    4. server.bind(('127.0.0.1',8848))
    5. server.listen(5)
    6. conn,addr = server.accept()
    7. from_client_data = conn.recv(1024)
    8. print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
    9. conn.close()
    10. server.close()
    11. # client客户端
    12. import socket
    13. phone = socket.socket()
    14. phone.connect(('127.0.0.1',8848))
    15. phone.send(b'he')
    16. phone.send(b'llo')
    17. phone.close()
  • 发一次收多次

    1. # server服务端
    2. import socket
    3. server = socket.socket()
    4. server.bind(('127.0.0.1',8848))
    5. server.listen(5)
    6. conn,addr = server.accept()
    7. from_client_data = conn.recv(3)
    8. print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
    9. from_client_data = conn.recv(3)
    10. print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
    11. from_client_data = conn.recv(3)
    12. print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
    13. from_client_data = conn.recv(3)
    14. print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
    15. conn.close()
    16. server.close()
    17. # client客户端
    18. import socket
    19. client = socket.socket()
    20. client.connect(('127.0.0.1',8848))
    21. client.send(b'hello world')
    22. client.close()

3. 粘包现象

  • 粘包现象概述:

    1. 发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾
  • 粘包第一种:

    send的数据过大,大于对方recv的上限时,对方第二次recv时,会接收上一次没有recv完的剩余的数据。

    1. # server服务端
    2. import socket
    3. import subprocess
    4. server = socket.socket()
    5. server.bind(('127.0.0.1',8848))
    6. server.listen(2)
    7. while 1:
    8. server,addr = server.accept() # 等待客户端链接我,阻塞状态中
    9. while 1:
    10. try:
    11. from_client_data = conn.recv(1024)
    12. if from_client_data.upper() == b'Q':
    13. print('客户端正常退出聊天了')
    14. break
    15. obj = subprocess.Popen(from_client_data.decode('utf-8'),
    16. shell=True,
    17. stdout=subprocess.PIPE,
    18. stderr=subprocess.PIPE,
    19. )
    20. result = obj.stdout.read() + obj.stderr.read()
    21. print(f'总字节数:{len(result)}')
    22. conn.send(result)
    23. except ConnectionResetError:
    24. print('客户端链接中断了')
    25. break
    26. conn.close()
    27. server.close()
    28. # client客户端
    29. import socket
    30. client = socket.socket()
    31. client.connect(('127.0.0.1',8848))
    32. while 1:
    33. to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
    34. if not to_server_data:
    35. print('发送内容不能为空')
    36. continue
    37. client.send(to_server_data)
    38. if to_server_data.upper() == b'Q':
    39. break
    40. from_server_data = client.recv(300) # 最多接受1024字节
    41. # 这里可能出现一个字符没有接受完整,进而在解码的时候会报错
    42. # print(f'{from_server_data.decode("gbk")}')
    43. print(len(from_server_data))
    44. client.close()
  • 粘包第二种:

    连续短暂的send多次(数据量很小),的数据会统一发送出去.

    1. # server服务端
    2. import socket
    3. server = socket.socket()
    4. server.bind(('127.0.0.1',8848))
    5. server.listen(5)
    6. conn,addr = server.accept()
    7. from_client_data = conn.recv(1024)
    8. print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
    9. conn.close()
    10. server.close()
    11. # client客户端
    12. import socket
    13. client = socket.socket()
    14. client.connect(('127.0.0.1',8848))
    15. client.send(b'he')
    16. client.send(b'll')
    17. client.send(b'o')
    18. client.close()
    19. # Nagle算法:就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
    20. # Nagle算法的规则:
    21. # 1.如果包长度达到MSS,则允许发送;
    22. # 2.如果该包含有FIN,则允许发送;
    23. # 3.设置了TCP_NODELAY选项,则允许发送;
    24. # 4.未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
    25. # 5.上述条件都未满足,但发生了超时(一般为200ms),则立即发送。

3. 解决粘包现象

  • 解决粘包现象的思路:

    1. 服务端发一次数据 5000字节,
    2. 客户端接收数据时,循环接收,每次(至多)接收1024个字节,直至将所有的字节全部接收完毕.将接收的数据拼接在一起,最后解码.
    3. 1. 遇到的问题: recv的次数无法确定.
    4. 发送总具体数据之前,先发一个总数据的长度:5000个字节。然后在发送总数据。
    5. 客户端: 先接收一个总数据的长度,再根据总数据的长度接收相应长度的字节。
    6. 然后再循环recv 控制循环的条件就是只要接收的数据< 5000 一直接收。
    7. 2. 遇到的问题: 如何将总数据的长度转化成固定的字节数
    8. 3.将不固定长度的int类型转化成固定长度的bytes并且还可以翻转回来:struct模块

  • instruct模块的使用:

    1. import struct
    2. # 将一个数字转化成等长度的bytes类型。
    3. ret = struct.pack('i', 183346)
    4. print(ret, type(ret), len(ret))
    5. # 结果:b'2\xcc\x02\x00' <class 'bytes'> 4
    6. # 通过unpack反解回来
    7. ret1 = struct.unpack('i',ret)[0]
    8. print(ret1, type(ret1))
    9. # 结果:183346 <class 'int'>
    10. # 但是通过struct 处理不能处理太大
    11. ret = struct.pack('l', 4323241232132324)
    12. print(ret, type(ret), len(ret)) # 报错
    13. # struct.error: argument out of range

4. low版解决粘包现象

  • server服务端

    1. import socket
    2. import subprocess
    3. import struct
    4. server = socket.socket()
    5. server.bind(('127.0.0.1',8848))
    6. server.listen(2)
    7. while 1:
    8. conn,addr = server.accept()
    9. while 1:
    10. try:
    11. from_client_data = conn.recv(1024) # 接收命令
    12. if from_client_data.upper() == b'Q':
    13. print('客户端正常退出聊天了')
    14. break
    15. obj = subprocess.Popen(from_client_data.decode('utf-8'),
    16. shell=True,
    17. stdout=subprocess.PIPE,
    18. stderr=subprocess.PIPE,
    19. )
    20. result = obj.stdout.read() + obj.stderr.read()
    21. total_size = len(result)
    22. print(f'总字节数:{total_size}')
    23. # 1. 制作固定长度的报头
    24. head_bytes = struct.pack('i',total_size)
    25. # 2. 发送固定长度的报头
    26. conn.send(head_bytes)
    27. # 3. 发送总数据
    28. conn.send(result)
    29. except ConnectionResetError:
    30. print('客户端链接中断了')
    31. break
    32. conn.close()
    33. server.close()
  • client客户端

    1. import socket
    2. import struct
    3. phone = socket.socket()
    4. phone.connect(('127.0.0.1',8848))
    5. while 1:
    6. to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
    7. if not to_server_data:
    8. print('发送内容不能为空')
    9. continue
    10. phone.send(to_server_data)
    11. if to_server_data.upper() == b'Q':
    12. break
    13. # 1. 接收报头
    14. head_bytes = phone.recv(4)
    15. # 2. 反解报头
    16. total_size = struct.unpack('i',head_bytes)[0]
    17. total_data = b''
    18. while len(total_data) < total_size:
    19. total_data += phone.recv(1024)
    20. print(len(total_data))
    21. print(total_data.decode('gbk'))
    22. phone.close()

5. 高级版解决粘包方式(自定制报头)

  • 解决思路

    1. 制作固定的报头,现在有两段不固定长度的bytes类型,需要固定的报头,所以
    2. 1. 获取不固定报头的长度,总数据的长度放在报头中(字典)
    3. 2. 利用struct模块将不固定的长度转化成固定的字节数 4个字节
    4. 3. 先发4个字节,再发报头数据,再发总数据
  • server服务端

    1. import socket
    2. import subprocess
    3. import struct
    4. import json
    5. server = socket.socket()
    6. server.bind(('127.0.0.1',8848))
    7. server.listen(2)
    8. while 1:
    9. conn,addr = server.accept()
    10. while 1:
    11. try:
    12. from_client_data = conn.recv(1024) # 接收命令
    13. if from_client_data.upper() == b'Q':
    14. print('客户端正常退出聊天了')
    15. break
    16. obj = subprocess.Popen(from_client_data.decode('utf-8'),
    17. shell=True,
    18. stdout=subprocess.PIPE,
    19. stderr=subprocess.PIPE,
    20. )
    21. result = obj.stdout.read() + obj.stderr.read()
    22. total_size = len(result)
    23. # 1. 自定义报头
    24. head_dic = {
    25. 'file_name': 'test1',
    26. 'md5': 6567657678678,
    27. 'total_size': total_size,
    28. }
    29. # 2. json形式的报头
    30. head_dic_json = json.dumps(head_dic)
    31. # 3. bytes形式报头
    32. head_dic_json_bytes = head_dic_json.encode('utf-8')
    33. # 4. 获取bytes形式的报头的总字节数
    34. len_head_dic_json_bytes = len(head_dic_json_bytes)
    35. # 5. 将不固定的int总字节数变成固定长度的4个字节
    36. four_head_bytes = struct.pack('i',len_head_dic_json_bytes)
    37. # 6. 发送固定的4个字节
    38. conn.send(four_head_bytes)
    39. # 7. 发送报头数据
    40. conn.send(head_dic_json_bytes)
    41. # 8. 发送总数据
    42. conn.send(result)
    43. except ConnectionResetError:
    44. print('客户端链接中断了')
    45. break
    46. conn.close()
    47. server.close()
  • client客户端

    1. import socket
    2. import struct
    3. import json
    4. client = socket.socket()
    5. client.connect(('127.0.0.1',8848))
    6. while 1:
    7. to_server_data = input('>>>输入q或者Q退出').strip().encode('utf-8')
    8. if not to_server_data:
    9. print('发送内容不能为空')
    10. continue
    11. client.send(to_server_data)
    12. if to_server_data.upper() == b'Q':
    13. break
    14. # 1. 接收固定长度的4个字节
    15. head_bytes = client.recv(4)
    16. # 2. 获得bytes类型字典的总字节数
    17. len_head_dic_json_bytes = struct.unpack('i',head_bytes)[0]
    18. # 3. 接收bytes形式的dic数据
    19. head_dic_json_bytes = client.recv(len_head_dic_json_bytes)
    20. # 4. 转化成json类型dic
    21. head_dic_json = head_dic_json_bytes.decode('utf-8')
    22. # 5. 转化成字典形式的报头
    23. head_dic = json.loads(head_dic_json)
    24. '''
    25. head_dic = {
    26. 'file_name': 'test1',
    27. 'md5': 6567657678678,
    28. 'total_size': total_size,
    29. }
    30. '''
    31. total_data = b''
    32. while len(total_data) < head_dic['total_size']:
    33. total_data += client.recv(1024)
    34. # print(len(total_data))
    35. print(total_data.decode('gbk'))
    36. client.close()
  • 总结:

    1. 1. 高大上版: 自定制报头
    2. dic = {'filename': XX, 'md5': 654654676576776, 'total_size': 26743}
    3. 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. 迷宫城堡+算法讲解【tarjian算法】

    Tarjan 算法 参考博客:https://www.cnblogs.com/shadowland/p/5872257.html 算法讲解 Tarjan 算法一种由Robert Tarjan提出的求解 ...

  2. 单页面应用下刷新当前iframe

    $('button.layui-btn-elastic-2').click(function(){ var srcIframe=$(".layui-side ul li dd"). ...

  3. mysql explain的extra

    导读 extra主要有是那种情况:Using index.Using filesort.Using temporary.Using where Using where无需多说,就是使用了where筛选 ...

  4. rust 代码生成选项

    Available codegen options: -C ar=val -- this option is deprecated and does nothing -C linker=val -- ...

  5. Linux 半连接队列,全连接队列

    socket 中 listen api中参数backlog指定的是 全队列大小 accept api是从全队列中获取, 没有就阻塞了, 直到有新连接进来. listen中指定的值大小,有一个最大上限, ...

  6. cb30a_c++_STL_算法_查找算法_(3)search_find_end

    cb30a_c++_STL_算法_查找算法_(3)search_find_endsearch()pos = search(ideq.begin(), ideq.end(), ilist.begin() ...

  7. 06.DRF-第一个demo

    一.环境安装与配置 DRF需要以下依赖: Python (2.7, 3.2, 3.3, 3.4, 3.5, 3.6) Django (1.10, 1.11, 2.0) DRF是以Django扩展应用的 ...

  8. 07.DRF-序列化

    Serializer序列化器 序列化器的作用: 进行数据的校验 对数据对象进行转换 一.定义Serializer 1.1 定义方法 Django REST framework中的Serializer使 ...

  9. WeChair项目Alpha冲刺(6/10)

    团队项目进行情况 1.昨日进展    Alpha冲刺第六天 昨日进展: 前端:和后端成功交互,页面修改和完善 后端:和前端成功交互,但是数据解密失败,初步编写登录的service层和dao层代码未测试 ...

  10. 性能测试之JVM的故障分析工具VisualVM

    VisualVM 是随JDK一同发布的jvm诊断工具,通过插件可以扩展很多功能,插件扩展也是其精华所在. 提供了一个可视界面,用于在Java应用程序在Java虚拟机上运行时查看有关Java应用程序的详 ...