网盘——客户端登陆注册注销请求

关于网盘设计,在客户端登录注册注销部分,主要有以下五个部分:消息类型、界面设计、注册、登录、注销

消息类型:我们可以把它写成枚举类型的

界面设计:

注册:用户名唯一,防止重复注册。查询数据是否存在用户注册的用户名,存在则回复失败,不存在的回复成功。在服务器新建文件夹用作该用户的网盘区域

登录:防止重复登陆,在数据库中添加字段用于记录用户是否在线,在线则不允许登录

注销:删除好友信息,删除个人信息,删除网盘文件。

目录

1、消息类型

2、界面设计

2.1、拖入对应的控件,并进行命名

2.2、登录、注册、注销都进行转到槽、

3、注册

3.1、编写注册代码

3.2、通过socket将他发送出去

3.3、在服务器端收数据

3.4、测试

A、运行TcpServer

B、运行TcpClient

C、在TcpServer可以看到该用户的信息以及数据类型

3.5、将数据写到数据库里面去

A、在Operator.h文件里面处理注册

B、判断消息类型

C、在协议protocol.h里面添加宏

3.6、运行测试

A、运行TcpServer

B、运行TcpClient

C、再注册一次tony,就会提示

D、查看cloud.db数据库里面是否有数据

4、登录

4.1、点击登录按钮,右键转到槽

A、定义枚举类型(用户端和服务器端都要添加,协议必须是一致的)

B、槽代码

C、类似于3.5节,我们在recv_msg里面添加 case ENUM_MSG_TYPE_LOGIN_REQUEST

D、同样,在协议.h文件里面写上

E、添加定义

F、添加宏定义——在协议.h里面

G、添加case ENUM_MSG_TYPE_LOGIN_REQUEST

H、在客户端tcpclient.cpp的recv_Msg里面添加

4.2、运行测试

A、运行TcpServer

B、运行TcpClient

C、再登录一次jack,就会提示

D、查看cloud.db数据库里面数据是否发生变化,我们发现jack的online字段已经改为1

5、注销

5.1、在登陆的时候,在服务器的mytcpsocket.h里添加一个成员函数

5.2、当登录成功的时候,就将他的名字给记录下来

5.3、创建一个槽函数,用来处理客户端下线

5.4、关联信号槽

5.5、在数据库.h里面定义一个函数

5.6、写一个信号,标志下线

5.7、在server定义槽函数,接收数据

5.8、删除socket槽函数

5.9、关联槽函数

6、测试

6.1、查看当前数据库信息

6.2、打开三个client用户的终端

A、输入三个用户名和密码都点击登录

B、查看终端显示

C、关闭jack,jack的online字段置为0

D、关闭lucy,lucy的online字段置为0

 E、关闭tony,tony的online字段置为0

7、最终代码


1、消息类型

我们设计成枚举类型的

//消息类型
enum ENUM_MSG_TYPE
{
    //最小的
    ENUM_MSG_TYPE_MIN = 0,
    //登录请求
    ENUM_MSG_TYPE_REGIST_REQUEST,
    //登录回复
    ENUM_MSG_TYPE_REGIST_RESPOND,
    //设置为32位。因为我们是要赋给一个无符号的整型
    ENUM_MSG_TYPE_MAX = 0x00ffffff,
};

在服务器端也得写

2、界面设计

2.1、拖入对应的控件,并进行命名

2.2、登录、注册、注销都进行转到槽、

void TcpClient::on_login_pb_clicked()
{
    
}

void TcpClient::on_regist_pb_clicked()
{
    
}

void TcpClient::on_cancel_pb_clicked()
{

}

3、注册

3.1、编写注册代码

