什么是黏包?什么情况下会出现黏包的情况?该如何避免黏包的情况?

首先来看一个例子

  1. #服务端
  2. import time
  3. from socket import *
  4. server = socket(AF_INET,SOCK_STREAM)
  5. server.bind(("127.0.0.1",8180))
  6. server.listen(5)
  7. conn,addr = server.accept()
  8. resl = conn.recv(1024)
  9. print(resl.decode('utf-8'))
  10.  
  11. #客户端
  12. from socket import *
  13. import time
  14. client = socket(AF_INET,SOCK_STREAM)
  15. client.connect(("127.0.0.1",8180))
  16. client.send(b"")
  17. client.send(b'world')

结果:

  1. 1234world #为什么会连在一起了

这种简单的情况其实也好解决:

  1. #客户端
  2. from socket import *
  3. import time
  4. client = socket(AF_INET,SOCK_STREAM)
  5. client.connect(("127.0.0.1",8180))
  6. client.send(b"")
  7. client.send(b'world')
  8. time.sleep(3) #用时间隔开就能不让它黏在一起
  9. client.send(b'world')

执行结果:

  1. 12345
  2. world

还可以这样:

  1. import time
  2. from socket import *
  3. server = socket(AF_INET,SOCK_STREAM)
  4. server.bind(("127.0.0.1",8180))
  5. server.listen(5)
  6. conn,addr = server.accept()
  7. resl = conn.recv(5) #指定的字节数就不能黏在一起了
  8. print(resl.decode('utf-8'))
  9. res2 = conn.recv(5)
  10. print(res2.decode('utf-8'))

我们可以猜测的:当包的大小有限,同时连续发送的时候就可能出现黏包的情况。

什么是黏包?

只有TCP有黏包现象,UDP永远不会黏包。

之前提到过,TCP是面向连接的通信方式,提供了顺序、可靠、不会重复的数据传输,而且不会加上边界。

这个就意味着,每一个要发送的信息,可能会被拆分成多份,每一份都会不多不少地正确地到达目的地,然后被重新拼装起来,传给正在等待地应用程序。

问题就出现了,传输地数据没有界限,当传输到另一台地机器的时候,从内存中取值的时候,取值的大小取决于recv地参数。

如果需要传输地数据比较大,那么recv肯定无法一次取完,在内存中有残留,这样就会出现黏包地现象。

还有就是为了提高效率,tcp协议在传输地过程中,会将数据包较小的和时间间距比较小的数据包一起发送,这样也会造成黏包。

黏包是如何产生的?

黏包出现的时候,表现在有可能并没有取到自己想要的包,为什么没有取到自己的包了?

难道是内存中没有吗?不是,是因为系统按顺序给你取,而上一个又没有取完,所以就可能取到别人的包,怎么解决了?

改变recv参数并不能从根本上解决这个问题,recv受制于当前系统可供支配的内存,如果传输的数据比内存大,这样也会产生黏包。

刚才说了,你之所以会取别人的,是别人没有取完,只要能保证每个人都取到自己的包并且不残留就可以了。

那为什么就不能取完了?还是TCP的性质决定了,没有边界,不知道取到那里为止,因此只要让它知道了边界的位置,问题也就解决了。

怎样解决黏包问题?

