python之粘包现象

1、粘包现象

1.1、什么是粘包

只有TCP有粘包现象,UDP永远不会粘包

粘包不一定会发生
如果发生了:
1.可能是在客户端已经粘了
2.客户端没有粘,可能是在服务端粘了

socket收发消息的原理

在这里插入图片描述

应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。(因为TCP是流式协议,不知道啥时候开始,啥时候结束)。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

1.2、粘包的两种情况
1.2.1、方式一
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据量很小,会合到一起,产生粘包)

# 服务端
from socket import *
ip_port = ('127.0.0.1', 8080)

TCP_socket_server = socket(AF_INET, SOCK_STREAM)
TCP_socket_server.bind(ip_port)
TCP_socket_server.listen(5)

conn, addr = TCP_socket_server.accept()

data1 = conn.recv(10)
data2 = conn.recv(10)

print('----->', data1.decode('utf-8'))
print('----->', data2.decode('utf-8'))

conn.close()

# 客户端
import socket
BUFSIZE = 1024
ip_port = ('127.0.0.1', 8080)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = s.connect_ex(ip_port)

s.send('hello'.encode('utf-8'))
s.send('allen'.encode('utf-8'))
1.2.2、方式二
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

# 服务端
from socket import *
ip_port = ('127.0.0.1', 8080)

TCP_socket_server = socket(AF_INET, SOCK_STREAM)
TCP_socket_server.bind(ip_port)
TCP_socket_server.listen(5)

conn, addr = TCP_socket_server.accept()

data1 = conn.recv(2)  # 一次没有收完整
data2 = conn.recv(10)  # 下次收的时候,会先取旧的数据,然后取新的

print('----->', data1.decode('utf-8'))
print('----->', data2.decode('utf-8'))

conn.close()

# 客户端
import socket
BUFSIZE = 1024
ip_port = ('127.0.0.1', 8080)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
res = s.connect_ex(ip_port)

s.send('hello allen'.encode('utf-8'))
1.3、解决远程执行命令粘包问题

服务端

import socket
import subprocess
import struct

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind(('127.0.0.1', 8080))

server.listen(5)

while True:
    conn, client_addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024)
            obj = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()

            total_size = len(stdout) + len(stderr)
            # 先发送数据的长度
            conn.send(struct.pack('i', total_size))
            # 发送真正的数据
            conn.send(stdout)
            conn.send(stderr)

        except Exception as e:
            break
    conn.close()

server.close()

客户端

import socket
import struct

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(('127.0.0.1', 8080))

while True:
    msg = input('请输入命令: ').strip()
    client.send(msg.encode('utf-8'))

    # 先收数据的长度
    n = 0
    header = b''
    while n < 4:
        data = client.recv(1)
        header += data
        n += len(data)

    total_size = struct.unpack('i', header)[0]

    # 收真正的数据
    recv_size = 0
    res = b''
    while recv_size < total_size:
        data = client.recv(1024)
        res += data
        recv_size += len(data)
    print(res.decode('gbk'))

client.close()
1.4、定制复杂的报头(拷贝文件)

服务端

import os
import struct
import json
from socket import *

server = socket(AF_INET, SOCK_STREAM)

server.bind(('127.0.0.1', 8080))

server.listen(5)

while True:
    conn, client_addr = server.accept()
    while True:
        try:
            msg = conn.recv(1024).decode('utf-8')
            cmd, file_path = msg.split()

            # 判断文件是否存在
            if not os.path.exists(file_path):
                conn.send('路径不存在'.encode('utf-8'))

            # 3、处理数据
            if cmd == 'get':
                # 一、制作报头
                header_dict = {
                    'total_size': os.path.getsize(file_path),
                    'file_name': os.path.basename(file_path),
                }
                header_json_dict = json.dumps(header_dict)
                header_json_bytes = header_json_dict.encode('utf-8')

                # 二、发送数据
                # 1、先发送报头的长度
                conn.send(struct.pack('i', len(header_json_bytes)))
                # 2、再发送报头
                conn.send(header_json_bytes)
                # 3、最后发送真实的数据
                with open(r'{}'.format(file_path), mode='rb') as fp:
                    for line in fp:
                        conn.send(line)
        except Exception as e:
            break
    conn.close()

server.close()

客户端

import socket
import struct
import json

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(('127.0.0.1', 8080))

while True:
    msg = input('请输入具体操作(get 文件路径): ').strip()
    if len(msg) == 0: continue
    client.send(msg.encode('utf-8'))

    # 1、先接收报头的长度
    header = b''
    n = 0
    while n < 4:
        data = client.recv(1)
        header += data
        n += len(data)
    header_size = (struct.unpack('i', header)[0])

    # 2、再接收报头
    header_info = b''
    n = 0
    while n < header_size:
        data1 = client.recv(1)
        header_info += data1
        n += len(data1)
    header_info_dict = json.loads(header_info.decode('utf-8'))

    # 3、最后接收真实的数据
    recv_size = 0
    with open(r'download/{}'.format(header_info_dict.get('file_name')), mode='wb') as fp:
        while recv_size < header_info_dict.get('total_size'):
            data = client.recv(1024)
            fp.write(data)
            recv_size += len(data)
client.close()
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值