void TcpClient::on_regist_pb_clicked()
{
    //获得界面数据
    QString strName = ui->name_le->text();
    QString strPwd = ui->pwd_le->text();
    //如果是空的
    if(!strName.isEmpty()&&!strPwd.isEmpty()){
        //发送注册请求
        //将用户名和密码填充到pdu里面去
        //消息部分不用放数据,直接写0
        PDU *pdu = mkPDU(0);
        //消息类型
        pdu->uiMsgType = ENUM_MSG_TYPE_REGIST_REQUEST;
        //将数据拷贝进去,也可以用strnpy
        memcpy(pdu->caData, strName.toStdString().c_str(),32);
        //从后面32个字节开始放pdu->caData+32,放我的密码,也都是放32个
        memcpy(pdu->caData+32, strPwd.toStdString().c_str(),32);
        //通过socket将他发送出去
        m_tcpSocket.write((char*)pdu, pdu->uiPDULen);
        //释放空间
        free (pdu);
        pdu =NULL;
        
    }else {
        QMessageBox::critical(this, "信息注册", "注册失败,用户名或者密码为空");
    }
}

3.2、通过socket将他发送出去

//通过socket将他发送出去
m_tcpSocket.write((char*)pdu, pdu->uiPDULen);
//释放空间
free (pdu);
pdu =NULL;

3.3、在服务器端收数据

//打印数据
    char caName[32] = {'\0'};
    char caPwd[32] ={'\0'};
    strncpy(caName, pdu->caData, 32);
    strncpy(caPwd, pdu->caData+32, 32);
    qDebug()<<caName<<caPwd<<pdu->uiMsgType;

3.4、测试

A、运行TcpServer

B、运行TcpClient

输入以下信息

C、在TcpServer可以看到该用户的信息以及数据类型

3.5、将数据写到数据库里面去

A、在Operator.h文件里面处理注册

因为是注册,不需要修改传过来的数据类型,所以直接写成const类型

bool handleRegist(const char *name, const char *Pwd);
bool OperateDB::handleRegist(const char *name, const char *Pwd)
{
    //将要执行的语句放在caQuery里面
    //    char caQuery[128] = {'\0'};
    //    sprintf(caQuery, "");

    if(NULL==name || NULL== Pwd){
        //考虑形参的有效性
        qDebug()<<"name |pwd is NUll";
        return  false;
    }
    //拼接
    QString data = QString("insert into usrInfo (name,pwd) values(\'%1\',\'%2\')").arg(name).arg(Pwd);
    qDebug()<<data;
    QSqlQuery query;
    return query.exec(data);
}

B、判断消息类型

在TcpClient.h和.cpp文件里面添加对应内容

void TcpClient::recvMsg()
{
    //打印当前可读的数据大小
    qDebug()<<m_tcpSocket.bytesAvailable();
    //收数据
    uint uiPDULen =0;
    //先收4个字节大小的数
    m_tcpSocket.read((char*)&uiPDULen, sizeof (uint));
    //根据总的大小,计算实际消息长度
    uint uiMsgLen = uiPDULen-sizeof (PDU);
    //根据实际消息长度,产生一个pdu,接收剩余的数据
    PDU *pdu = mkPDU(uiMsgLen);
    //(char*)pdu +sizeof (uint)从这儿开始放,放uiPDULen-sizeof (uint)些数据
    m_tcpSocket.read((char*)pdu +sizeof (uint),uiPDULen-sizeof (uint));
    //qDebug()<<pdu->uiMsgType<<(char*)pdu->caMsg;
    //判断消息类型
    switch (pdu->uiMsgType) {
    case ENUM_MSG_TYPE_REGIST_RESPOND:
    {
        if(0== strcmp(pdu->caData, REGIST_OK)){
            QMessageBox::information(this, "注册",REGIST_OK);
        }
        else if(0== strcmp(pdu->caData, REGIST_FAILED)){
            QMessageBox::warning(this, "注册", REGIST_FAILED);
        }
        break;
    }
    default:
        break;
    }
    free (pdu);
    pdu =NULL;
}

C、在协议protocol.h里面添加宏

#define REGIST_OK "regist ok"
#define REGIST_FAILED "regist failed : name existed"

3.6、运行测试

A、运行TcpServer

B、运行TcpClient

输入以下信息

C、再注册一次tony,就会提示

 

D、查看cloud.db数据库里面是否有数据

注册成功!!!!!!!

4、登录

在https://blog.csdn.net/wjl990316fddwjl/article/details/136983744这一节,我们创建用户表的时候,使用了online字段,这个字段假如是1,则登录过了,就不可以在登陆了,假如是0那就可以继续登录。

