4.3 主要步骤及具体实现方案 4.3.1 查询服务器端信息 [1]设计思路: 服务器端信息记录于http://localhost/update/index.asp(这里以本机测试为例)文件中,主要包括版本号、更新文件路径信息。在ASP中,使用 Response对象可以将输出发送到客户端,其中的Write方法Respongs.Write是将变量作为字符串写入当前的 HTTP 输出。这里是通过读取index.asp中的Respongs.Write得到字符串,此字符串为服务器版本号以及Client.exe更新文件路径。 [2]具体实现如下: 以本机测试为例,由strInfo=dlg->pMyFun->InternetGetInfo(strUrl)得到#”&VersionAuto&”#http://”&ServerName&”/UpDate/Client.exe# 即#1.0.0.1#http://127.0.0.1/UpDate/Client.exe#; 然后由: iPos1=strInfo.Find(“#”); iPos2=strInfo.Find(“#”,iPos1+1); NewVersion=strInfo.Mid(iPos1+1,iPos2-iPos1-1); 得到服务器端版本号; 最后再由: iPos1=iPos2; iPos2=strInfo.Find(“#”,iPos1+1); strUpdateUrl=strInfo.Mid(iPos1+1,iPos2-iPos1-1); 得到更新文件Client.exe路径; 函数Cstring::Find和Cstring::Mid将在4.3.2中说明。 4.3.2 比较新旧版本 [1]设计思路: 版本号格式设计为X.X.X.X,例如客户原始版本号为1.0.0.1;这里我采用的比较方法是把版本号转换为4位数字,如:版本号1.2.3.4分别提取这4个字符然后转换成整形变量,采用公式1*1000+2*100+3*10+4转换成1234,最后比较大小。 [2]相关函数说明: (1)Cstring::Find(str,”要查找的字符串”,pos),函数用于从给定的字符串中寻找并返回第一处匹配指定子字符串开始的序号, 第三个参数pos指定搜索开始的位置,这个参数可以省略(使用默认值1),如果字符串不能包含该子字符串相匹配部分,则返回-1; (2)Cstring::Mid(string,npos,n), 函数用于从字符串 string 的 npos 位开始截取 n 位; (3)atoi函数用于将字符串转换成整型数。 [3]下面以版本号第一位的比较为例来说明程序版本的比较方法: iOldPos1=strOldVersion.Find(“.”); iOldPos2=strOldVersion.Find(“.”,iOldPos1+1); iOldPos3=strOldVersion.Find(“.”,iOldPos2+1); //分别保存老版本号中第一、二、三个“.”字符出现的序号到iOldPos1,iOldPos2,iOldPos3; iPos1=strNewVersion.Find(“.”); iPos2=strNewVersion.Find(“.”,iPos1+1); iPos3=strNewVersion.Find(“.”,iPos2+1); //分别保存新版本号中第一、二、三个“.”字符出现的序号到iPos1,iPos2,iPos3; strOldVer1=strOldVersion.Mid(0,1); strOldVer2=strOldVersion.Mid(iOldPos1+1,1); strOldVer3=strOldVersion.Mid(iOldPos2+1,1); strOldVer4=strOldVersion.Mid(iOldPos3+1,1); //分别从版本号字符串的第0、iOldPos1+1、iOldPos2+1、iOldPos3+1位截取1个字符长度保存到strOldVer1、strOldVer2、strOldVer3、strOldVer4; strNewVer1=strNewVersion.Mid(0,1); strNewVer2=strNewVersion.Mid(iPos1+1,1); strNewVer3=strNewVersion.Mid(iPos2+1,1); strNewVer4=strNewVersion.Mid(iPos3+1,1); //分别从版本号字符串的第0、iPos1+1、iPos2+1、iPos3+1位截取1个字符长度保存到strNewVer1、strNewVer 2、strNewVer 3、strNewVer 4; Oldnum1=atoi(strOldVer1); Oldnum2=atoi(strOldVer2); Oldnum3=atoi(strOldVer3); Oldnum4=atoi(strOldVer4); //分别把4个老版本号字符转换成整形; Newnum1=atoi(strNewVer1); Newnum2=atoi(strNewVer2); Newnum3=atoi(strNewVer3); Newnum4=atoi(strNewVer4); //分别把4个新版本号字符转换成整形; Oldnum=Oldnum1*1000+Oldnum2*100+Oldnum3*10+Oldnum4; Newnum=Newnum1*1000+Newnum2*100+Newnum3*10+Newnum4; if(Newnum>Oldnum) { bHaveNewVersion=TRUE; } return bHaveNewVersion; //通过比较两个版本号转换值来确定版本新旧与否。 4.3.3 获得升级程序文件的路径 [1]首先简要说明下GetModuleFileName函数: 格式:GetModuleFileName(NULL, szPath,MAX_PATH); 说明:把指定模块中的可执行文件绝对路径与文件名赋值给szPath, [2]设计思路: 首先通过GetModuleFileName函数得到本地升级程序MyUpdate.exe的路径,例如:F:\CUIT\毕业设计\备份\7.24\MyUpdate\Debug\Myupdate.exe。当然得到的路径还包括EXE名,这里需要先去掉这部分,然后才能得到目录。这里我们可以使用iPos_U =strFilePath.Find(“MyUpdate.exe”)找到程序上级目录所在字符的序号,最后使用strPath=strFilePath.Mid(0,iPos_U)得到F:\CUIT\毕业设计\备份\7.24\MyUpdate\Debug,即得所求正确路径。 4.3.4 获得文件升级后的保存路径 [1]首先简要说明下string.format函数: 格式string.format(fm,...); 第一个参数用fm表示输出的格式,每个%符号后面是一个格式化表达式,格式化表达式:%[零个或多个标志][最小字段宽度][精度][修改符]格式码(PS:[]方括号表示可选参数),这里用到的是格式码 s:参数:字符串值(string) 含义:打印一个字符串 [2]实现方法: 通过strOutLocal.Format(“%s%s”,strPath,_T(“MyClient.exe”)),这样就得到文件升级后的保存路径和文件名,即F:\CUIT\毕业设计\备份\7.24\MyUpdate\Debug\ MyClient.exe。 4.3.5 从服务器下载文件并保存到本地 如何从Internet上有效而稳定地下载文件 ,这是很多网络应用程序要考虑的重要问题,下面是本人针对这个问题进行的初步探索: [1]DWORD dwSize; //这个变量被用于存储每次调用InternetReadFile读取了多少数据; [2]CHAR szHead[] = “Accept: */*\r\n\r\n”; //用于存储多个HTTP头信息。如果在调用InternetOpenUrl时不传递这个头信息,则只能允许打开文本文件;
[3]VOID* szTemp[16384]; //缓冲变量,可以存储来自Internet的16KB的文件数据; [4]HINTERNET hConnect; //这是一个HINTERNET句柄,包含请求结果(来自InternetOpenUrl); [5]if (!(hConnect = InternetOpenUrlA (hOpen, szUrl, szHead, lstrlenA (szHead),INTERNET_FLAG_DONT_CACHE|INTERNET_FLAG_PRAGMA_NOCACHE|INTERNET_FLAG_RELOAD, 0))) //此调用可以打开一个使用URL的Internet文件句柄。标志表示这个文件总是被读取,而不是缓存(cache); [6]DWORD dwByteToRead = 0; DWORD dwSizeOfRq = 4; DWORD dwBytes = 0; //这三个值分别存储文件的大小,HttpQueryInfo内容的大小和总共读取的字节数; [7]if(!HttpQueryInfo(hConnect,HTTP_QUERY_CONTENT_LENGTH|HTTP_QUERY_FLAG_NUMBER, (LPVOID)&dwByteToRead, &dwSizeOfRq, NULL)) { dwByteToRead = 0; } //此调用可以获得文件的大小。如果失败则dwByteToRead被置为0,并且当文件被下载时不会显示百分比和总数; [8] memset(szTemp,0,sizeof(szTemp)); //meset格式:void *memset(void *s, int c, size_t n); memset:作用是在一段内存块中填充某个给定的值,它对较大的结构体或数组进行清零操作的一种最快方法; [9]Do{InternetReadFile (hConnect, szTemp, 16384, &dwSize) { ……… } }while(TRUE); //此调用循环中,每次下载一个16KB的数据块。 4.3.6 关于如何保存当前版本号的问题 [1]这里采用的是使用TXT文件来保存当前客户端版号的方法,下面是用到的几个函数的说明: (1)FILE * fopen(const char * path,const char * mode); 函数说明: 参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。 mode有下列几种形态字符串: r 打开只读文件,该文件必须存在; r+ 打开可读写的文件,该文件必须存在; w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件; w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 (2)*fgets(char *string, int n, FILE *stream); 函数说明: 从指定的文件中读一个字符串到字符数组中,函数调用的形式为: fgets(字符数组名,n,文件指针); 其中的n是一个正整数。表示从文件中读出的字符串不超过 n-1个字符。在读入的最后一个字符后加上串结束标志’\0’。例如:fgets(str,n,fp);的意义是从fp所指的文件中读出n-1个字符送入字符数组str中。 [2]实现过程: (1)首先用fopen 的r方式打开version.txt文件,看是否为空。如为空或文件不存在则默认程序的OldVersion为1.0.0.1;如不为空,则使用fgets函数读取现版本号存入OldVersion: FILE *fp = fopen(“version.txt”,”r”); if(fp == NULL) { OldVersion=_T(“1.0.0.1”); } else { char str[100]; fgets(str,100,fp); OldVersion=str ; fclose(fp); (2)判断是否更新版本号,如是,则用fopen 的w+方式打开version.txt,清空该文件的内容(版本号),写入新版本号,即实现了版本号的更新。 FILE *fp = fopen(“version.txt”,”w+”); { fprintf(fp,”%s”,NewVersion.GetBuffer(0)); } fclose(fp); 5 程序测试过程及结果 5.1 老版-新版本成功升级 服务器版本:2.0.0.1 客户端版本:1.0.0.1 测试结果如图5和图6所示。 |