黏包

TCP(transport control protocol,传输控制协议):面向连接的,面向流的,提供可靠的服务。为了高效的发送包,使用了Nagle 算法,将多次间隔较小且数据量小的数据,合并成一个大的数据块进行封包,因此面向流的通信是无消息保护边界的。

UDP(user datagram protocol,用户数据报协议):无连接,面型消息的,提供高效率的服务。UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构记录每一个到达的UDP包(包含消息来源地址,端口等信息),对于接收端来说可以进行区分。面向消息是有消息保护边界的

黏包: 接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

    TCP协议为了提高传输效率,发送方往往要手机到足够多的数据后才会发送一个TCP段,若连续几次的数据量都少,TCP会根据Nagle算法将数据合成一个TCP段后一次发送出去,接收方就收到了黏包数据

黏包产生原因: 1 、发送数据时间间隔短,数据量小,合在一起发送,产生粘包

        2 、接收方不及时接收缓冲区的包,造成多个包接收

拆包发生原因: 当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。

解决粘包的方法: (发送端在发送数据是,把自己将要发送的字节流总大小让接收端知晓,然后接收端用循环接收所有的数据)

# 基于TCP的CMD服务端
# 思路:想统计数据长度发给对方,待对方确认后,在发送数据

import socket
import subprocess
server = socket.socket()
socket.bind(("127.0.0.1",9999))
socket.listen()
while True:
    client,address = socket.accept()
    while True:
        try:
            cmd = client.recv(1024).decode('utf-8')
            if not data:
                client.close()
                break

            p1 = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr = subprocess.PIPE)
            data = p1.stdout.read()
            err_data = p1.stderr.read()
            # 先发送数据长度,然后发送数据
            length = str(len(data)+len(err_data))

            client.send(length.encode('utf-8'))
            data1 = server.recv(1024).decode('utf-8')  # 确认对方已经收到长度信息
            if data == 'recv_ready':
                client.send(err_data.encode('utf-8'))
                client.send(data.encode('utf-8'))
        except ConnectionResetError:
            print('客户端断开了连接!')

server.close()

  

# CMD客户端

import socket
client = socket.socket()
client.connect(('127.0.0.1',8888))
while True:
    msg = input("请输入指令: ").strip()
    # 判断指令是否正确,q 退出
    client.send(msg.encode('utf-8'))
    # 这个是数据的长度
    length = int(client.recv(1024).decode('utf-8'))
    # 收到数据后要给服务端回复'recv_ready'
    client.send('recv_ready'.encode('utf-8'))

    total_data = b""
    recv_size = 0
    # 每次接收一部分,分批次接收完
    while recv_size< length:
        data += client.recv(1024)
        recv_size+=len(data)
    print(data.decode('utf-8'))

 小结: 以上方法虽然解决了粘包,但是 程序的运行速度远快于网络传输速度,在发送一次字节前,要先把字节的长度发给对方,这种方式放大网络延迟甙类的性能损耗

 解决办法: 为字节流加上自定义的固定长度的报头(报头中包含字节流长度),一次send到对端,对端在接收时,先从缓存中取出订场的报头,然后在取出只是数据

 struct模块:把一个类型,如整数,转成固定长度的bytes

       struct.pack("i", 11111)   ------> 转成固定长度的二进制

# CMD 服务端

# 思路:发送真正的数据之前,先添加一个报头(包括数据的长度,时间,文件名)计算报头长度
# 先发送报头(4 个字节)对方接收后反解出报头长度,我方发送报头数据,对方接收,然后反解出真正数据的长度,然后接收数据

import socket
import subprocess
import datetime
import struct
import json
server = socket.socket()
server.bind(("127.0.0.1",8888))
server.listen()
while True:
    client,address = server.accept()
    while True:
        try:
            cmd = client.recv(1024).decode('utf-8')
            p1 = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            data = p1.stdout.read()
            err_data = p1.stderr.read()
            if data:
                res = data
            else:
                res = err_data
            length = len(res)
            # 发送数据之前发送额外的信息(时间,数据长度,文件名)
            t = {}
            t['time']=str(datetime.datetime.now())
            t['size'] = length
            t['filename']='a.txt'
            # 将字典转成json格式,算出字典长度,用struct变成固定长度的二进制
            t_json = json.dumps(t)   #得到json格式的字符串
            t_data = t_json.encode('utf-8')
            t_length = struct.pack("i",len(t_data))

            #第一步,发送额外信息的长度
            client.send(t_length)
            #第二步,发送额外信息内容
            client.send(t_data)
            #第三步,发送真正的数据
            client.send(res)
        except ConnectionResetError:
            print('连接中断。。。')
            client.close()
            break

server.close()

 

# CMD 的客户端

import socket
import struct
import json
client = socket.socket()
client.connect(("127.0.0.1",8888))
while True:
    msg = input('输入指令:').strip()
    #判断输入不能为空
    if not msg:
        print('命令不能为空!')
        continue
    client.send(msg.encode('utf-8'))
    #第一次: 收到对方发来的报头长度
    t_length1 = client.recv(4)
    # 反解出长度
    t_length = struct.unpack("i",t_length1)[0]  # struck 反解出的是元组形式
    # 第二次:接收报头内容
    head_data1 = client.recv(t_length).decode('utf-8')
    # 解析报头内容(时间,文件名,数据长度),得到数据长度
    head_data = json.loads(head_data1)
    print('执行时间:%s'%head_data['time'])
    data_length = head_data['size']
    # 第三次: 接收真正的数据
    all_data = b""
    recv_length = 0
    while recv_length < data_length:
        all_data+=client.recv(1024)
        recv_length = len(all_data)

    print('接收长度为:%s'%recv_length)
    print(all_data.decode('gbk')) # 注意解码为GBK

client.close()

总结: TCP发生粘包的三种情况

  1. 当单个数据包较小时接收方可能一次性读取多个包的数据

  2. 当整体数据较大时接收方可能一次仅能读取一个包的一部分内容

  3. 另外TCP协议为了提高效率,增加一种优化机制,会将数据较小且发送间隔较短的数据合并发送,该机制也会导致发送方将两个数据包粘在一起发送。

产生黏包的原因:

    接收方不知道发送方发送了多少数据

 struck模块: 将python中的类型,例如整型,转化成固定长度的二进制

a=struct.pack("i",89007)
print(a)   #结果: b'xaf[x01x00'

b = struct.unpack("i",a)  # 结果为元组形式

print(b)  # (89007,)

  

原文地址:https://www.cnblogs.com/Afrafre/p/10181317.html