当前位置:C++技术网 > 精选软件 > C++文件加密解密功能的实现过程详细分析:1 打开文件和读取文件

C++文件加密解密功能的实现过程详细分析:1 打开文件和读取文件

更新时间:2016-06-19 23:08:34浏览次数:1+次

    我们实现的文件加密解密功能可以对任何文件进行加密,然后也可以解密。这里重点不是加密,而是讲解整个实现过程。这是一个解决问题的过程,属于开发经验,而不是一个技术知识。我们这里介绍的加密和解密是极其简单的,马上你就知道了。
    我这里操作文件,使用的是Windows的API函数,而不是C语言的操作函数。一个目的就是为后面讲内存映射文件技术做准备。对于内存映射文件技术的总结,是朋友【诡道】问我后,我曾经做过写过这方面的代码,然而却没有总结成文章,一下子也没有办法给他看。所以很是可惜。所以也因此总结一下。贸然进击内存文件映射有点唐突,所以循序渐进,先从文件读写讲起,然后开始分析其中的道道。让你知道为什么要用内存文件映射,这样你会更加容易接受和理解内存映射文件技术的好处。
    那么就即兴想起了可以读取文件,然后对文件进行加密,这样的代码也有一定的实用性。大家可以将本次讲解的代码写成一个加密解密的小软件,核心代码也就是本文讲的内容,你的软件就只需要包装一下,所以还是很简单的。
    不管是用C语言的文件操作函数还是API函数,效果都是一样的,Windows上的C语言的文件操作函数,最终还是会转换成Windows的API函数。要从效率来讲,自然API函数更高。不过,这不是本次讲解使用Windows的文件操作API函数的原因。因为后面讲解的内存映射文件技术会用到API级别的文件操作函数,也就是CreateFile,所以就这样了。
    因为我们是以实现加密解密这个功能来分析的,所以重点是讲解整个实现过程思路分析,所以基本语法和函数使用就简单说一下,自己可以私下查询MSDN或者C++技术网上的相关说明文章。
    总体的实现思路是这样的:我们先打开一个文件,然后对文件数据进行操作,也就是加密过程,然后将操作过的数据写回文件,最后关闭文件。这样也就完成了一个加密解密功能过程。实际上,这个过程是文件操作的一个公共思路,并不特定用于加密解密哦。
    有了这个基本思路,你是不是感觉似乎和没有讲似的。基本思路是必须有的。我们需要遵从思维的发展走向来说。如果你能够想到这个基本思路,说明你还有点经验。很多新手压根就不知道从何下手。所以在思考问题的时候,不要急于一下子将所有的详细思路都想出来,这对于一个熟手来说,也是不太现实的。但是基本思路是好说的。
    有了这个基本思路,我们就一步步来细化,然后就慢慢让每一步实现过程具体起来,然后就可以实际应用了。如果你一开始一点思路也没有,不要急于动手写代码,大概想想基本思路,然后才能动手。其实编程最复杂的也就是具体的细节了。基本思路,写过代码的基本也都能够想到,除非是比较巧妙的思路。
    好了,那么我们开始分析实现第一步,即打开文件。打开文件我们就用CreateFile函数。打开文件我们需要考虑到文件打开失败的情况,所以打开文件后要检查是否成功,失败了的话,就不能往后执行了。所以第一步搞定,代码如下:
