软件自动更新解决方案及QT实现

from:https://blog.csdn.net/hulinhulin/article/details/46839107

软件自动更新解决放案及QT实现...1

1 文件的版本控制-XML.2

2 更新程序的实现...2

2.1 界面设置...2

2.2 程序功能...3

2.2.1 下载网络数据...3

2.2.2 XML文件的分析...6

2.2.3 下载XML文件的DownLoadXML函数...8

2.2.4 返回指定XML文件中的name版本号...8

2.2.5 返回指定XML文件中的name版本号...10

2.2.6 比较两个XML文件CheckUpdateFiles.11

2.2.7 下载文件DownLoadUpdateFiles.13

2.2.7 退出当前程序,并启动指定的程序...16

3 更新程序的启动...16

3.1 从主程序启动更新程序...16

3.2 主程序的关闭...17

3.3 更新程序关闭时启动主程序...17

4 程序调试...17

软件自动更新解决放案及QT实现

需要考虑解决的问题:

1) 需要知道哪些文件需要更新,哪些不需要;

2) 从哪里下载更新文件;

3) 将旧的文件用新的文件替换掉(包含版本控制文件);

4) 更新完毕后重新启动主程序;

 

第一个问题,可以为程序所使用的文件都设定一个版本号,版本号都记录在一个 XML 文件中,升级时,检查最新程序的版本控制文件和当前的版本控制文件,最新版本号较大时,表示该文件需要更新。如果一个文件不再需要了,则将该文件的版本信息从最新的版本控制文件中删除,通过对比控制文件,就知道该文件不再需要了,可以将之删除;

第二个问题,最新的版本控制文件需要放在一个可供方便下载的地方,例如FTP或者一个固定的IP;

第三个问题,通过对比新的版本控制文件和旧的版本控制文件来确定哪些需要替换或者删除;

第四个问题,更新程序运行完后,启动主程序即可,需要一个标识来说明是否更新完成;

 

按照以上的思路,下面对每一个步骤结合程序进行详细地阐述,软件使用QT5.1实现。

 

1 文件的版本控制-XML

下面是使用XML文件来表示的版本控制文件:

<?xml version="1.0"encoding="utf-8"?> 

<filelist> 

 <filename="qico.dll" dir="imageformats"version="1.0"/>

 <filename="qminimal.dll" dir="imageformats"version="1.0"/>

…..

 <file name="main.exe"version="1.0"/>

</filelist> 

Name表示文件的名称;

dir表示所在的目录(相对目录);

version表示当前文件的版本;

2 更新程序的实现

更新程序使用QT5.1来实现,最终生成一个可执行文件(exe文件)。新建工程时选择QT GUI应用,其他都默认,工程名为Updater。

2.1 界面设置

在构造函数中设置界面。

应用程序在屏幕中间:

QDesktopWidget *deskdop = QApplication::desktop();

this->move((deskdop->width() -this->width())/2, (deskdop->height() - this->height())/2);

 

无标题栏:

this->setWindowFlags(Qt::FramelessWindowHint);//没有标题栏

 

隐藏菜单栏和工具栏  

this->ui->menuBar->hide();

this->ui->mainToolBar->hide();

 

固定高和宽:

this->setFixedSize(400,200);

   

设置背景颜色(两种方法都可以)

   //this->setStyleSheet("QMainWindow{background:rgb(240,250,250)}");

QPalette pal;

pal.setColor(QPalette::Background,QColor(255,245,225) );

this->setPalette(pal);

this->setAutoFillBackground(true);

2.2 程序功能

    程序功能包括从网络下载数据,分析XML文件,比较当前的XML及下载的XML文件,并最终确定哪些文件需要更新或者添加

2.2.1 下载网络数据

新建类CHttpDownLoadFile,类功能:从指定网络中下载指定的文件,并且存储到指定的本地文件目录中。

主要的成员函数及槽函数:

public slots:

   void ReplyNewDataArrived();//响应m_netReply有新的数据到达

   void ReplyFinished();//响应数据接收完成

