参考博客:http://www.cnblogs.com/kex1n/p/6502002.html

一、粘包现象

  在上一篇的socket传输大数据文章中,我们可以顺利的接发数据,似乎做的不错,可以接收了。但是其实还隐藏着一个问题,请看下面的情况:

  socket--粘包

  我们看到出错的位置,具体分析一下是什么情况

  socket--粘包

  这个功能在上篇中介绍是用来判断数据量大小的,即服务端先将其要发送的数据量大小发给客户端,然后客户端再接收数据时,以此来判断是否接收了全部数据。

  我们在来看看服务端的代码: 

# 服务端先将数据大小发送给客户端,用于对比
conn.send(str(len('{}'.format(cmd_res).encode('utf-8'))).encode('utf-8'))
# 发送数据
conn.send('{}'.format(cmd_res).encode('utf-8'))

  第一句代码的作用是发送数据量大小,第二句则是发送数据本身。由错误我们可以判断出,第一次发送的数据当中,不仅仅是数据量大小,即数据长度,还包含了第二次发送的部分数据。所以str格式的字符当然没办法 int了,就报错了。但是为什么发送的数据会混淆呢,why?

二、Socket粘包

  同志们,这就是粘包了,也就是前后发送的数据在接收端那边是合在一起的。粘包现象不一定每次都出现,我试了二三十次才终于出现了。那么粘包现象是怎么来的呢?我目前知道的还少,只能有一点说一点了,先做个笔记,再慢慢了解了。

  从现象上来看,粘包就是后一个数据紧跟着前一个数据的末尾被接收了,这样的影响就是如果两个包的数据格式不一样,分别要用来处理不同问题,比如上例中一个是int 一个是 str,就会出错。即便是数据格式一致,但前后数据连在一起,可能接收方就不知道其本意了。

  2.1 考虑出现粘包

  1)如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题。关闭连接主要是要双方都发送close连接,双方在数据发送和接收完毕后不再继续发送和接收新数据,而是双方关闭连接,这样就不用考虑粘包问题。

  2)如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包

       3)UDP发送数据不用考虑

  4)如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构,需要考虑粘包

  5)双方建立连接后,一段时间内不断发送数据,需要考虑粘包

  2.2 粘包原因

  1. 发送端需要等缓冲区满才发送出去,造成粘包
  2. 接收方不及时接收缓冲区的包,造成多个包接收 

  具体点:

  (1)发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。

  (2)接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。

  粘包情况有两种,一种是粘在一起的包都是完整的数据包,另一种情况是粘在一起的包有不完整的包。

三、如何解决粘包

  具体情况具体分析吧。我们还是以上例来处理

  3.1 利用超时

  socket将数据存入缓冲区后,TCP/IP协议会将数据发送给接收方,我们在两次连续发送数据时加入一个间隔时间,比如说: 

conn.send(str(len('{}'.format(cmd_res).encode('utf-8'))).encode('utf-8'))

time.sleep(0.5)   # 两次发送数据间隔一段时间
# 发送数据
 conn.send('{}'.format(cmd_res).encode('utf-8', 'ignore'))

  这样确实能解决问题,但是显然有缺陷,你让老司机发车的时候,总是停停顿顿的,他还不砸了硬盘?哦不,是方向盘。

   3.2 接收端收到数据后回复一个确认

  利用socket中的recv()函数是阻塞的,在接收端不返回确认时,就不会继续发送数据。同样在两次发送数据之间添加这个功能,代码如下:

  服务端:

# -*- coding: UTF-8 -*-
import os
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # TCP/IP协议, tcp ,如果不填写就是默认这个

server.bind(('localhost', 9999))

server.listen()

while True:  # 可以接受多个客户端

    conn, addr = server.accept()

    while True:

        data = conn.recv(1024)
        if not data:   # 防止当接受的客户端数据为空时,程序卡掉
            print('client has lost...')
            break
        print('执行命令:', data.decode())
        cmd_res = os.popen(data.decode()).read()

        if len(cmd_res) == 0:
            print('command not found')
        else:
            # 服务端先将数据大小发送给客户端,用于对比
            conn.send(str(len('{}'.format(cmd_res).encode('utf-8'))).encode('utf-8'))
            # 这里等待接收到返回
            clientACK = conn.recv(1024)
            # 发送数据
            conn.send('{}'.format(cmd_res).encode('utf-8', 'ignore'))
            print('发送完成')
View Code

相关文章:

  • 2021-12-22
  • 2022-02-25
  • 2022-02-11
  • 2022-12-23
  • 2022-12-23
  • 2022-01-01
  • 2021-04-21
猜你喜欢
  • 2021-10-09
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-06-21
  • 2021-07-30
相关资源
相似解决方案