HANDLE hFile = CreateFile(L"D:/科洛弗道10号.rmvb", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE ,NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile == INVALID_HANDLE_VALUE)return;
    因为我们是加密文件,所以我们默认文件是存在的,所以在第五个参数中传入OPEN_ALWAYS标志,表示始终打开文件,而不是创建一个新文件。其他参数根据英文名称你也知道什么意思了,我也就不解释了,不懂使用,看MSDN。
    第二步就是读取文件。我们用ReadFile函数。那么问题就来了。ReadFile函数要指定一个缓存地址和要读取数据的大小。那么缓存从哪来呢?要多大的缓存呢?文件大小如何获取呢?是一次性读取文件所有数据还是分多次读取呢?如果要多次读取,如何实现呢?
    我想,这些问题是你做到这一步会想到的,而且很多人在这里就懵逼了。因为问题比较多,理不清头绪了。如果是这样,你将这些问题写到纸上,然后一条条找到解决办法,然后再综合汇总起来。写在纸上是防止在脑中混乱和重复思考,用来提高思考效率的。可能你一次尝试并不能考虑到所有问题,也可能一下子找不到最好的办法,不过不用着急,先从能想到的办法着手解决,慢慢也就发现更多问题,然后再思考,慢慢就能找到最好的办法。最好的办法,通常不是一蹴而就的,是经过大量的筛选得来,所以几次失败不要沮丧哦。
    我们来一个个探讨。缓存从哪来?一个是使用自动数组(如int arr[100]的arr),一种是使用动态创建的数组。如果使用自动数组,内存分配是在线程栈里。而线程栈里的空间是不够大的,通常线程创建的时候会传入一个栈大小。也就是说,不管怎么样,栈的大小总是有一个上限的,而且一般线程栈不会太大的。所以,使用自动数组的话,能分配的内存大小还是受到了限制。如果你想创建一个500MB的数组,你可以试试效果,应该会崩溃的。这个会超过了线程栈的大小,无法满足要求,因此会失败。然而动态创建的数组则是在堆中创建,如果堆中不够了,会想系统申请增加,直到物理没有可用内存为止。所以,基本上除了物理限制外,其他方面没有什么问题。
    所以,选择动态创建数组来创造缓冲区是一个不错的选择。这样你可以让缓冲区变得很大,比如100MB甚至是500MB。如果要操作的文件很大,那么缓冲区自然也要变大。否则要分很多次很多次才能操作完一个文件,来回读取文件会大大降低程序的效率。
    缓存是确定了,使用动态创建的数组,因为对文件的操作一般是以二进制进行处理的,也是按照字节处理的,所以我们使用char数组就好了。而缓存区到底确定为多大合适?我们先来讨论下,是一次性将文件所有数据读入内存还是分多次?其实对于这个问题,还是好说的。如果是一次将文件内容读入内存,那么缓冲区就必须是文件大小那样大。如果是多次,则可以先将文件分成多个部分,然后一段一段的读入处理,这样缓冲区大小就只需要和一段的大小一样即可。
    如果将文件一次性读入内存,有一个问题,那就是,如果文件本身非常大,比如5GB,那么在内存中要创建的缓冲区就要是5GB,而32位系统最大支持的内存也就只有4GB,这个就无法实现了。如果文件比较小,如100KB,可以接受,如果是500MB这样的,还是可以勉强接受的,因为这样会让你的程序占用的内存一下子飙到500MB,内存占用这么高,电脑都可能变得卡顿了。如果你再提高缓存区大小,比如提到1GB的样子,那卡顿更加厉害了。虽然堆是可以给你这么多内存,然而这些内存都被你占用了,系统中运行的很多程序就没有足够内存了,所以要进行大量的内存交换,将暂时不用的数据放到硬盘中,频繁的交换,而且是大量程序都频繁的交换,电脑不卡顿才怪。
    对于大文件的读写,一次性读取到内存是不可取的。然而如何来确定一次性读入还是分多次呢?这就由缓冲区大小决定了。如果文件大小小于你确定的缓冲区的大小,那么一次读入就可以了。当然你的缓冲区不要太大了。如果超过缓冲区的大小,分多次就好了。而缓存的大小,这个没有固定的说法,如果文件很大,可以定得大些,而且缓冲区大小你可以根据情况来决定大小。反正后面的代码都是一样的,只是决定了读取文件的次数而已。
    那么文件大小如何确定呢?这个简单,API函数GetFileSizeEx可以帮你搞定。如果是GetFileSize函数,返回值是文件大小的低32位,第二个参数返回的是高32位,合起来的64位是最终的文件大小。32位支持4GB文件大小,实际上我们的文件可以超过4GB,所以32位无法完成文件大小记录的职责,而使用64位,可以支持很大很大的单个文件哦,你自己可以去计算一下,2的64次方字节。因为GetFileSize的参数处理起来很麻烦,你还要去拼接文件大小的高低32位,所以就选用GetFileSizeEx,第二个参数直接返回的是64位的大整数。数据类型是LARGE_INTEGER结构体。对于LARGE_INTEGER结构体的理解,请参考《LARGE_INTEGER结构体使用详解》。我们使用结构体的QuadPart成员就可以存储64位的值了。
    那么最后一个问题,就是如何分多次来读取文件?开始,我是用文件大小除以缓冲区的大小得到文件分段数,来确定读取多少次。这是一个解决办法。这样我们知道明确的读取文件的次数。还有另外一种方法,就是根据已读的数据量和文件总数据量比对,如果小于文件总大小,就继续循环读取。你也可以用读取剩余量是否为0来判断,或者你还可以用文件指针来判断,如果到了文件尾,也就可以结束循环读取了。这里方法很多,发散思维的去想,你会想到很多办法。不过对于每一种方法,你都要有一个完整的实现思路,不同的方法实现思路是不一样的。有的可能无法在此场景使用,有的可能可以。
    因为篇幅问题,所以先就此打住,后面再继续分析。这里留出来的几种方法,你都可以去尝试下。当然,方法不限于此。最后告诉你获取文件大小的代码:
LARGE_INTEGER FileSize;
if(!GetFileSizeEx(hFile,&FileSize))
{
    MessageBox(NULL,L"获取文件大小出错",L"出错",0);
    return;
}