Python全栈学习笔记day 32:黏包、struct模块

本文深入探讨TCP协议中的黏包现象,分析其成因,包括发送方与接收方的缓存机制,以及Nagle算法的影响。同时,文章提供了解决黏包问题的方法,如通过自定义报头和使用struct模块实现数据的正确拆分。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、黏包

1.1  黏包成因

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

1.1.1  TCP协议中的数据传递

         当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 
         MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包。MTU的单位是字节。 大部分网络设备的MTU都是1500。如果本机的MTU比网关的MTU大,大的数据包就会被拆开来传送,这样会产生很多数据包碎片,增加丢包率,降低网络速度

面向流的通信特点和Nagle算法:

    TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。
收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。
这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。 
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。 
可靠黏包的tcp协议:tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

 

1.1.2   UDP不会发生黏包

UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。 
不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。 

1.1.3  会发生黏包的两种情况

情况一: 发送方的缓存机制

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

           情况二 接收方的缓存机制

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 

 

黏包现象只发生在tcp协议中:

1.从表面上看,黏包问题主要是因为发送方和接收方的缓存机制、tcp协议面向流通信的特点。

2.实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

 

二、黏包的解决方案

        2.1  struct模块

该模块可以把一个类型,如数字,转成固定长度的bytes

struct.pack('i',4)    4就是转成固定长度的bytes类型的数字
struct.unpack('i',bytes类型)[0]        输出元祖

上传下载(自定制报头):

client端:

import json
import socket

sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()

conn,addr = sk.accept()
content = conn.recv(1024).decode('utf-8')
content_dic = json.loads(content)
if content_dic['operate'] == 'upload':
    conn.send(b'received!')
    with open(content_dic['filename'],'wb') as f:
        while content_dic['filesize']:
            file = conn.recv(1024)
            f.write(file)
            content_dic['filesize'] -= len(file)
conn.close()
sk.close()

server端:

import os
import json
import socket

sk = socket.socket()
sk.connect(('127.0.0.1',8080))

def get_filename(file_path):
    filename = os.path.basename(file_path)
    return filename

operate = ['upload','download']
for num,opt in enumerate(operate,1):
    print(num,opt)
num = int(input('请输入您要做的操作序号 : '))
if num == 1:
    '''上传操作'''
    #file_path = 'E:\python10\day33\作业.py'
    file_path = input('请输入要上传的文件路径 : ')
     告诉对方要上传的文件的名字
    file_name = get_filename(file_path)
     读要上传的文件 存成字符串
    with open(file_path,encoding='utf-8') as f:
        content = f.read()
    dic = {'operate':'upload','filename':file_name,'content':content}
     将字符串send给server端
    str_dic = json.dumps(dic)
    sk.send(str_dic.encode('utf-8'))
     server端接收 bytes转码程字符串
     server端打开文件 写文件
elif num == 2:
    '''下载操作'''
sk.close()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值