zoukankan      html  css  js  c++  java
  • python 黏包现象

    一、黏包

    1、tcp有黏包现象

    表现两种情况

      发送的数据过小且下面还有一个发送数据,这两个数据会一起发送

      发送的数据过大,超过最大缓存空间,超出的部分在下一次发送的时候发送

    原因:

      tcp是面向流的,根据算法,自动把数据拆分、组合,没有保护边界

    2、udp无黏包现象

    表现形式

      发送的数据包大小超出最大缓存空间,超出的数据直接丢弃

      udp不是面向流的,是面向消息的

    总结

      tcp协议是:可靠的,面向连接的,面向流的,效率低

      udp协议是:不可靠的,无连接的,面向对象的,效率高

      一般视频下载是tcp协议

      聊天软件是udp协议

    数据传输,传输的是数据包,数据包的内容是报文,报文有报头等

    二、黏包现象

    1、接连发生数据较小的数据包,且只接收数据一次

    """
    Server端
    在Client端接连发送两个小的数据包,Server端只有一个接收,且接收文件较大
    会出现黏包现象
    """
    import socket
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk.bind(('127.0.0.1', 8010))
    sk.listen()
    connect, addr = sk.accept()
    ret = connect.recv(1024)
    print(ret.decode('utf-8'))
    connect.close()
    sk.close()
    """
    Client端
    """
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8010))
    sk.send('tom'.encode('utf-8'))
    sk.send(' is god'.encode('utf-8'))
    sk.close()

    2、发送一个大的数据包,接收多次,且第一次接收的数据比较小

    """
    Server端
    在Client端发送一个数据包,Server端只接收两次,且第一次接受的数据较少
    会出现黏包现象
    """
    import socket
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk.bind(('127.0.0.1', 8010))
    sk.listen()
    connect, addr = sk.accept()
    ret1 = connect.recv(4).decode('utf-8')
    ret2 = connect.recv(10).decode('utf-8')
    print(ret1)
    print(ret2)
    connect.close()
    sk.close()
    """
    Client端
    """
    import socket
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8010))
    sk.send('tom is god'.encode('utf-8'))
    sk.close()

    示例

    """
    Server端
    向Client端发送cmd命令,利用subprocess,执行命令并且发送两次
    发送黏包现象
    """
    import socket
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk.bind(('127.0.0.1', 8010))
    sk.listen()
    connect, addr = sk.accept()
    while 1:
        cmd = input('>>>')
        connect.send(cmd.encode('gbk'))
        if cmd == 'q':
            break
        ret = connect.recv(1024).decode('gbk')
        print(ret)
    connect.close()
    sk.close()
    """
    Client端
    """
    import socket
    import subprocess
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8010))
    while 1:
        cmd = sk.recv(1024).decode('gbk')
        if cmd == 'q':
            break
        res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        std_out = res.stdout.read()     # bytes数据类型
        std_error = res.stderr.read()
        sk.send(std_out)
        sk.send(std_error)
        print(std_out.decode('gbk'))
        print(std_error.decode('gbk'))
    sk.close()

    三、解决黏包

    两种方法

      1、预先知道发送端发送数据包的大小

      2、使用struct变成固定大小的bytes类型

     第一种方法,为了不产生黏包,每执行一次多产生一次网络延迟

    """
    Server端
    """
    import socket
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk.bind(('127.0.0.1', 8010))
    sk.listen()
    connect, addr = sk.accept()
    while 1:
        cmd = input('>>>')
        connect.send(cmd.encode('utf-8'))
        if cmd == 'q':
            break
        new_len = int(connect.recv(1024).decode('utf-8'))
        connect.send(bytes('ok', 'utf-8'))
        msg = connect.recv(new_len)
        print(msg.decode('utf-8'))
    connect.close()
    sk.close()
    """
    Client端
    subprocess 产生的数据是bytes类型
    计数bytes的长度->str
    """
    import socket
    import subprocess
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8010))
    while 1:
        cmd = sk.recv(1024).decode('utf-8')
        if cmd == 'q':
            break
        res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        std_out = res.stdout.read()
        std_err = res.stderr.read()
        new_len = str(len(std_out + std_err))
        sk.send(new_len.encode('utf-8'))
        sk.recv(1024)
        sk.send(std_out)
        sk.send(std_err)
        print(std_out.decode('utf-8'))
        print(std_err.decode('utf-8'))
    sk.close()

     第二种方法使用struct

    struct的应用

    """
    'i'-> int
    作用:把数字转换成固定4个字节的bytes类型
    注意: unpack 时,要使用pack的返回值,unpack的是一个tuple,需要取第一个值
    """
    import struct
    a = struct.pack('i', 1234567)
    print(a)
    b = struct.unpack('i', a)[0]
    print(b, type(b))
    """
    b'x87xd6x12x00'
    1234567 <class 'int'>
    """

    解决黏包方法实现,每一次执行一次,对比上面的方法,少一次网络延迟

    """
    Server端,接收pack的数据,unpack
    """
    import socket
    import struct
    sk = socket.socket()
    sk.bind(('127.0.0.1', 8010))
    sk.listen()
    connect, addr = sk.accept()
    while 1:
        cmd = input('>>>')
        connect.send(cmd.encode('gbk'))
        if cmd == 'q':
            break
        num = connect.recv(4)
        b = struct.unpack('i', num)[0]
        ret = connect.recv(b)
        print(ret.decode('gbk'))
    connect.close()
    sk.close()
    """
    Client端,将数据的长度pack,并传输
    """
    import socket
    import subprocess
    import struct
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8010))
    while 1:
        cmd = sk.recv(1024).decode('gbk')
        if cmd == 'q':
            break
        res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        std_out = res.stdout.read()
        std_err = res.stderr.read()
        new_len = len(std_out) + len(std_err)
        res = struct.pack('i', new_len)
        sk.send(res)
        sk.send(std_out)
        sk.send(std_err)
        print(std_out.decode('gbk'))
        print(std_err.decode('gbk'))
    sk.close()

     简单的文件下载

    注意:文件的读写速度不一样,读的速度远大于写

    """
    Server端
    接收端
    bytes->str->dict
    """
    import socket
    import struct
    import json
    buff = 1024
    sk = socket.socket()
    sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sk.bind(('127.0.0.1', 8010))
    sk.listen()
    connect, addr = sk.accept()
    pack_len = connect.recv(4)
    head_len = struct.unpack('i', pack_len)[0]
    head_bytes = connect.recv(head_len)
    head_str = head_bytes.decode('utf-8')
    head = json.loads(head_str)
    print(head)
    file_size = head['file_size']
    with open(file=head['filename'], mode='wb') as f:
        while file_size:
            if file_size >= buff:
                context = connect.recv(buff)
                f.write(context)
                file_size -= buff
                print(file_size)
            else:
                try:
                    context = connect.recv(file_size)
                    f.write(context)
                except TypeError:
                    print('integer argument expected, got float')
                break
    connect.close()
    sk.close()
    """
    Client端
    发送端
    dict->str->bytes
    """
    import socket
    import os
    import json
    import struct
    buff = 1024
    sk = socket.socket()
    sk.connect(('127.0.0.1', 8010))
    # 设置文件报头,dict
    head = {'filepath': r'D:Temp', 'filename': r'test.mp4', 'file_size': None}
    file_path = os.path.join(head['filepath'], head['filename'])
    file_size = os.path.getmtime(file_path)
    head['file_size'] = file_size
    # dict ->str
    head_str = json.dumps(head)
    # str -> bytes
    head_bytes = head_str.encode('utf-8')
    # 将长度,转换成固定长度的bytes类型
    pack_len = struct.pack('i', len(head_bytes))
    sk.send(pack_len)
    sk.send(head_bytes)
    print(file_path)
    with open(file=file_path, mode='rb') as f:
        while file_size:
            if file_size >= buff:
                context = f.read(buff)
                sk.send(context)
                file_size -= buff
                print(file_size)
            else:
                try:
                    context = f.read()
                    sk.send(context)
                except TypeError:
                    print('integer argument expected, got float')
                break
    sk.close()
  • 相关阅读:
    NYOJ 527 AC_mm玩dota
    程序员励志小说链接
    android——ListView功能的实现
    调用系统工具
    HDU SPFA算法 Invitation Cards
    nginx sendfile tcp_nopush tcp_nodelay参数解释
    结构体中使用#define定义宏
    HRPlugin For Xcode发布(附源码地址)
    Derby的下载安装和使用,(和JAVA中使用Derby)
    UNIX环境高级编程——进程管理和通信(总结)
  • 原文地址:https://www.cnblogs.com/wt7018/p/10976695.html
Copyright © 2011-2022 走看看