在之前我们已经添加的数据里面,可以看到,现在的在线情况都是0.

4.1、点击登录按钮,右键转到槽

A、定义枚举类型(用户端和服务器端都要添加,协议必须是一致的)

enum ENUM_MSG_TYPE
{
    //最小的
    ENUM_MSG_TYPE_MIN = 0,
    
    //注册
    ENUM_MSG_TYPE_REGIST_REQUEST,      //注册请求
    ENUM_MSG_TYPE_REGIST_RESPOND,      //注册回复
    //登录
    ENUM_MSG_TYPE_LOGIN_REQUEST,        //登录请求
    ENUM_MSG_TYPE_LOGIN_RESPOND,        //登录回复
}

B、槽代码

void TcpClient::on_login_pb_clicked()
{
    //获得界面数据
    QString strName = ui->name_le->text();
    QString strPwd = ui->pwd_le->text();
    //如果是空的
    if(!strName.isEmpty()&&!strPwd.isEmpty()){
        //发送登录请求
        //将用户名和密码填充到pdu里面去
        //消息部分不用放数据,直接写0
        PDU *pdu = mkPDU(0);
        //消息类型
        pdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_REQUEST;
        //将数据拷贝进去,也可以用strnpy
        memcpy(pdu->caData, strName.toStdString().c_str(),32);
        //从后面32个字节开始放pdu->caData+32,放我的密码,也都是放32个
        memcpy(pdu->caData+32, strPwd.toStdString().c_str(),32);
        //通过socket将他发送出去
        m_tcpSocket.write((char*)pdu, pdu->uiPDULen);
        //释放空间
        free (pdu);
        pdu =NULL;
        
    }else {
        QMessageBox::critical(this, "登录", "登录失败,用户名或者密码为空");
    }
}

C、类似于3.5节,我们在recv_msg里面添加 case ENUM_MSG_TYPE_LOGIN_REQUEST

    case ENUM_MSG_TYPE_LOGIN_REQUEST:
    {
        //1、查看有没有这个用户,
        //2、再就是查看有没有登陆过



        break;
    }

D、同样,在协议.h文件里面写上

    //-------------------------登录-----------------------------
    bool handleLogin(const char *name, const char *Pwd);

E、添加定义

bool OperateDB::handleLogin(const char *name, const char *Pwd)
{
    //将要执行的语句放在caQuery里面
    //    char caQuery[128] = {'\0'};
    //    sprintf(caQuery, "");
    
    if(NULL==name || NULL== Pwd){
        //考虑形参的有效性
        qDebug()<<"name |pwd is NUll";
        return  false;
    }
    
    //拼接一个查询语句
    //必须三个条件都满足
    QString data = QString("select * from usrInfo where name=\'%1\' and pwd=\'%2\' and online =0").arg(name).arg(Pwd);
    qDebug()<<data;
    QSqlQuery query;
    //select * 返回的是一个结果集
    query.exec(data);
    //获得每一个结果集
    //调用nect的时候会调用它的第一条数据,以此类推。直到没有数据,返回的是一个false
    //条件不匹配那就是没有数据,直接false,退出函数
    if(query.next()){
        //更新online
        data = QString("update usrInfo set online=1 where name=\'%1\' and pwd=\'%2\'").arg(name).arg(Pwd);
        qDebug()<<data;
        QSqlQuery query;
        query.exec(data);
        return  true;
        
    }else {
        return  false;
    }
}

F、添加宏定义——在协议.h里面

#define LOGIN_OK "login ok!"
#define LOGIN_FAILED "login failed : name or pwd or relogin!"

