• 黏包


    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,)

      

  • 相关阅读:
    删除所有空白列 cat yum.log | awk '{$1=$2=$3=$4=null;print $0}'>>yum.log1 sed ‘s/[ ]*$//g' 删除所有空格 sed -i s/[[:space:]]//g yum.log
    make clean 清除之前编译的可执行文件及配置文件。 make distclean 清除所有生成的文件。
    ipmitool -I lanplus -H 10.1.81.90 -U admin -P admin mc reset cold
    netperf对比
    iozone
    CentOS 7 vs. CentOS 8 版本差异大比拼
    seajs模块化jQuery与jQuery插件【转】
    教你怎么写jQuery的插件
    Jquery特效之=》仿京东多条件筛选特效
    sql FOR XML PATH('')
  • 原文地址:https://www.cnblogs.com/Afrafre/p/10181317.html
Copyright © 2020-2023  润新知