public:

   QNetworkAccessManager *m_netAccessManager;//网络参数

   QNetworkReply *m_netReply;

 

   QUrl m_urlAdress;//网络地址

   QString m_strFileName;//需要下载的文件名

   QString m_strDir;//文件的存储位置

 

   QFile *m_file;//下载的文件

 

       qint64m_nReceived;//下载文件时,已经接收的文件大小和总共大小

   qint64m_nTotal;

主要函数及功能:

a.构造函数

CHttpDownloadFile(QString url,QStringfileName,QString dir,QObject *parent = 0);

url表示文件的网络地址;

filename表示文件名;

dir表示文件存储路径

    如果fileName不为空,那么文件名使用fileName,否则从url提取(注:不需要加后缀)。如果dir不为空,那么将文件存储到dir指向的文件夹中,否则存储在默认路径中(即与可执行文件在同一个文件夹中),如果文件夹不存在,那么会创建,dir举例:c:/temp/,或者c:/temp,如果前面不加盘符,那么将会在默认文件夹中创建。

b. 开始下载文件的函数:void DownLoadFile()

m_netReply=m_netAccessManager->get(QNetworkRequest(m_urlAdress));

connect(m_netReply,SIGNAL(readyRead()),

this,SLOT( ReplyNewDataArrived()) );//当有新数据到达时就会触发此信号

connect(m_netReply, SIGNAL(finished()),

this,SLOT( ReplyFinished()) );//完成数据接收后发送此信号

connect(m_netReply, SIGNAL(error(QNetworkReply::NetworkError)),this, SLOT( ReplyError(QNetworkReply::NetworkError)) );//出现错误时发送此信号;

connect(m_netReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(ReplyDownloadProgress(qint64,qint64) ) );//用来提示文件下载进度

 

   /*************存储文件的检测及使用************/

   if( m_strFileName.isEmpty() )//文件名

    {

       QFileInfo fileInfo(m_urlAdress.path());

       m_strFileName = fileInfo.fileName();

    }

 

   if( !m_strDir.isEmpty() )//文件夹

    {

       QDir directory( m_strDir );

 

       if( !directory.exists() )//没有此文件夹,则创建

       {

           directory.mkpath( m_strDir );

       }

 

       m_strFileName = m_strDir + "/"+m_strFileName;//添加/是为了防止用户名没有加/,因为对于文件夹来说两个/都会当成一个/

    }

   if( QFile::exists(m_strFileName) )//如果文件已经存在,那么删除

    {

       QFile::remove(m_strFileName);

    }

   m_file = new QFile( m_strFileName );

   if (!m_file->open(QIODevice::WriteOnly))

    {

       qDebug()<<"不能存储文件:"<<m_strFileName;

       delete m_file;

       m_file = NULL;

       return;

    }

c.槽函数void ReplyFinished(),当下载网络数据结束时响应此函数,主要是释放资源

   m_netAccessManager->deleteLater();

   m_netReply->deleteLater();

   m_file->close();

   m_file->deleteLater();

 

d.槽函数voidReplyNewDataArrived()—当数据到达时调用此函数,并存储到指定的文件中
    if(m_file)
    {
        m_file->write(m_netReply->readAll());
        m_file->flush();//注意需要刷新
    }
    else
    {
        qDebug()<<m_netReply->readAll();
    }

2.2.2 XML文件的分析

    QFile file(filename);

    if(!file.open(QIODevice::ReadOnly | QFile::Text)) {

       qDebug()<<"open for read error..." ;

    }

    QString errorStr;

    int errorLine;

    int errorColumn;

    QDomDocument doc;

   if(!doc.setContent(&file, false, &errorStr, &errorLine,&errorColumn)) {

       qDebug()<<"setcontent error..." ;

        file.close();

    }

    file.close();

    QDomElement root =doc.documentElement();

    if (root.tagName() !="filelist")

    {

      qDebug()<<"root.tagname != filelist.." ;

    }

    else

    {

        QDomNodeListnodeList = root.elementsByTagName("file");

        for(inti=0;i<nodeList.size();i++)

        {

            qDebug()<<nodeList.at(i).toElement().attribute("name")

                     <<nodeList.at(i).toElement().attribute("dir")

                       <<nodeList.at(i).toElement().attribute("version");

        }

}       

}

这里只是将XML文件的内容输出,详细的代码在工程Updater的其他函数中。

2.2.3 下载XML文件的DownLoadXML函数