在传输之前,我们先给对方添加一个包头,这样它就知道取到那里了;

  1. #服务方
  2. from socket import *
  3. import subprocess
  4. import struct
  5. server = socket(AF_INET,SOCK_STREAM)
  6. server.bind(('127.0.0.1',8888))
  7. server.listen(5)
  8.  
  9. while True:
  10. conn,addr = server.accept()
  11. print('收到来自%s的访问' % addr[0])
  12. while True:
  13. try:
  14. cmd = conn.recv(1024)
  15. cmd = cmd.decode("utf-8") #接受的是二进制的格式,因为下面需要执行这条消息,所以必须解码
  16. ret = subprocess.Popen(cmd,shell=True,
  17. stdout=subprocess.PIPE, #正确输出
  18. stderr=subprocess.PIPE, #错误输出
  19. )
  20. stdout = ret.stdout.read()
  21. stderr = ret.stderr.read()
  22.  
  23. #发送之前先创建一个报头
  24. total_size = len(stdout) + len(stderr)
  25. #报头必须是定长,不然发过去,不知道边界也没有意义
  26. header_size = struct.pack('i',total_size) #转化为可以直接传出的二进制,而且i就为4
  27. print(header_size)
  28. conn.send(header_size)
  29.  
  30. conn.send(stdout)
  31. conn.send(stderr)
  32.  
  33. except ConnectionResetError:
  34. break
  35. conn.close()
  36. server.close()

接收方:

  1. #客户端
  2. from socket import *
  3. import struct
  4. client = socket()
  5. client.connect(('127.0.0.1',8888))
  6. while True:
  7. cmd = input(">>>").strip()
  8. if len(cmd) == 0:continue
  9. client.send(cmd.encode("utf-8"))
  10. header_size = client.recv(4) #先接受报头,这样就能知道这个包的边界了
  11. total_size = struct.unpack('i',header_size)[0] #返回的是一个元祖
  12. fact_size = 0
  13. fact = b''
  14. while fact_size < total_size: #使用循环,只有这个包取完了,才能取下一个,这样就没有遗漏
  15. data = client.recv(1024)
  16. fact += data
  17. fact_size += len(data)
  18. print(fact.decode('gbk')) #由于实在window下面执行,所有返回的编码是GBK格式,也只能用GBK解开。
  19. client.close()

上面的代码加上一个包头,已经解决了黏包问题,但是如果我想在包头之中添加其它信息,而struct只能接收数字参数,又该如何了?

我们可以先把需要传输的信息解出来,再在信息中提取边界。

升级版:

  1. #服务端
  2. from socket import *
  3. import subprocess
  4. import struct,json
  5. server = socket(AF_INET,SOCK_STREAM)
  6. server.bind(('127.0.0.1',8888))
  7. server.listen(5)
  8. while True:
  9. conn,addr = server.accept()
  10. print('收到来自%s的访问' % addr[0])
  11. while True:
  12. try:
  13. cmd = conn.recv(1024) #因为一般命令都不会太长,使用1024足矣
  14. cmd = cmd.decode("utf-8")
  15. ret = subprocess.Popen(cmd,shell=True,
  16. stdout=subprocess.PIPE, #正确输出
  17. stderr=subprocess.PIPE, #错误输出
  18. )
  19. stdout = ret.stdout.read()
  20. stderr = ret.stderr.read()
  21.  
  22. #发送之前先创建一个报头
  23. total_size = len(stdout) + len(stderr)
  24. #报头必须是定长,不然发过去,不知道边界也没有意义
  25. # 先创建一个报头
  26. header_dict = {'total_size':total_size, #封装的包头
  27. "md5":78978900967890878,
  28. "filename":"a.txt"
  29. }
  30. header_str = json.dumps(header_dict) #字典不能传输,先使用json转化为字符串
  31. header_bytes = header_str.encode('utf-8') #转化成二进制格式,不然直接报错
  32. header_size = struct.pack('i',len(header_bytes)) #第一层数据(包含包头大小)
  33. conn.send(header_size) #发送大小给客户端
  34. conn.send(header_bytes) #包头信息也要一并发过去
  35. conn.send(stdout)
  36. conn.send(stderr)
  37. except ConnectionResetError:
  38. break
  39. conn.close()
  40. server.close()

为什么UDP不会出现黏包问题了?

UDP的数据报会保留边界,这就表示,数据是整个发送的,不会像面向连接的协议那样先被拆分成小块。自然就不会存在残留,所以不会出现黏包现象。

