目录
3、能否将拷贝到临时目录的过程省略掉,直接对原始日志目录压缩呢?
4、使用命令行命令copy替换掉对CopyFile的调用,有效地提升了文件拷贝的速度
4.2、使用CreateProcess创建一个cmd.exe进程去执行命令行命令,可以控制cmd.exe窗口的显示
4.3、除了CreateProcess,还可以调用WinExec、ShellExecuteEx去实现
C++软件异常排查从入门到精通系列教程(核心精品专栏,订阅量已达8000多个,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战专栏(重点专栏,专栏文章已更新500多篇,订阅量已达6000多个,欢迎订阅,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/140824370C++ 软件开发从入门到实战(重点专栏,专栏文章已更新300多篇,欢迎订阅,持续更新中...)
https://blog.csdn.net/chenlycly/category_12695902.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)
https://blog.csdn.net/chenlycly/article/details/124272585C++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)
https://blog.csdn.net/chenlycly/category_2276111.html 测试同事在测试从服务器侧获取客户端软件日志的功能时,发现客户端上传日志的速度很慢,甚至会出现上传失败的问题。本文详细讲述一下该问题的排查与优化过程,供大家借鉴或参考。
1、问题说明
我们软件支持上传日志到平台,这是平台运维的一个基础功能。当客户端软件出问题时,用户可以主动上传日志到平台,也可以从平台上拉取客户端的日志(运维服务器主动给指定客户端发送指令,让其将日志上传到平台),待日志上传成功后,再到平台上将日志文件下载下来进行分析。
测试同事在测试上述日志上传功能时发现,不管是客户端主动上传日志,还是从平台侧拉取客户端日志,速度都非常慢,甚至会出现日志上传失败的情况。该问题已出现多次,已严重影响使用了,必须排查优化一下。于是添加日志打印,通过查看日志发现,在拷贝日志文件到临时目录中时会特别慢,大部分时间都消耗在这个步骤上。
我们在上传日志之前,会先将多个日志文件拷贝到一个临时目录中,然后再对该临时目录进行压缩,打成zip包,然后再上传。在将多个日志文件拷贝到临时目录这一过程消耗了大量的时间。
2、初步分析
既然时间都消耗在将多个日志文件拷贝到临时目录这一过程中,就去查看这一过程的实现代码,如下所示:
void CopyLogFiles(LPCTSTR lpszDir)
{
CUIString strFindFileName = lpszDir;
strFindFileName += _T("log\\");
strFindFileName += _T("*.*");
WIN32_FIND_DATA wfd;
HANDLE hFindFile = FindFirstFile(strFindFileName, &wfd);
if (hFindFile == INVALID_HANDLE_VALUE)
{
return;
}
CUIString strFilePath;
CUIString strRelPath;
while (1)
{
if (wfd.cFileName[0] != '.')
{
strFilePath = lpszDir;
strFilePath += _T("log\\");
strRelPath = lpszDir;
strRelPath += _T("logback\\");
if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // 目录
{
continue;
}
else // 文件
{
strFilePath = strFilePath + wfd.cFileName;
strRelPath = strRelPath + wfd.cFileName;
CopyFile(strFilePath, strRelPath, FALSE);
}
}
if (!FindNextFile(hFindFile, &wfd))
{
break;
}
}
FindClose(hFindFile);
}
上述代码很简单,就是遍历log文件夹中的所有日志文件,然后调用系统API函数CopyFile将这些日志文件拷贝到临时目录logpack中。拷贝完成后,进行压缩打包,然后将压缩包上传到服务器上。
代码一目了然,并没有什么问题,但为啥拷贝文件时会非常的慢呢?拷贝文件是调用系统API函数CopyFile实现的,难道是软件在运行的过程中在持续向日志文件中写入内容,影响到了CopyFile函数的拷贝速度?我们手动从电脑磁盘上拷贝文件非常快,比代码拷贝要快很多。
3、能否将拷贝到临时目录的过程省略掉,直接对原始日志目录压缩呢?
既然将日志文件拷贝到临时目录的过程比较耗时,能否将这个过程省略掉,直接对原始日志目录进行压缩呢?
于是在软件运行的过程中打开日志目录,直接用鼠标右键手动对该日志目录进行压缩,结果就报错了,如下所示:
具体错误原因为:
读文件失败 (C:\Users\xxxxxx\AppData\Roaming\xxxxxx\log\.mnet0.log.lock) | 另一个程序已锁定文件的一部分,进程无法访问。 (33)
猜测可能是文件正在使用中,软件还在持续地向文件中写日志,文件处于占用状态,所以压缩时报错了。此外,文件如果在持续写入内容,对文件的压缩是不是也有影响。
所以,为了安全起见,将日志文件拷贝到临时目录的这一过程是不能省略的,问题还是回到如何优化文件拷贝慢的问题。
4、使用命令行命令copy替换掉对CopyFile的调用,有效地提升了文件拷贝的速度
目前调用系统API函数CopyFile实现日志文件拷贝,速度很慢,搞不清楚具体的原因。除了调用CopyFile拷贝文件,还有没有其他实现方式了呢?我们手动从磁盘中拷贝日志文件非常快,我们能否尝试通过执行命令行命令去拷贝文件呢?比如copy /y D:\XXX\log\*.* D:\XXX\logback,即使用copy命令将D:\XXX\log目录中所有的日志文件拷贝到D:\XXX\logback目录中。把这个命令放到cmd.exe命令行中执行了一下,非常快!
4.1、使用system函数去运行命令行命令
那如何在C++程序中执行命令行命令呢?以前使用过系统C函数system,如下所示:
system("copy /y D:\XXX\log\*.* D:\XXX\logback");
但调用这个system函数有个问题,会自动跳出cmd.exe命令行窗口,执行完后会自动关闭。在C++程序运行过程中突然弹出cmd窗口,显然是不可接收的,所以不能使用system函数。
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:【C++软件异常与异常排查从入门到精通系列教程】(该精品技术专栏的订阅量已达到10000多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,已经更新到200篇以上!欢迎订阅!)
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,详细介绍分析C++软件问题的常用分析工具,以图文并茂的方式给出具体的项目问题实战分析实例(详细讲述分析排查过程,很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:【C/C++实战进阶】(该专栏涵盖了C++多方面的内容,是当前重点打造的专栏,订阅量已达8000多个,专栏文章已经更新到500多篇,持续更新中...)
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(开源代码中可能会用到很多新特性(比如WebRTC开源库),日常编码中也会用到部分新特性,面试时也会频繁地涉及到,学习新特性很有必要)、常用C++开源库的介绍与使用(比如SQLite、libcurl、libwebsockets、libevent、jsoncpp/RapidJson、Redis、RabbitMQ、MongoDB、MQTT、ZooKeeper、OpenCV、FFmpeg、SDL、GStreamer、Live555、ReactOS等)、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(引发C++软件异常的常见原因分析与总结、排查C++软件异常的手段与方法、分析C++软件异常的基础知识、使用常用软件分析工具分析C++软件问题、多个项目实战问题分析案例分享等)、设计模式(单例模式、工厂模式、观察者模式、状态模式等)、网络基础知识与网络问题分析进阶内容(实战问题分析实例分享)等。本专栏的内容都是建立在项目实践的基础上,来源于项目实战,服务于项目实战,很有实战参考价值!
专栏3:【分析C++软件问题的实用软件与高效工具实战案例集锦】
常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro以及内存泄漏检测工具等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏4:【VC++常用功能代码封装】
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5:【C/C++软件开发从入门到实战】(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到300多篇,持续更新中!欢迎订阅!)
根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
4.2、使用CreateProcess创建一个cmd.exe进程去执行命令行命令,可以控制cmd.exe窗口的显示
可以调用CreateProcess创建一个cmd.exe进程去执行命令行命令,可以通过参数设置让启动起来的cmd.exe进程窗口隐藏起来:(将si.wShowWindow字段值设置为SW_HIDE)
// 设置启动参数
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW; // 使用wShowWindow字段
si.wShowWindow = SW_HIDE; // 让启动起来的cmd.exe进程窗口隐藏起来
TCHAR cmdLine[MAX_PATH * 2] = { 0 };
_stprintf(cmdLine, _T("cmd.exe /C \"copy /y D:\XXX\log\*.* D:\XXX\logback\""));
if (!CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi))
{
return;
}
但这里有个问题,CreateProcess只负责启动cmd.exe进程去执行命令行命令copy /y D:\XXX\log\*.* D:\XXX\logback,CreateProcess将cmd.exe进程启动起来后就返回了,返回不代表拷贝操作执行完了。但代码流程中要等待所有日志文件拷贝执行完之后,对日志文件进行压缩。
其实要等待CreateProcess启动起来的cmd.exe进程将copy命令执行完,很简单,调用WaitForSingleObject等待这个cmd.exe进程结束即可,可以设置无限等待参数值INFINITE:
WaitForSingleObject(pi.hProcess, INFINITE);
WaitForSingleObject不仅可以等待事件,还可以等待进程和线程,有很多妙用,可以查看我之前写的文章:
C++程序中 WaitForSingleObject 函数的诸多用途与使用场景总结https://blog.csdn.net/chenlycly/article/details/135604637 此外,在上述代码执行完后,要将进程和线程句柄关闭掉:
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
所以,实现日志文件拷贝的完整代码实现如下:
void CopyLogFiles(LPCTSTR lpszDir, LPCTSTR lpLogBackDir)
{
// 设置启动参数
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(STARTUPINFO);
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE; // 让启动起来的cmd.exe进程窗口隐藏起来
TCHAR cmdLine[MAX_PATH * 2] = { 0 };
_stprintf(cmdLine, _T("cmd.exe /C \"copy /y %s\\*.* %s\""), lpszDir, lpLogBackDir);
if (!CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi))
{
return;
}
// 等待cmd.exe进程将命令行命令执行完
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
将所有日志文件拷贝到logback临时目录后,就可以对临时目录压缩打包,然后发起对服务器的上传了。
最后,需要将临时目录logback删除掉,如果通过调用DeleteFile逐个删除,可能也会比较慢,也可以通过命令行命令RD /S /Q D:\XXX\logback,对应实现代码和上面完全一样,就不赘述了。
4.3、除了CreateProcess,还可以调用WinExec、ShellExecuteEx去实现
启动一个cmd.exe进程去执行命令行命令,除了调用CreateProcess,还可以调用WinExec、ShellExecuteEx去实现,具体实现方法,我就不展开了,可以到deepseek中自行搜索。
此外,日志文件拷贝到临时目录后,使用开源的zip.cpp进行压缩打包,关于如何使用开源的zip.cpp和unzip.cpp进行压缩与解压缩,可以查看我之前写的文章:
C++程序使用开源的zip.cpp和unzip.cpp去压缩和解压文件(附完整源码)https://blog.csdn.net/chenlycly/article/details/140298447C++ 开源 unzip.cpp解压zip包失败,企业微信自动生成的zip包格式不标准导致的
https://blog.csdn.net/chenlycly/article/details/141101152
5、最后
当我们开发过程中遇到问题瓶颈时,可以尝试其他的实现方式,可能会收到意想不到的效果。本案例中的问题虽小,但解决问题的思路和部分细节,还是有一定的参考价值的,所以整理成文章,分享给大家。