函数下载XML文件,当XML文件下载完成时发送下载完成信号,提示进行下一步操作。

    /**从网页下载XML版本控制文件,里面记录了最新的文件版本**/

    QStringstrCurrentDir=QDir::currentPath();//当前程序运行路径

    QStringstrDownLoad=strCurrentDir+"/download/";//存放下载文件的路径

 

    QDirdirectory(strDownLoad);//如果路径不存在,则创建

    if(!directory.exists())

    {

       directory.mkpath(strDownLoad);

    }

 

    m_httpXML=newCHttpDownloadFile("http://www.***.com/download/**.xml","",strDownLoad,this);//调用下载文件的类

    connect(m_httpXML,SIGNAL(DownloadFinishedSignal()),this,SLOT(ReplyHttpFinished()));//发生错误时一样会发送此信号

m_httpXML->DownLoadFile();

在ReplyHttpFinished()函数中需要错误处理。

实例化m_httpXML时,需要将parent设置为this,这样当程序结束时,会自动释放。

2.2.4 返回指定XML文件中的name版本号

    /***********在xml中查找名字与name相同的元素,并返回版本号**

     *xml:xml文件的路径;

     *name:需要查找的元素名称;

     *

     *return:QString:版本号,如果有则返回,没有则为空

     ************************************************/

 QStringGetElementVersion(QStringxml,QStringname);

 

实现:

GetElementVersion(QStringxml,QStringname)

{

    QStringresult="";

    if(xml.isEmpty()||name.isEmpty())

    {

       qDebug()<<"名称或者xml文件路径为空";

       returnresult;

    }

 

    if(!QFile::exists(xml))

    {

       qDebug()<<"xml文件不存在";

       returnresult;

    }

 

    QFilefile(xml);

    if(file.open(QIODevice::ReadOnly|QFile::Text))//文件打开成功

    {

       QDomDocumentdoc;

       if(doc.setContent(&file))

       {

           QDomElementroot=doc.documentElement();

           if(root.tagName()=="filelist")

           {

                inti=0;

                QDomNodeListnodeList=root.elementsByTagName("file");

                for(;i<nodeList.size();i++)

                {

                    QStringtempName   =nodeList.at(i).toElement().attribute("name");

                    //QStringdir    =nodeList.at(i).toElement().attribute("dir");

                    QStringversion=nodeList.at(i).toElement().attribute("version");

 

                    if(name==tempName)

                    {

                        qDebug()<<"find!"<<name;

                        result=version;

                        break;

                    }

                }

 

                if(i==nodeList.size())

                {

                    qDebug()<<"can'tfind!"<<name;

                }

           }

           else

           {

                qDebug()<<"root.tagname!=filelist..";

           }

       }

       else

       {

           qDebug()<<"setcontenterror...";

       }

 

       file.close();

    }

    else

    {

       qDebug()<<"openforreaderror...";

    }

 

    returnresult;

}

2.2.5 返回指定XML文件中的name版本号

    /**************比较两个版本号*******************

     *@params:

     * v1,v2:两个版本号,格式:1.1.0,不能为空

     *

     *@return:

     * true:如果两个版本号码相同;

     * false:两个版本号不同

     *

     *说明:暂时只比较了是否相同

     *******************************************/

 boolCheckVersion(QStringv1,QStringv2);

实现比较简单,就不贴代码了。

2.2.6 比较两个XML文件CheckUpdateFiles

通过比较两个XML文件来确定需要下载的文件。

/************比较两个XML文件****************

     *@params:

     * name1,name2:两个XML文件,不能为空,并且文件需要存在,name1必须是最新的xml文件(刚刚下载下来的),name2是本地的xml文件;

     *

     *@return:

     *0-someerrorhappens,elsesuccess

     *将需要更新的文件名存储到m_listFileName

     *路径存储到m_listFileDir

     *

     *注意:m_listFileDir和m_listFileName的个数是一样的,

     *    如果没有元素表示所有的文件都不用更新

     ******************************************/

 intCheckUpdateFiles(QStringname1,QStringname2);

 

实现:

CheckUpdateFiles(QStringname1,QStringname2)