TCP黏包问题的更多相关文章

  1. netty]--最通用TCP黏包解决方案

    netty]--最通用TCP黏包解决方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender 2017年02月19日 15:02:11 惜暮 阅读数:1 ...

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

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

  3. socketserver tcp黏包

    socket (套接字) tcp(黏包现象原因) 传输中由于内核区缓冲机制(等待时间,文件大小),会在 发送端 缓冲区合并连续send的数据,也会出现在 接收端 缓冲区合并recv的数据给指定port ...

  4. python tcp黏包和struct模块解决方法,大文件传输方法及MD5校验

    一.TCP协议 粘包现象 和解决方案 黏包现象让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)执行远程命令的模块 需要用到模块subprocess sub ...

  5. 038.Python关于TCP黏包问题

    黏包现象 1 黏包现象演示 服务端 #服务端 import socket sk = socket.socket() # 注册主机到网络 sk.bind( ("127.0.0.1", ...

  6. tcp黏包问题与udp为什么不黏包

    1.先说下subprocess模块的用法,为了举个黏包的例子 # 通过一个例子 来认识网络编程中的一个重要的概念 # 所有的客户端执行server端下发的指令,执行完毕后,客户端将执行结果给返回给服务 ...

  7. Linux tcp黏包解决方案

    tcpip协议使用"流式"(套接字)进行数据的传输,就是说它保证数据的可达以及数据抵达的顺序,但并不保证数据是否在你接收的时候就到达,特别是为了提高效率,充分利用带宽,底层会使用缓 ...

  8. tcp黏包

    转载https://www.cnblogs.com/wade-luffy/p/6165671.html 无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. 回 ...

  9. struct 模块解决 TCP黏包问题

    首先来看一下产生黏包现象的一段代码: # server.py 服务端 import socket ​ sk = socket.socket() sk.bind(('127.0.0.1',9000)) ...

随机推荐

  1. 了解什么是WebLogic Server 多数据源(Multi-DataSource)

      1. 什么是多数据源 我们知道配置WebLogic Server集群时一定要配置一个单一接入点(例如:Apache或F5),这样客户端只要访问这个单一入口点就可以了.对于客户来说,就好象访问一台服 ...

  2. python GIL

    https://www.cnblogs.com/MnCu8261/p/6357633.html 全局解释器锁,同一时间只有一个线程获得GIL,

  3. xampp 安装 mysql-python

    在已经安装brew前提下:brew install mysql-connector-c pip install MySQL-python

  4. node/webpack/react

    node是运行引擎,通过他可以直接在后端运行js语法 webpack是打包工具 react是前端框架 通过 npm 使用 React 我们建议在 React 中使用 CommonJS 模块系统,比如 ...

  5. YOLO+yolo9000配置使用darknet

    Installing Darknet 1.直接设置使用,编译通过 git clone https://github.com/pjreddie/darknet.git cd darknet make 2 ...

  6. ntp时间服务同步

    第一种方式:同步到网络时间服务器 # ntpdate time.windows.com将硬件时间设置为当前系统时间. #hwclock –w 加入crontab: 30 8 * * * root /u ...

  7. 连接Zookeeper操作

    public class ZKConnector implements Watcher{ private static final Logger logger =LoggerFactory.getLo ...

  8. 对数据进行GZIP压缩和解压

    public class GzipUtils { /** * 对字符串进行gzip压缩 * @param data * @return * @throws IOException */ public ...

  9. Could not open lock file/var/lib/dpkg/lock

    apt-get时出现错误提示: E: Could not get lock /var/lib/dpkg/lock - open (11: Resource temporarily unavailabl ...

  10. linux中grep注意

    grep -l 只输出文件名: -h 只输出匹配的行 不输出文件名: -c 之处匹配内容的行数: -n 将结果输出的同时,也输出改行的行号: -c 统计查到的总行数: -i 忽略大小写: grep ' ...