G、添加case ENUM_MSG_TYPE_LOGIN_REQUEST

        //------------------登录------------------
    case ENUM_MSG_TYPE_LOGIN_REQUEST:
    {
        //1、查看有没有这个用户,
        //2、再就是查看有没有登陆过
        char caName[32] = {'\0'};
        char caPwd[32] ={'\0'};
        strncpy(caName, pdu->caData, 32);
        strncpy(caPwd, pdu->caData+32, 32);
        
        bool ret =OperateDB::getInstance().handleLogin(caName,caPwd);
        PDU *respdu = mkPDU(0);
        respdu->uiMsgType = ENUM_MSG_TYPE_LOGIN_RESPOND;
        if(ret){
            strcpy(respdu->caData, LOGIN_OK);
        }else {
            strcpy(respdu->caData, LOGIN_FAILED);
        }
        //发送
        write((char*)respdu, respdu->uiPDULen);
        //释放空间
        free (respdu);
        respdu =NULL;
        
        
        break;
    }
    default:
        break;
    }
    free (pdu);
    pdu =NULL;
    //qDebug()<<caName<<caPwd<<pdu->uiMsgType;
}

H、在客户端tcpclient.cpp的recv_Msg里面添加

    //-----------------------------登录------------------------------
    case ENUM_MSG_TYPE_LOGIN_RESPOND:
    {
        if(0== strcmp(pdu->caData, LOGIN_OK)){
            QMessageBox::information(this, "登录",LOGIN_OK);
        }
        else if(0== strcmp(pdu->caData, LOGIN_FAILED)){
            QMessageBox::warning(this, "登录", LOGIN_FAILED);
        }
        break;
    }

4.2、运行测试

A、运行TcpServer

B、运行TcpClient

输入以下信息

C、再登录一次jack,就会提示

 

D、查看cloud.db数据库里面数据是否发生变化,我们发现jack的online字段已经改为1

登录完成!!!!!!!

5、注销

在登录的时候,我们将online字段设置成了1,那么我们在退出的时候,就应该将这个字段设置为0.要不然下次在登陆的时候还是1,就不可以再登录了。

在服务器那边,把产生的socket保存到了一个链表里面,那么再退出登陆的时候,我们就要将socket删除掉,因为客户端已经下线了,就没有必要了,如果不进行删除的话,下次再重新登陆过来就会产生新的socket,那这样就会类加成很多socket,就会占用很多资源,后面回产生资源耗尽,服务器崩溃。

5.1、在登陆的时候,在服务器的mytcpsocket.h里添加一个成员函数

到时候修改数据库状态的时候,可以通过修改它的名字来进行查找。

private:
    QString m_strName;

5.2、当登录成功的时候,就将他的名字给记录下来

m_strName = caName;

5.3、创建一个槽函数,用来处理客户端下线

void clientOffline();
void MyTcpSocket::clientOffline()
{
    //将在线状态设置为非在线状态
    //将数据库的状态做修改
    OperateDB::getInstance().handleOffline(m_strName.toStdString().c_str());
    //删除链表的socket
    emit offline(this);
}

5.4、关联信号槽

connect(this, SIGNAL(disconnected()), this, SLOT(clientOffline()));

5.5、在数据库.h里面定义一个函数

  //-------------------------注销-----------------------------
    void handleOffline(const char *name);
void OperateDB::handleOffline(const char *name)
{

    if(NULL==name){
        //考虑形参的有效性
        qDebug()<<"name is NUll";
        return;
    }
    QString data = QString("update usrInfo set online=0 where name=\'%1\'").arg(name);
    qDebug()<<data;
    QSqlQuery query;
    query.exec(data);
}

5.6、写一个信号,标志下线

signals:
    //下线_放地址
    void offline(MyTcpSocket *mysocket);

5.7、在server定义槽函数,接收数据

public slots:
    void deleteSocket(MyTcpServer *socket);

5.8、删除socket槽函数

public slots:
    void deleteSocket(MyTcpSocket *mysocket);
void MyTcpServer::deleteSocket(MyTcpSocket *mysocket)
{
    QList<MyTcpSocket*>::iterator iter = m_tcpSocketList.begin();
    for(;iter!=m_tcpSocketList.end(); iter++)
    {
        if(mysocket==*iter){
            delete  *iter;
            *iter = NULL;
            m_tcpSocketList.erase(iter);
            break;
        }
    }
    for(int i=0; i<m_tcpSocketList.size();i++)
    {
        qDebug()<<m_tcpSocketList.at(i)->getName();
    }
}

5.9、关联槽函数