{

    m_listFileDir.clear();

    m_listFileName.clear();

 

    if(name1.isEmpty()||name2.isEmpty())return0;

 

    if(QFile::exists(name2))

    {

       if(QFile::exists(name1))

       {

           m_strTip="检查需要更新的文件...";

 

           QFilefile(name1);

           if(file.open(QIODevice::ReadOnly|QFile::Text))//文件打开成功

           {

                QStringerrorStr;

                interrorLine;

                interrorColumn;

 

                QDomDocumentdoc;

                if(doc.setContent(&file,false,&errorStr,&errorLine,&errorColumn))

                {

                    QDomElementroot=doc.documentElement();

                    if(root.tagName()=="filelist")

                    {

                        QDomNodeListnodeList=root.elementsByTagName("file");

                        for(inti=0;i<nodeList.size();i++)

                        {

                            QStringname   =nodeList.at(i).toElement().attribute("name");

                            QStringdir    =nodeList.at(i).toElement().attribute("dir");

                            QStringversion=nodeList.at(i).toElement().attribute("version");

 

                            QStringversionDownload=GetElementVersion(name2,name);//获取本地xml文件对应文件(name)的版本信息

                            if(versionDownload.isEmpty())//本地XML没有此文件:下载,并放到相应的目录中

                            {

                                m_listFileDir.append(dir);

                                m_listFileName.append(name);

                            }

                            else

                            {

                                /**检查版本,如果本地版本低于下载的版本,则下载**/

                                if(!CheckVersion(version,versionDownload))

                                {

                                    m_listFileDir.append(dir);

                                    m_listFileName.append(name);

                                }

                                else

                                {

                                     qDebug()<<name<<"文件是最新版本,不需要更新";

                                }

                            }

                        }

 

                        return1;//此时要退出,避免关闭程序

                    }

                    else

                    {

                        m_strTip="XML内容错误!";

                        return0;

                    }

                }

                else

                {

                    qDebug()<<"setcontenterror...";

                    return0;

                }

 

                file.close();

           }

           else

           {

                m_strTip="不能打开更新文件!";

                return0;

           }

       }

       else

       {

           m_strTip="下载更新文件错误!";

           return0;

       }

    }

    else

    {

       m_strTip="本地的更新文件不存在!";

       return0;

    }

}

2.2.7 下载文件DownLoadUpdateFiles

  下载需要的文件,完成后启动主程序 。

/********下载最新的版本的文件,并替换或者增加*********************

     *旧XML中有的文件,新XML没有的,此文件不做处理。

     *下载信息由m_listFileName和m_listFileDir提供

     *****************************************************/

   voidDownLoadUpdateFiles();

实现:

DownLoadUpdateFiles()

{

    QStringstrServer="http://www.***.com/download/";//需要下载的文件存储位置

    QStringstrCurrentDir=QDir::currentPath();//当前程序运行路径

 

    if(m_listFileDir.isEmpty()||m_listFileDir.isEmpty())

    {

       qDebug()<<"没有需要下载的文件1";

       ExitApp(strCurrentDir+"/main.exe");

       return;

    }

 

    m_strTip="开始下载更新文件...";

 

 

 

    m_bIsFinished=false;

 

    for(inti=0;i<m_listFileName.size();i++)

    {

       m_strTip="正在下载文件"+m_listFileName.at(i);

       m_progUpdate->setValue(100*i/m_listFileName.size());

 

       /**放置下载的文件的路径**/

       QStringstrPlaceDir=strCurrentDir+"/download/"+m_listFileDir.at(i);

       QDirdirectory(strPlaceDir);//如果路径不存在,则创建

       if(!directory.exists())directory.mkpath(strPlaceDir);

 

       QStringstrFileDirServer=strServer+m_listFileDir.at(i)+"/"+m_listFileName.at(i);//文件在服务器中的存储位置

       CHttpDownloadFile*http=newCHttpDownloadFile(strFileDirServer,"",strPlaceDir,this);//调用下载文件的类

       http->DownLoadFile();

       while(!http->m_bIsFinished)

       {

           if(http->m_nTotal==-1)

           {

                m_progDownload->setValue(1);

           }

           else

           {

                m_progDownload->setValue(100*http->m_nReceived/http->m_nTotal);

           }

           QCoreApplication::processEvents();

       }

 

       m_strTip="文件"+m_listFileName.at(i)+"下载完成";

 

 

       /**将下载好的文件复制到主目录中,先删除原先的文件**/

       QStringstrLocalFileName=strCurrentDir+"/"+m_listFileDir.at(i)+"/"+m_listFileName.at(i);

       if(QFile::exists(strLocalFileName))QFile::remove(strLocalFileName);

 

       QDirdirectory1(strCurrentDir+"/"+m_listFileDir.at(i));//如果路径不存在,则创建

       if(!directory1.exists())directory1.mkpath(strCurrentDir+"/"+m_listFileDir.at(i));

 

       QFile::copy(strPlaceDir+"/"+m_listFileName.at(i),strLocalFileName);

    }

 

    m_bIsFinished=true;

    m_strTip="更新完成!";

 

    /**替换旧的xml文件**/

    QStringstrNewXML=strCurrentDir+"/download/**.xml";//最新的XML文件

    QStringstrOldXML=strCurrentDir+"/"+"**.xml";//旧的XML文件

 

    QFile::remove(strOldXML);

    QFile::copy(strNewXML,strOldXML);

 

    ExitApp(strCurrentDir+"/main.exe");

}

