C++实现Base64编解码并应用于图片传输
前言
最近接觸的項目有一個小功能是在服務器(C++)和客戶端(Python)之間傳輸圖片,開始這部分是由另外一位同學完成的。但由于服務器是用C++寫的,他不是很熟悉,所以讓我來完成這部分功能。在項目中遇到了傳輸問題,還有文件輸入輸出的問題(又要復習了)。
?
Base64
Base64的定義(來源于百度百科):Base64是網絡上最常見的用于傳輸8Bit字節碼的編碼方式之一,Base64是一種基于64個可打印字符來表示二進制數據的方法。Base64編碼是從二進制到字符的過程,可用于在HTTP環境下傳遞較長的標識信息。例如,在Java Persistence系統Hibernate中,就采用了Base64來將一個較長的唯一標識符(一般為128-bit的UUID)編碼為一個字符串,用作HTTP表單和HTTP GET URL中的參數。在其他應用程序中,也常常需要把二進制數據編碼為適合放在URL(包括隱藏表單域)中的形式。此時,采用Base64編碼具有不可讀性,需要解碼后才能閱讀。Base64由于以上優點被廣泛應用于計算機的各個領域,然而由于輸出內容中包括兩個以上“符號類”字符(+, /, =),不同的應用場景又分別研制了Base64的各種“變種”。其原理是選出64個字符——小寫字母a-z、大寫字母A-Z、數字0-9、符號”+”、”/”(再加上作為墊字的”=”,實際上是65個字符)——作為一個基本字符集。然后,其他所有符號都轉換成這個字符集中的字符。
在完成這個項目的時候我也查閱了很多資料,也在CSDN上看了幾篇文章。看到別人也做過用Base64對圖片進行編解碼。使用博主提供的代碼并沒有完成相應的功能,之后自己復習了C/C++的文件輸入輸出才找到了問題。不多說了,下面先將代碼附上,再說遇到的問題。這里我就不寫成服務器的代碼了,相當于一個小Demo,只是為了展示編解碼的功能。
?
C++實現Base64
代碼如下:
#include <iostream> #include <string> #include <cstring> #include <fstream> #include <malloc.h> using namespace std;static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/";static inline bool is_base64(const char c) {return (isalnum(c) || (c == '+') || (c == '/')); }std::string base64_encode(const char * bytes_to_encode, unsigned int in_len) {std::string ret;int i = 0;int j = 0;unsigned char char_array_3[3];unsigned char char_array_4[4];while (in_len--){char_array_3[i++] = *(bytes_to_encode++);if(i == 3){char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);char_array_4[3] = char_array_3[2] & 0x3f;for(i = 0; (i <4) ; i++){ret += base64_chars[char_array_4[i]];}i = 0;}}if(i){for(j = i; j < 3; j++){char_array_3[j] = '\0';}char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);char_array_4[3] = char_array_3[2] & 0x3f;for(j = 0; (j < i + 1); j++){ret += base64_chars[char_array_4[j]];}while((i++ < 3)){ret += '=';}}return ret; }std::string base64_decode(std::string const & encoded_string) {int in_len = (int) encoded_string.size();int i = 0;int j = 0;int in_ = 0;unsigned char char_array_4[4], char_array_3[3];std::string ret;while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {char_array_4[i++] = encoded_string[in_]; in_++;if (i ==4) {for (i = 0; i <4; i++)char_array_4[i] = base64_chars.find(char_array_4[i]);char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];for (i = 0; (i < 3); i++)ret += char_array_3[i];i = 0;}}if (i) {for (j = i; j <4; j++)char_array_4[j] = 0;for (j = 0; j <4; j++)char_array_4[j] = base64_chars.find(char_array_4[j]);char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];for (j = 0; (j < i - 1); j++) ret += char_array_3[j];}return ret; }int main(int argc, char** argv){fstream f;f.open("test.jpg", ios::in|ios::binary);f.seekg(0, std::ios_base::end); //設置偏移量至文件結尾std::streampos sp = f.tellg(); //獲取文件大小int size = sp;char* buffer = (char*)malloc(sizeof(char)*size);f.seekg(0, std::ios_base::beg); //設置偏移量至文件開頭f.read(buffer,size); //將文件內容讀入buffercout << "file size:" << size << endl;string imgBase64 = base64_encode(buffer, size); //編碼cout << "img base64 encode size:" << imgBase64.size() << endl;string imgdecode64 = base64_decode(imgBase64); //解碼cout << "img decode size:" << imgdecode64.size() << endl;const char *p = imgdecode64.c_str();std::ofstream fout("D:/result.jpg", ios::out|ios::binary);if (!fout){cout << "error" << endl;}else{cout << "Success!" << endl;fout.write(p, size);}fout.close();return 0; }使用上面代碼,可以完成編解碼的功能,但當我把它放到項目中去時,圖片總是無法打開。最后找到了問題出在f.seekg(0, std::ios_base::end);。源代碼為了得到圖片文件的大小,將偏移量設置到了文件的結尾,而在將文件讀入buffer時,沒有將其設置到文件開始,導致最終讀取的數據是錯誤的。解決的辦法就是在讀取數據前設置f.seekg(0, std::ios_base::beg);。
?
C++ I/O系統管理兩個與一個文件相聯系的指針。一個是讀指針,它說明輸入操作在文件中的位置;另一個是寫指針,它下次寫操作的位置。每次執行輸入或輸出時,相應的指針自動變化。所以,C++的文件定位分為讀位置和寫位置的定位,對應的成員函數是 seekg()和 seekp(),seekg()是設置讀位置,seekp是設置寫位置。它們最通用的形式如下:
istream?&seekg(streamoff?offset,seek_dir?origin);?? ostream?&seekp(streamoff?offset,seek_dir?origin);??streamoff定義于 iostream.h 中,定義有偏移量 offset 所能取得的最大值,seek_dir 表示移動的基準位置,是一個有以下值的枚舉:
ios::beg: ?文件開頭 ios::cur: ?文件當前位置 ios::end: ?文件結尾file1.seekg(1234,ios::cur);//把文件的讀指針從當前位置向后移1234個字節?? file2.seekp(1234,ios::beg);//把文件的寫指針從文件開頭向后移1234個字節?上述代碼解碼后得到的是字符串,所以我就直接fout << imgdecode64<< endl; 將數據寫到文件中了。結果圖片是能顯示的,但比原本圖片大1字節(‘\0’)。為了程序的正確性,這才改用fout.write(p, size);
輸入/輸出流總結可以看:C++學習筆記:(九)輸入/輸出流
當時沒有發現是f.seekg(0, std::ios_base::end);的問題,我一度懷疑是編解碼代碼出錯了,所以我改用C語言函數來讀寫圖片編解碼數據進行驗證。
用C語言獲取文件大小的方式:
int file_size(char* filename) {FILE *fp=fopen(filename, "r");if(!fp) return -1;fseek(fp,0L,SEEK_END);int size=ftell(fp);fclose(fp);return size; }上述方法利用fseek移動一個文件的存取位置到文件的末尾,然后利用ftell獲得目前的文件訪問位置。這種方法可以認為是一種間接的獲取方式。雖說可以獲得文件大小,但是有兩個缺點。首先,ftell的返回值為long,在不同環境下占用的字節數也不同,這就可能存在long是四個字節的情況。此時,獲取的文件大小就不能超過2G,否則就會出錯。
但是,上述缺點在大多數情況下都沒問題,超大文件還可以通過fsetpos和fgetpos獲取文件大小。最致命的缺陷就是它需要加載文件到內存,然后跳轉到文件末尾,這個操作非常耗時!可能在讀取少量文件時體現不出,但是當文件達到上萬個時,速度就會慢的要命,這種方法相當于把所有的文件都讀到內存中一遍!
如果可能,盡量避免采用上述間接的方式獲取文件大小。在linux下,還有一種更簡單的方式,通過讀取文件信息獲得文件大小,速度也快很多。代碼如下:
#include <sys/stat.h> int file_size2(char* filename) {struct stat statbuf;stat(filename,&statbuf);int size=statbuf.st_size;return size; }這種方式首先獲得相關文件的狀態信息,然后從狀態信息中讀取大小信息。由于沒有讀取文件的操作,所以操作速度非常快。強烈建議大家在linux下使用這種方式。
?
獲取了文件的大小后,就可以使用fwrite()和fread()函數進行讀寫操作。
fwrite()原型:
size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);fwrite()函數將二進制數據寫入文件。size_t類型是根據標準C類型定義的。它是sizeof運算符返回的類型,通常是unsigned int類型,不過具體的實現中可以選擇其他類型。指針ptr是要寫入的數據塊的地址。Size表示要寫入的數據塊大小(以字節為單位)。Nmemb表示數據塊的數目。像一般函數一樣,fp指定要寫入的文件。
例如,要保存一個256字節大小的數據對象(如一個數組),可以這樣做: char buffer[256]; fwrite(buffer, 256, 1, fp); //這一調用將一塊256字節大小的數據塊從緩沖區寫入到文件。要保存一個包含10個double值的數組,可以這樣做: double earings[10]; fwrite(earings, sizeof(double), 10, fp);//這一調用將earings數組中的數據寫入文件,數據分成10塊,每塊都是double大小。fwrite()函數返回成功寫入的數目,正常情況下,它與nmemb相等。fread()原型:
size_t fread(void restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);fread()函數與fwrite()函數的參數相同。這時,ptr為讀入文件數據的內存存儲地址,fp指定要讀取的文件。例如:
double earings[10]; fread(earings, sizeof(double), 10, fp); //該調用將10個double值復制到earings數組中。 fread()函數返回成功讀入的項目數,正常情況下,它與nmemb相等。?
使用C語言fread()函數與fwrite()函數讀寫圖片文件,然后編解碼,保存的圖片就能顯示出來了。在確定編解碼代碼沒有問題后,才開始細看上述代碼中打開文件和讀寫文件的操作,最終找出了問題所在。以下代碼是Base64的另一種實現方法,也是我嘗試和驗證過的方法。作為對C/C++文件操作的小總結,下面代碼中使用多種方式對文件進行操作。
#include <iostream> #include <string> #include <cstring> #include <fstream> #include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <sys/stat.h>using namespace std; /*** Base64 編碼/解碼*/ class Base64{ private:std::string _base64_table;static const char base64_pad = '='; public:Base64(){_base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; /*這是Base64編碼使用的標準字典*/}/*** 這里必須是unsigned類型,否則編碼中文的時候出錯*/std::string Encode(const unsigned char * str,int bytes);std::string Decode(const char *str,int bytes);void Debug(bool open = true); };std::string Base64::Encode(const unsigned char * str,int bytes) {int num = 0,bin = 0,i;std::string _encode_result;const unsigned char * current;current = str;while(bytes > 2) {_encode_result += _base64_table[current[0] >> 2];_encode_result += _base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];_encode_result += _base64_table[((current[1] & 0x0f) << 2) + (current[2] >> 6)];_encode_result += _base64_table[current[2] & 0x3f];current += 3;bytes -= 3;}if(bytes > 0){_encode_result += _base64_table[current[0] >> 2];if(bytes%3 == 1) {_encode_result += _base64_table[(current[0] & 0x03) << 4];_encode_result += "==";} else if(bytes%3 == 2) {_encode_result += _base64_table[((current[0] & 0x03) << 4) + (current[1] >> 4)];_encode_result += _base64_table[(current[1] & 0x0f) << 2];_encode_result += "=";}}return _encode_result; } std::string Base64::Decode(const char *str,int length) {//解碼表const char DecodeTable[] ={-2, -2, -2, -2, -2, -2, -2, -2, -2, -1, -1, -2, -2, -1, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-1, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, 62, -2, -2, -2, 63,52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -2, -2, -2, -2, -2, -2,-2, 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, -2, -2, -2, -2, -2,-2, 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, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2,-2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2, -2};int bin = 0,i=0,pos=0;std::string _decode_result;const char *current = str;char ch;while( (ch = *current++) != '\0' && length-- > 0 ){if (ch == base64_pad) { // 當前一個字符是“=”號/*先說明一個概念:在解碼時,4個字符為一組進行一輪字符匹配。兩個條件:1、如果某一輪匹配的第二個是“=”且第三個字符不是“=”,說明這個帶解析字符串不合法,直接返回空2、如果當前“=”不是第二個字符,且后面的字符只包含空白符,則說明這個這個條件合法,可以繼續。*/if (*current != '=' && (i % 4) == 1) {return NULL;}continue;}ch = DecodeTable[ch];//這個很重要,用來過濾所有不合法的字符if (ch < 0 ) { /* a space or some other separator character, we simply skip over */continue;}switch(i % 4){case 0:bin = ch << 2;break;case 1:bin |= ch >> 4;_decode_result += bin;bin = ( ch & 0x0f ) << 4;break;case 2:bin |= ch >> 2;_decode_result += bin;bin = ( ch & 0x03 ) << 6;break;case 3:bin |= ch;_decode_result += bin;break;}i++;}return _decode_result; }int file_size(char* filename) {FILE *fp=fopen(filename, "r");if(!fp) return -1;fseek(fp,0L,SEEK_END);int size=ftell(fp);fclose(fp);return size; }int file_size2(char* filename) {struct stat statbuf;stat(filename,&statbuf);int size=statbuf.st_size;return size; }int main() {string normal,normaltest,encoded,encodedtest;int i,len,datalen;FILE *fa, *fb;Base64 *base = new Base64();ifstream f;//使用兩種方法打開文件f.open("test.jpg", ios_base::in | ios_base::binary);if( (fa = fopen("test.jpg", "ab+")) == NULL ){cout << "Error!" <<endl;}//使用三種方法獲取文件大小int asd_file_size1 = file_size((char*)"test.jpg");cout << "File_size1:" << asd_file_size1 <<endl;int asd_file_size2 = file_size2((char*)"test.jpg");cout << "File_size2:" << asd_file_size2 <<endl;f.seekg(0, std::ios_base::end);std::streampos sp = f.tellg();int size_of_file = sp;cout << "File_size3:" << size_of_file << endl;unsigned char buffer1[15000];char buffer2[15000];//讀取文件到buffer內f.seekg(0, std::ios_base::beg); //讀之前先將偏移量設置到文件開頭f.read((char*)buffer1, size_of_file);fread(buffer2, sizeof(char), asd_file_size1, fa);fclose(fa);const unsigned char* b, *c;//因為這個方法編碼要求const unsigned char*,所以要將buffer轉成相應的類型b = reinterpret_cast<const unsigned char*>(buffer1);c = reinterpret_cast<const unsigned char*>(buffer2);//編碼encoded = base->Encode(b, size_of_file);encodedtest = base->Encode(c, size_of_file);cout << "Encode_Len:" << encoded.length() <<endl;cout << "Encodetest_Len:" << encodedtest.length() <<endl;//參數要求,轉換成const char*后解碼const char * str1 = encoded.c_str();const char * str2 = encodedtest.c_str();normal = base->Decode(str1, strlen(str2));normaltest = base->Decode(str2, strlen(str2));cout << "Decode_Len:" << normal.length() <<endl;cout << "Decodetest_Len:" << normaltest.length() <<endl;const char *qwe = normal.c_str();const char *asd = normaltest.c_str();ofstream foutasd("D:/teststringasd.jpg", ios_base::out|ios_base::binary);if (!foutasd){cout << "error" << endl;}else{cout << "Success!" << endl;foutasd.write(qwe, size_of_file);}foutasd.close();fb = fopen("D:/teststring.jpg", "wb+");fwrite(asd , size_of_file, 1, fb);fclose(fb);return 0; }參考:https://www.cnblogs.com/lrxing/p/5535601.html
https://blog.csdn.net/yutianzuijin/article/details/27205121
https://blog.csdn.net/m0_37263637/article/details/79559097
總結
以上是生活随笔為你收集整理的C++实现Base64编解码并应用于图片传输的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C/C++运算符
- 下一篇: Bigtable数据模型和架构