文章目录
一、为什么做这个项目?(项目背景)
在计算机中一个字节共有256种,即ascii码表,而ascii码的128~255之间的值是不可见字符,对于一些只支持可见字符的协议,比如邮件传输协议(SMTP)只支持可见的ASCII字符的传递,如果要传输二进制文件,比如:图片、视频是无法实现的,因此就有了Base64编码格式,Base64编码格式对于所有二进制格式的数据,都可以转化为可显示的字符。
- 在网络上交换数据时,比如说从A地传递到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,不利于传输。
- http先以当中的key-value字段,必须进行url编码,不然出现的等号或者空格可能是解析失败
- 有些文本协议不支持可见字符传输,比如简单邮件传输协议(SMTP)
- 网页中内嵌简单图片
二、这个项目能实现怎么样的功能?(项目目标)
实现一个在线的base64转换工具,支持文本的base64可逆转化以及图片 base64转换。
三、项目用什么框架来实现的?(项目框架)
四、具体怎么实现的?(具体实现)
1.base64算法+实现
2.简单的前端知识:html + CSS + javascript + ajax
3.页面搭建
4.搭建http服务器 ---->使用httplib库:只需要包含头文件
5.前后端调通
1.编码/解码算法模块
base64编码之所以叫base64,是因为其使用64个字符来对二进制数据进行编码
A~Z,a~z,0~9,+ / 一共64个
从0到63:
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
= 用来不够补位
1.1编码过程
对于用户给的二进制比特流,三个三个字节一组进行编码
3个字节 -------->base64 --------->4个可以显示的字节
24个比特位---->base64 ---------->将24个比特位均分为4组,每组6个比特位------>转为为4个字节
举个例子
字符串长度被3整除时
使用base64编码:man
字符串长度不能被3整除时
使用base64编码:manm
当需要编码的字节数不能被3整除时,最后会多出一个或者两个字节,先用0字节值在末尾补足,使其能够被3整除,然后进行base64编码,最后在编码结果后补上一个或者两个=号,代表补足的字节数。
比如传4个字节,这时多出一个字节,就要补两个字节的0(16个位的0),然后再编码,最后编码结果后补两个 = 符号。
注意:base64一行最多显示76个可显字符
缺陷:base64将三个字节转化成四个字节,因此Base64编码后的文本,会比原文本大出三分之一左右
编码详细思路
1.对字符按照三个三个进行分组:ch1、ch2、ch2
2.关键操作
能被3整除的字节数:
ch1:直接取其高6位-----> ch1>>2 ------>比如M(01001101) 右移两位------>00010011 ------>转换十进制19 ----->查base64表:T
将ch1中末尾还剩余的两位,与ch2中高4四位进行拼接,拼接成6个比特位-------> ( ch1 << 4 | ch2 >> 4) & 0x3F,和0x3F按位与后相当于清零前两位,即在前两位补0
M(01001101) 左移4位后--------->11010000
a(01100001)右移4位后----------->00000110
| 拼接后 11010110
& 0x3F后 00010110 --------转换十进制22 ----->查base64表:W
ch2:还剩低4位,与ch3的高2位来进行拼接,拼接成6个比特位:(ch2 << 2 | ch3 >> 6) &0x3F
a(01100001)左移2位后----------->10000100
n(01101110)右移6位后-----------> 00000001
拼接后10000101
& 0x3F后 00000101--------转换十进制5 ----->查base64表:F
ch3:还剩低6位,成为一组,前面补两个0:(ch3 & 0x3F)
n(01101110) & 0x3F-----------> 00101110--------转换十进制46 ----->查base64表:u
最终得到base64编码:TWFu
不能被3整除的字节数:
补需要字节数的0,比如4个字节缺两个字节被3整除,则补2个字节的0。有多出一个字节的情况,也有多出两个字节的情况,基本操作和上述思路一致
编码模块代码
//编码模块
std::string Base64::Encode(const std::string& strData)//编码模块
{
std::string strEncode;
std::string strEncodeTable("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"); //Base64编码表
unsigned char temp[4];
size_t index = 0;
size_t lineLength = 0;//记录base64每行的字节数
for (size_t i = 0; i < strData.size()/3; ++i)
{
//3个字符为一组进行base64编码转换
temp[1] = strData[index++];
temp[2] = strData[index++];
temp[3] = strData[index++];
//转换后每个字节都由 补位的00 + 拼接后的6位 组成
//取第一个字节的高6位
strEncode += strEncodeTable[temp[1] >> 2];
//取第一个字节的低2位 + 第二字节的高四位拼接
strEncode += strEncodeTable[((temp[1] << 4) | (temp[2] >> 4)) & 0x3F];
//取第二个字节的低4位 + 第三个字节的高2位拼接
strEncode += strEncodeTable[((temp[2] << 2) | (temp[3] >> 6)) & 0x3F];
//取第三个字节的低6位
strEncode += strEncodeTable[temp[3] & 0x3F];
lineLength += 4;
if (lineLength == 76)//base64一行只能显示76的字节
{
strEncode += "\r\n";
lineLength = 0;
}
}
size_t mod = strData.size() % 3;//查看传入数据字节数多出3的倍数几个字节
if (mod == 1)//如果多出3的倍数一个字节,补两个 = 符号
{
temp[1] = strData[index++];
//取多出来的这个字节的高6位
strEncode += strEncodeTable[temp[1] >> 2];
//取多出来的这个字节的低2位 + 补4位的0进行拼接
strEncode += strEncodeTable[(temp[1] & 0x03) << 4];
//最后补两个 = 符号
strEncode += "==";
}
else if (mod == 2)//如果多出3的倍数两个字节,补一个 = 符号
{
temp[1] = strData[index++];
temp[2] = strData[index++];
//取多出的第一个字节的高6位
strEncode += strEncodeTable[temp[1] >> 2];
//取多出的第一个字节的低2位 + 多出的第二个字节的高4位拼接
strEncode += strEncodeTable[((temp[1] << 4) | (temp[2] >> 4)) & 0x3F];
//取多出的第二个字节的低4位 + 补2位的0进行拼接
strEncode += strEncodeTable[(temp[2] & 0x0F) << 2];
//最后补一个 = 符号
strEncode += "=";
}
return strEncode;
}
1.2解码过程
1.获取base64编码后的每个字符ch
2.获取ch在编码表中的下标
3.该下标就是原字符对应的6个比特位
解析出4组之后,就形成了24个比特位,然后将24个比特位划分为3个字节
解码过程就是把编码逆过来,以aGVsbG8=为例子,四个字节为一组进行解码,前四个解码过程如图,最终全部解码为hello
//快速解码表
const char DecodeTable[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
62, // '+'
0, 0, 0,
63, // '/'
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'
0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z'
0, 0, 0, 0, 0, 0,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z'
};
解码详细思路
比如将 aGVsbG8= 进行解码
int nvalue = 0 nvalue: 00000000 00000000 00000000 00000000
四个一组进行解码aGVs:
DecodeTable[‘a’] ---->DecodeTable[97] = 26
-------->26(011010)存储到一个32位int型的第18~23比特的位置:(26 << 18) | nvalue-------> 00000000 01101000 00000000 00000000
DecodeTable[‘G’] ---->DecodeTable[71] = 6 -------->6(000110)
-------->6(000110)存储到一个32位int型的第12~17比特的位置:(6 << 12) | nvalue-------> 00000000 01101000 01100000 00000000
DecodeTable[‘V’] ---->DecodeTable[86] = 21 -------->21(010101)
-------->21(010101)存储到一个32位int型的第06~11比特的位置:(21 << 6) | nvalue-------> 00000000 01101000 01100101 01000000
DecodeTable[‘s’] ---->DecodeTable[115] = 44 -------->44(101100)
-------->44(101100)存储到一个32位int型的第00~05比特的位置:(44) | nvalue-------> 00000000 01101000 01100101 01101100
然后将nvalue从第23比特位开始八位八位读出共取三个字节就得到了解码后的数据:hel
00000000 01101000 01100101 01101100 ------->h(01101000)e(01100101)l(01101100)
bG8=同上解码过程,不同的是在解码时遇到 ‘=’ 就结束,解码结果:lo
aGVsbG8= 解码结果为:hello
编码模块代码
//解码模块
std::string Base64::Decode(const std::string& strData)//解码模块
{
std::string strDecode;
//快速解码表
const char DecodeTable[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
62, // '+'
0, 0, 0,
63, // '/'
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'
0, 0, 0, 0, 0, 0, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z'
0, 0, 0, 0, 0, 0,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z'
};
size_t value = 0; //保存解码的4组6个比特位(总共24个比特位)
size_t index = 0;
while (index < strData.size())
{
//编码时一行只能放置76个字符,超过76个字符会放到下一行
if (strData[index] != '\r' && strData[index + 1] != '\n')//说明一行还没解码完毕
{
//解析第一个编码
value = DecodeTable[strData[index++]] << 18;
//解析第二个编码
value = (DecodeTable[strData[index++]] << 12) | value;
strDecode += ((value >> 16) & 0xFF);//存储第一个解码后的数据16-23位
if (strData[index] != '=')
{
//解析第三个编码
value = (DecodeTable[strData[index++]] << 6) | value;
strDecode += ((value >> 8) & 0xFF);//存储第二个解码后的数据08-15位
if (strData[index] != '=')
{
//解析第四个编码
value = (DecodeTable[strData[index++]]) | value;
strDecode += (value & 0xFF);//存储第三个解码后的数据00-07位
}
}
else
{
break;
}
}
else
{
//解码到该行的末尾了
index += 2; //跳过 \r\n
}
}
return strDecode;
}
(未完待续)