2.2.7 退出当前程序,并启动指定的程序

    /********退出当前程序,并且启动需要的程序*************************

     *name:需要启动的程序,可以使用相对位置

     *程序不能使用exit(0),会发生线程错误,这里使用this->close()函数

     *****************************************************/

实现:

ExitApp(QStringname)

{

    if(!name.isEmpty())

    {

       /**运行主程序,并且退出当前更新程序(说明:主程序在上上一级目录中)**/

       if(!QProcess::startDetached(name))//启动主程序,主程序在其上一级目录

       {

           QMessageBox::warning(this,"警告信息","启动主程序错误!\n可能主程序不存在或者被破坏!\n解决办法:重新安装程序!");

       }

    }

 

    this->close();

}

3 更新程序的启动

    更新程序是独立的可执行文件,所以在启动主程序时,首先启动更新程序(注:网络检查放在了更新程序中),然后关闭主程序,更新完成后,再重新启动主程序。

3.1 从主程序启动更新程序

    首先需要判断是否需要启动更新程序,避免更新后再次启动更新程序;

    将是否需要启动更新程序的参数放在软件的初始参数文件中,例如params.txt文件中,初始值为true,表示需要启动更新程序。主程序启动时,先获取里面的参数,并判断是否需要启动更新程序。完成更新后,将此参数设置为false,表示下次再次启动主程序时不需更新,当点击关闭主程序时再次将此参数设置为true,这样就可以保证每次启动时都会检查更新;

启动更新程序调用QProcess::startDetached()方法,启动外部程序后立即返回(也就是主程序也运行),即使主程序关闭,启动的程序也会运行,本程序使用这个方法,因为更新后。

注:如果调用QProcess::execute函数,此方法可以阻塞主程序的运行,直到启动的程序关闭,因为需要关闭主程序,还要重新启动更新后得程序,所以不能使用这个方法。

3.2 主程序的关闭

    如果QProcess::startDetached方法返回true,则关闭主程序,调用exit(0),或者quit()方法;

3.3 更新程序关闭时启动主程序

更新程序结束时调用QProcess::startDetached("main.exe"),主程序和更新程序在同一目录。但是在关闭更新程序的时候使用exit会发送错误,错误原因:会出现夸线程发送信号的情况,这是不容许的。

当所有文件都下载完成时,使用close函数。

4 程序调试

需要调试以下功能:

(1)主程序启动时,检测是否需要启动更新程序, 检测params.cq文件里面的is_update参数的值,如果为1则启动,否则不启动(代码在CLoginBox的构造函数中);

(2)如果is_update=1,那么启动更新程序,更新程序启动后,is_update设置为0;

(3)主程序退出时,将is_update设置为1,即每次重新启动主程序的时候都需要检查更新;

(4)正确下载xml文件,存储位置:与主程序同目录下的download文件 夹中(特别提醒:由于更新程序是从主程序中调用的,所以在更新程序中调用获取当前运行程序的目录是得到的是主程序的的目录,但是如果更新程序单独运行的话,得到的目录是和更新程序目录一样的,所以最好的方式就是讲主程序和更新程序放在同一个目录中);

