python-day32 粘包 报头

网络编程的粘包问题

一 什么是粘包

定义:粘包是存在于tcp协议中的,因为tcp是传输是需要三次握手和四次挥手的,这两种模式分别是确立通道和确保数据输送完整的.  在这个过程中因为传输和接收的不一致性就会到时粘包的出现.  而在udp模式中则不存在这种现象,因为udp是只管传输,不管接收的

二  粘包出现在tcp模式中的三种情况

粘包会出现在发送端  和 接收端

1: 发送端输入的内容过小且时间间隔很短   会粘包

2:接收端一次性接受了多次传输的内容时    会粘包

3;接收端一次接收小于一次传输的内容量  下次继续接收时  会把剩下没有接收的和下次要接收的内容粘在一起     

 

三 解决粘包的方案

粘包问题没出现之前 是按照指定的长度来进行传输数据   而粘包的问题主要是接收信息差造成的  那么让双方明确传输的长度就能能解决这问题   那么解决方案就是先传递长度  在传输内容

解决步骤:

发送端

1:利用struct将目标数据转化为固定的字节

2:发送数据长度给接收duan

3;发送数据

 

接收端

1;先接收长度数据   此时字节数是固定的

2:接收数据   (当发送端储传输的数据过大时,需要利用循环来进行分批次接收)

 

首先介绍struct 模块

是内置函数   主要功能是将数据转化为固定字节模式  

struct应用
import struct

# 整形转化为字节数  struct.pack()  q代表的是八个字节
res = struct.pack('q',100)
print(res)   #
print(len(res))   # 8


#  字节数转化为整形 struct.unpack() 返回的 是一个元祖  取值则取0号位就是目标整数
res2 = struct.unpack('q',res)
print(res2)   # (100,)
print(res2[0])  # 100  

四 解决粘包案例

案例一 不完善

客户端版本
import socket
client= socket.socket()  # 创建socket对象


client.connect(("192.168.13.80",1933))  # 连接服务器
print("链接成功!")
while True:
    msg = input(":")
    if msg == "q": break
    if not msg: continue
    client.send(msg.encode("utf-8"))  # 发送信息

    data = client.recv(1024)  # 接收数据 
    length= int(data.decode('utf-8'))
    print(length)

    size = 0
    res = b''
    while size < length:
        temp= client.recv(1024)
        size += len(temp)
        res += temp
    print(res.decode("gbk"))
  
注意:send  发  只能发二进制数据  
    recv  收  收多少字节
    收发数据 需要循环
    
    
    
    
服务器版本   
    
import socket  
import subprocess

