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()
原文地址:https://www.cnblogs.com/wt7018/p/10976695.html