(5)是否正确调用了更新程序里面的CheckUpdateFiles函数,主要是检查是否输入正确的xml文件目录;

(6)下载的更新文件是否正确放置;

(7)下载完后旧的XML是否换成新的XML;

(8)运行主程序的电脑或者服务端没有网络的情况;

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hulinhulin/article/details/46839107

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java 基本功 —— 内存相关

2019独角兽企业重金招聘Python工程师标准>>> 首先我们来说说内存&#xff0c;因为从内存的角度来出发来分析一些变量&#xff0c;引用或者对象的生命周期会更好理解一些。 java是一门编程语言&#xff0c;他跟C有什么不同呢&#xff1f;本质上&#xff0c;他们都是一…

DOM事件处理有三个阶段

DOM事件处理有三个阶段&#xff1a; 捕捉阶段&#xff08;capture phase&#xff09;&#xff1a;从最上层元素&#xff0c;直到最下层&#xff08;你点击的那个target&#xff09;元素。路过的所有节点都可以捕捉到该事件。命中阶段&#xff08;target phase&#xff09;&…

客户端程序自动更新(升级)的方式

from&#xff1a;https://blog.csdn.net/woaitingting1985/article/details/72954652一、C/S自动更新原理C/S程序自动升级是一个很重要的功能&#xff0c;原理其实很简单&#xff0c;一般包含两个程序一个是主程序&#xff0c;也就是除了升级功能以外的程序&#xff0c;另一个就…

怎么用源程序把ChemDraw结构复制到Word文档

在学习化学过程中&#xff0c;不可避免的会接触到各种化学结构。这个时候就需要通过绘制化学结构来进行这方面的学习和传播。ChemDraw Professional 15就可以辅助完成这方面的工作。很多的用户朋友会通过选中后复制粘贴可以将ChemDraw结构复制到Word文档中&#xff0c;但这只是…

网络流(最大流) HDU 1565 方格取数(1) HDU 1569 方格取数(2)

HDU 1565 方格取数(1)给你一个n*n的格子的棋盘&#xff0c;每个格子里面有一个非负数。从中取出若干个数&#xff0c;使得任意的两个数所在的格子没有公共边&#xff0c;就是说所取的数所在的2个格子不能相邻&#xff0c;并且取出的数的和最大。 Input 包括多个测试实例&#…

python学习 第一篇 基础

上周报名了reboot python 课程&#xff0c;终于下决心要把python 搞好了&#xff0c;希望自己能坚持下来&#xff0c;并得到自己想要的成绩#coding:utf-8 #呵呵 #print hello world #xhello world #print x #xraw_input(hello world) #print x #int #print 23 #print 12*3 #pri…

QT串口编程的相关类(QSerialPortInfo)