connect(pTcpSocket, SIGNAL(offline(MyTcpSocket*)), this, SLOT(deleteSocket(MyTcpSocket*)));

6、测试

6.1、查看当前数据库信息

6.2、打开三个client用户的终端

A、输入三个用户名和密码都点击登录

B、查看终端显示

查看数据库发现这三个人的onlin字段都是1;

C、关闭jack,jack的online字段置为0

D、关闭lucy,lucy的online字段置为0

 E、关闭tony,tony的online字段置为0

7、最终代码

http://链接:https://pan.baidu.com/s/1kwnan3pWmdIVgJjk4owCHw 提取码:fann

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/488461.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UI自动化_id 元素定位

## 导包selenium from selenium import webdriver import time1、创建浏览器驱动对象 driver webdriver.Chrome() 2、打开测试网站 driver.get("你公司的平台地址") 3、使浏览器窗口最大化 driver.maximize_window() 4、在用户名输入框中输入admin driver.find_ele…

编译u-boot(硬件: atk-dl6y2c)和NFS/EMMC模式启动Linux Kernel

目录 概述 1 编译u-boot 1.1 解压文件 1.2 编译u-boot 2 配置环境 2.1 在Ubunt 搭建TFTP 2.2 建立下载目录 3 烧写bootloader到SD 4 使用NFS模式启动板卡 5 从EMMC 启动 Linux 系统 5.1 通过配置参数方式 5.2 使用命令直接启动内核 文中使用的代码下载地址&#xf…

QT(3/25)

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示“登录成功”&#xff0c;提供一个OK按钮&#xff0c;用户点击OK后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面。 如果账号和密码不匹配&#…

day04_JDBC_课后练习 - 参考答案(一共11道练习题)

文章目录 day04_JDBC_课后练习第1题参考答案第1题-第3题的sql第4题第5题第6题第7题第8题第9题第10题第11题 day04_JDBC_课后练习 第1题 案例&#xff1a; 1、创建数据库day04_test01_bookstore 2、创建如下表格 &#xff08;1&#xff09;图书表books &#xff08;2&#…

基于SpringBoot+MyBatis校园周边美食探索及分享平台

采用技术 基于SpringBootMyBatis校园周边美食探索及分享平台的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 功能清单 前台首页 登录页面 美食鉴赏界面…

【牛客】SQL142 对试卷得分做min-max归一化

描述 现有试卷信息表examination_info&#xff08;exam_id试卷ID, tag试卷类别, difficulty试卷难度, duration考试时长, release_time发布时间&#xff09;&#xff1a; idexam_idtagdifficultydurationrelease_time19001SQLhard602020-01-01 10:00:0029002Chard802020-01-0…

等保测评密评对照:一文看懂两者差异

最近&#xff0c;在去几个客户的办公室交流的方案的时候&#xff0c;都会被重点问到网络安全问题,在方案中“等保”是如何体现和落实的。而且有些客户的领导也会提到“密评”与“等保”如何衔接&#xff0c;是否有先后顺序&#xff0c;可否同时进行测评等问题。 关于“等保”与…

后端常问面经之Java集合

HashMap底层原理 HashMap的数据结构&#xff1a; 底层使用hash表数据结构&#xff0c;即数组和链表或红黑树 当我们往HashMap中put元素时&#xff0c;利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 存储时&#xff0c;如果出现hash值相同的key&#xff0c;此…

《自动机理论、语言和计算导论》阅读笔记:p28-p48

《自动机理论、语言和计算导论》学习第3天&#xff0c;p28-p48总结&#xff0c;总计21页。 一、技术总结 1.希腊字母(Greek Alphabet包含大小写)。 24个&#xff0c;掌握其读写有助于阅读及数学推导时使用。 2.自动机理论的核心概念 (1)alphabet (2)string (3)language …

【CXL协议-事务层之CXL.mem(3)】

3.3 CXL.mem 3.3.1 Introduction &#xff08;介绍&#xff09; CXL 内存协议称为 CXL.mem&#xff0c;它是 CPU 和内存之间的事务接口。 跨芯片通信时&#xff0c;它使用 Compute Express Link (CXL) 的物理层和链路层。 该协议可用于多种不同的内存连接选项&#xff0c;包括…