server = socket.socket()  # 创建socket对象
server.bind(('192.168.13.80',1933))  # 绑定固定的ip和端口号  必须是元祖
server.listen()  # 开始监听客户端的信息
while True:
    client_socket,client_addr = server.accept() # 接收客户端的连接请求
    print('接到对方信息 开始工作了')
    while True:
            data = client_socket.recv(1024).decode('utf-8')  # 收数据
            print(data)
            if not data:
                break
            p = subprocess.Popen(data,shell= True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            data = p.stdout.read()+p.stderr.read()
            length= str(len(data))
            client_socket.send(length.encode('utf-8'))  # 发长度数据
            print(length)  # 发长度
            client_socket.send(data)  # 发具体数据

案例一 不完善的地方 : 但是由于negle优化机制的存在,长度信息和数据还是有可能会粘包,而接受方并不知道长度信息具体几个字节,所以现在的问题是如何能够长度信息做成一个固定长度的bytes数据

案例二 引用struct模块 

客户端

import socket
import struct
c = socket.socket()
c.connect(("127.0.0.1",8888))
while True:
    cmd = input(">>>:").strip()
    c.send(cmd.encode("utf-8"))

    data = c.recv(4)
    length = struct.unpack("i",data)[0]
    
    print(length)
    size = 0
    res = b""
    while size < length:
        temp = c.recv(1024)
        size += len(temp)
        res += temp
    print(res.decode("gbk"))
服务器

import socket
import subprocess
import struct
server = socket.socket()
server.bind(("127.0.0.1",8888))
server.listen()

while True:
    client, addr = server.accept()
    while True:
        cmd = client.recv(1024).decode("utf-8")
        p = subprocess.Popen(cmd,shell=True,stdout=-1,stderr=-1)
        data = p.stdout.read()+p.stderr.read()
        length = len(data)
        len_data = struct.pack("i",length)
        client.send(len_data)

        print(length)
        client.send(data)
老师版本
"""
客户端输入指令
服务器接收指令并执行  最后返回执行结果
"""
1 导入的模块  from 二_CMD程序 import smallTool

import struct

def recv_data(client):
    # 接收长度数据  固定位8个字节
    len_bytes = client.recv(8)
    # 转换为整型
    len_size = struct.unpack("q", len_bytes)[0]
    print("服务器返回了%s长度的数据" % len_size)

    # 再接收真实数据
    # 问题  如果数据量太大 则不能 一次收完  必须循环一次收一部分

    # 缓冲区大小
    buffer_size = 1024
    # 已接受大小
    recv_size = 0

    # 最终的数据
    data = b""
    while True:
        # 如果剩余数据长度 大于缓存区大小 则缓冲区有多大就读多大
        if len_size - recv_size >= buffer_size:
            temp = client.recv(buffer_size)
        else:
            #  剩余数据长度 小于缓冲区大小   剩多少就收多少
            temp = client.recv(len_size - recv_size)
        recv_size += len(temp)
        data += temp
        # 当已接受大小等于数据总大小则跳出循环
        if recv_size == len_size:
            break

    # print(data.decode("gbk"))  # windows执行指令返回的结果默认为GBK
    return data



 2 客户端
import socket
from 二_CMD程序 import smallTool
import struct

client = socket.socket()
try:
    client.connect(("127.0.0.1",1688))
    print("链接成功!")
    while True:
        msg = input("请输入要执行指令:").strip()
        if msg == "q": break
        if not msg: continue
        # 发送指令
        # 先发长度
        len_bytes = struct.pack("q",len(msg.encode("utf-8")))
        client.send(len_bytes)
        # 在发指令
        client.send(msg.encode("utf-8"))

        data = smallTool.recv_data(client)
        print(data.decode("GBK"))

    client.close()
except ConnectionRefusedError as e:
    print("链接服务器失败了!",e)
except ConnectionResetError as e:
    print("服务器挂了!", e)
    client.close()
    
    
    
  3 服务器
import socket
import subprocess
import struct
from 二_CMD程序 import  smallTool

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",1688))
server.listen()
# back

while True:
    # socket,addr一个元组 客户端的ip和port
    client,addr = server.accept()
    print("客户端链接成功!")
    # 循环收发数据
    while True:
        try:
            cmd = smallTool.recv_data(client)
            if not cmd:
                break
            print(cmd)

            p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            # 不要先读err错误信息  它会卡主  原因不详  linux不会有问题  tasklist  netstat - ano啥的
            data = p.stdout.read()
            err_data = p.stderr.read()

            len_size = len(data) + len(err_data)
            print("服务器返回了: %s " %  len_size)

            len_bytes = struct.pack("q",len_size)

            # 在发送真实数据前先发送 长度
            client.send(len_bytes)

            # 返回的结果刚好就是二进制
            # 发送真实数据
            client.send(data + err_data)


        except ConnectionResetError as e:
            print("客户端了挂了!",e)
            break
    client.close()

#server.close()

五 自定义报头

粘包问题解决了 就可以按照思路去添加其他内容   其实就是粘包的延伸

什么时候用报头

当需要在传输数据时 传呼一些额外参数时就需要自定义报头

报头本质是一个json  数据

具体过程如下:

发送端

 

1 发送报头长度

2  发送报头数据    其中包含了文件长度  和其他任意的额外信息 

3  发送文件内容

接收端

1.接收报头长度

2.接收报头信息

3.接收文件内容

服务器
import socket
import os
import struct
import json
"""
客户端接链成功我就给你发个文件过去  
固定的文件下载

"""

server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",1688))
server.listen()
# back

while True:
    # socket,addr一个元组 客户端的ip和port
    client,addr = server.accept()
    print("客户端链接成功!")
    f = None
    try:

        path = r"F:\2.半链接数.mp4"
        file_size = os.path.getsize(path)


        # 我想把文件名发过去
        file_info = {"file_name":"半链接数.mp4","file_size":file_size,"md5":"xxxxxxxxx"}

        json_str = json.dumps(file_info).encode("utf-8")

        # 发送报头长度
        client.send(struct.pack("q",len(json_str)))

        # 发报头
        client.send(json_str)

        # 发文件了
        # 发送文件数据
        f = open(path,"rb")
        # 循环发送文件内容   每次发2048
        while True:
            temp = f.read(2048)
            if not temp:
                break
            client.send(temp)
        print("文件发送完毕!")

    except Exception as e:
        print("出问题了",e)
    finally:
        if f:f.close()
    client.close()

    # 无论是否抛出异常 文件都要关闭

#server.close()

# 用户可以指定要下载什么文件 FTP

  

"""
客户端输入指令
服务器接收指令并执行  最后返回执行结果
"""

import socket
import struct
import json

client = socket.socket()
try:
    client.connect(("127.0.0.1",1688))
    print("链接成功!")

    # 1.先收报头长度
    head_size = struct.unpack("q",client.recv(8))[0]

    # 2.收报头数据
    head_str = client.recv(head_size).decode("utf-8")
    file_info = json.loads(head_str)
    print("报头数据:",file_info)
    file_size = file_info.get("file_size")
    file_name = file_info.get("file_name")


    # 3.再收文件内容
    # 已接收大小
    recv_size = 0
    buffer_size = 2048
    f = open(file_name,"wb")
    while True:
        if file_size - recv_size >= buffer_size:
            temp = client.recv(buffer_size)

        else:
            temp = client.recv(file_size - recv_size)
        f.write(temp)
        recv_size += len(temp)
        print("已下载:%s%%" % (recv_size / file_size * 100))
        if recv_size == file_size:
            break
    f.close()
except ConnectionRefusedError as e:
    print("链接服务器失败了!",e)

 

 

 

 

猜你喜欢

转载自www.cnblogs.com/wakee/p/10946698.html