QT Serial Port相关的类只有两个QSerialPortInfo(#include<QSerialPortInfo>) 和QserialPort(#include<QSerialPort>) 先来介绍QSerialPortInfo 1&#xff1a;QSerialPortInfo(#include<QSerialPortInfo>) 该类是一个串口的辅助类类&#xff0c;提供主要是提…

用jquery写一个属于自己的音乐播放器

看到一个用css3实现的CD的动画&#xff0c;演示在这儿http://codepen.io/_kieran/pen/QNRmep 突然那我就想说给自己做一个音乐播放器吧&#xff0c;说做就做。演示在https://echolsx.github.io/music/ Github传送门&#xff1a;https://github.com/EchoLsx/music 主要代码&…

四年一闰 随笔

今天日子比较特殊&#xff0c;碰到闰年的2月29日。好久没有记录随笔了&#xff0c;今天随便记上几笔吧 1、上家公司居然没帮我交社保&#xff0c;一整年了&#xff0c;发工资时还照扣社保的钱。。。现在说会补差额给我&#xff0c;算下来一年XXXX&#xff0c;也只是个数字&…

Qt 串口类QSerialPort 使用笔记

Qt 串口类QSerialPort 使用笔记虽然现在大多数的家用PC机上已经不提供RS232接口了。但是由于RS232串口操作简单、通讯可靠&#xff0c;在工业领域中仍然有大量的应用。Qt以前的版本中&#xff0c;没有提供官方的对RS232串口的支持&#xff0c;编写串口程序很不方便。现在好了&a…

什么是H标签?H1,H2,H3标签?以及和strong标签使用的方法及重要性

大家都知道&#xff0c;seo的一个很重要的一点就是要把网站做的条理清晰&#xff0c;让搜索引擎很容易的读明白&#xff0c;这个条理清晰不仅体现在网站的物理路径&#xff0c;url等地 方。在<h1><h2><h3>等方面也是这样。并不是<h1>对于关键字排名有帮…

OC语言中的便利初始化函数和便利构造器

便利遍历初始化函数与便利构造器&#xff08;以Student类为例&#xff09;&#xff1b; main函数 Student.h&#xff08;声明&#xff09; 。。。。。。。。。。。。。。。。。。。 Student.m&#xff08;实现&#xff09; 。。。。。。。。。。。。。。。。。 转载于:https://…

MySQL 性能监控 4 大指标

【编者按】本文作者为 John Matson&#xff0c;主要介绍 mysql 性能监控应该关注的 4 大指标。 文章系国内 ITOM 管理平台 OneAPM 编译呈现。 MySQL 是什么&#xff1f; MySQL 是现而今最流行的开源关系型数据库服务器。由 Oracle 所有&#xff0c;MySQL 提供了可以免费下载的社…

【深度相机系列四】深度相机原理揭秘--结构光(iPhone X 齐刘海原理)

from&#xff1a;https://blog.csdn.net/electech6/article/details/78707839导读 结构光法&#xff1a;为解决双目匹配问题而生 深度图效果&#xff1a;结构光vs.双目 投射图案的编码方式直接编码时分复用编码空分复用编码 Kinect1原理 iPhone X原深感相机是缩小版的更强大的K…

iOS开发中对于一些常用的相对路径(持续更新)

1.iOS开发的证书的描述文件放置地点 ~/Library/MobileDevice/Provisioning Profiles 2.$(SRCROOT)代表的是这个项目文件夹所在的位置 $(PROJECT_DIR) 表示的包含可执行文件的哪一个文件夹 3.对于pod导入的第三方库&#xff0c;引用不自动补全问题。选择target -> BuildSet…

Android倒计时工具类

为什么80%的码农都做不了架构师&#xff1f;>>> 原文地址:http://my.oschina.net/reone/blog/710003 多谢touch_ping 的回应. 原来api有这个类 android.os.CountDownTimer , 具体实现很下面的差不多. import android.content.Context; import android.os.Handler…

深度相机原理揭秘--双目立体视觉

欢迎关注计算机视觉life&#xff01;导读 为什么非得用双目相机才能得到深度&#xff1f; 双目立体视觉深度相机的工作流程 双目立体视觉深度相机详细工作原理理想双目相机成像模型极线约束图像矫正技术基于滑动窗口的图像匹配基于能量优化的图像匹配 双目立体视觉深度相机的优…

微信扫码支付模式一和模式二的区别

http://www.baidu.com/link?urlAj_xhOM5Q6rpZXkTMBPq4o0UbCO4eLq0esX8B3K2v06bkRS8F8lC4k06rv-3uZARLLTEKJHMhwzI_cdcJiHfqK&wd&eqid904bc71f000181740000000356d7d9bf https://www.zhihu.com/question/35818812/answer/66086727 知乎页面访问存在502 Bad Gateway问题…

双目视觉几何框架详解(玉米专栏8篇汇总)

一、图像坐标&#xff1a;我想和世界坐标谈谈(A) 玉米竭力用轻松具体的描述来讲述双目三维重建中的一些数学问题。希望这样的方式让大家以一个轻松的心态阅读玉米的《计算机视觉学习笔记》双目视觉数学架构系列博客。这个系列博客旨在捋顺一下已标定的双目视觉中的数学主线。数…

(原)Ubuntu14中安装GraphicsMagick

转载请注明出处&#xff1a; http://www.cnblogs.com/darkknightzh/p/5661439.html 参考网址&#xff1a; http://comments.gmane.org/gmane.comp.video.graphicsmagick.core/514 http://www.graphicsmagick.org/INSTALL-unix.html https://github.com/clementfarabet/graphics…