Pandas操作MultiIndex合并行列的Excel,写入读取以及写入多余行及Index列处理,插入行,修改某个单元格的值

Pandas操作MultiIndex合并行列的excel&#xff0c;写入读取以及写入多余行及Index列处理 1. 效果图及问题2. 源码参考 今天是谁写Pandas的 复合索引MultiIndex&#xff0c;写的糊糊涂涂&#xff0c;晕晕乎乎。 是我呀… 记录下&#xff0c;现在终于灵台清明了。 明天在记录下直…

Vue3进阶(叁):关于 Vue2 项目迁移至 Vue3 的几点注意事项

文章目录 一、前言二、Vue3 新特性三、Vue2 安全保障四、迁移方案4.1 迁移辅助工具 vue/compat 库 五、迁移注意事项5.1 升级和兼容性5.2 组件的改变5.3 Composition API5.4 Vue Router5.5 Vuex5.6 其他库和插件5.7 差异示例5.7.1 创建vue5.7.2 定义时间格式全局过滤器5.7.3 vu…

PTAxt的考研路

xt是我院19级专业第一&#xff0c;但他认为保研并不能展示他全部的实力&#xff0c;所以他在22年初试一结束就加入了23考研的队伍中&#xff0c;并且他为了填补我院近些年来无北大研究生的空白&#xff0c;毅然决然决定扛起19级的大旗&#xff0c;在学校百年华诞之际献上他最诚…

平台介绍-搭建赛事运营平台(3)

上文介绍了品牌隔离的基本原理&#xff0c;就是通过不同的前端和微服务来实现。但是确实很多功能是类似的&#xff0c;所以从编程角度还是有些管理手段的。 前端部分&#xff1a;前端部分没有什么特别手段&#xff0c;就是两个独立的项目工程&#xff0c;分别维护。相同的部分复…

maven的依赖继承

先说一下创建子maven工程的步骤 继承 继承的作用&#xff1a;在父工程中&#xff0c;统一管理项目中的依赖信息&#xff0c;进行统一的版本控制 继承的背景是&#xff1a;对一个大型的项目进行了模块拆分&#xff0c;一个project下&#xff0c;创建了很多的module&#xff0c…

绝地求生:PUBG七周年在线人数70万,比六周年增加24万

大家好&#xff0c;我是闲游盒 自从3月PUBG7周年活动开启&#xff0c;PUBG在线人数再次突破70万&#xff0c;黑盒数据显示最高在线人数达704344人,PUBG迎来又一春。 对比6周年的数据而言&#xff0c;7周年的玩家在线人数增长了24万&#xff01; 而根据数据显示&#xff0c;PUBG…

centos7 装 docker-ce

安装必要的系统工具&#xff1a; sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 命令会以超级用户的身份安装三个软件包&#xff1a;yum-utils&#xff0c;device-mapper-persistent-…

『笔记』可扩展架构设计之消息队列

前言 众所周知&#xff0c;开发低耦合系统是软件开发的终极目标之一。低耦合的系统更加容易扩展&#xff0c;低耦合的模块更加容易复用&#xff0c;更易于维护和管理。我们知道&#xff0c;消息队列的主要功能就是收发消息&#xff0c;但是它的作用不仅仅只是解决应用之间的通…

数据库高级查询【mysql】

数据库高级查询【数据库】 前言版权推荐数据库高级查询行转列统计数据 CASE WHEN 条件 THEN 结果1 ELSE 结果2 END数据库查询带排名建表查询带排名&#xff0c;排名连续查询带排名&#xff0c;排名不连续查询带排名&#xff0c;按行号 Mysql数据库函数常用函数最后 前言 2024-…

(四)图像的%2线性拉伸

环境&#xff1a;Windows10专业版 IDEA2021.2.3 jdk11.0.1 OpenCV-460.jar 系列文章&#xff1a; &#xff08;一&#xff09;PythonGDAL实现BSQ&#xff0c;BIP&#xff0c;BIL格式的相互转换 &#xff08;二&#xff09;BSQ,BIL,BIP存储格式的相互转换算法 &#xff08;三…