【原創性聲明】:本文無實質性創新性內容,性質屬於技術總結,內容是基於已有知識或定義的代碼實現。文中的代碼是我根據其他代碼或者網絡上的資料,寫出的自己的版本。因為網絡上的代碼 C/C++ 版本的較少,或者本身不一定是最合適最容易使用的。所以我寫的代碼基本是以 C 語言和 C 字符串處理為主的,因此它也可以直接在 C++ 的項目中使用。
(1)Base64 編碼。Base64 編碼是把每 3 個字節轉換成 4 個ascii 字符(根據字符表映射)。把文本編碼后,對人來說難以直接閱讀。結尾不足時可能有一到兩個 "=" 字符的補齊。

//以下是 Base64.h 的內容: size_t Base64_Decode(char *pDest, const char *pSrc, size_t srclen); size_t Base64_Encode(char *pDest, const char *pSrc, size_t srclen); //以下是 Base64.cpp 的內容: BYTE Decode_GetByte(char c); char Encode_GetChar(BYTE num); //=================================== // Base64 解碼 //=================================== BYTE Decode_GetByte(char c) { if(c == '+') return 62; else if(c == '/') return 63; else if(c <= '9') return (BYTE)(c - '0' + 52); else if(c == '=') return 64; else if(c <= 'Z') return (BYTE)(c - 'A'); else if(c <= 'z') return (BYTE)(c - 'a' + 26); return 64; } //解碼 size_t Base64_Decode(char *pDest, const char *pSrc, size_t srclen) { BYTE input[4]; size_t i, index = 0; for(i = 0; i < srclen; i += 4) { //byte[0] input[0] = Decode_GetByte(pSrc[i]); input[1] = Decode_GetByte(pSrc[i + 1]); pDest[index++] = (input[0] << 2) + (input[1] >> 4); //byte[1] if(pSrc[i + 2] != '=') { input[2] = Decode_GetByte(pSrc[i + 2]); pDest[index++] = ((input[1] & 0x0f) << 4) + (input[2] >> 2); } //byte[2] if(pSrc[i + 3] != '=') { input[3] = Decode_GetByte(pSrc[i + 3]); pDest[index++] = ((input[2] & 0x03) << 6) + (input[3]); } } //null-terminator pDest[index] = 0; return index; } //=================================== // Base64 編碼 //=================================== char Encode_GetChar(BYTE num) { return "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/="[num]; } //編碼 size_t Base64_Encode(char *pDest, const char *pSrc, size_t srclen) { BYTE input[3], output[4]; size_t i, index_src = 0, index_dest = 0; for(i = 0; i < srclen; i += 3) { //char [0] input[0] = pSrc[index_src++]; output[0] = (BYTE)(input[0] >> 2); pDest[index_dest++] = Encode_GetChar(output[0]); //char [1] if(index_src < srclen) { input[1] = pSrc[index_src++]; output[1] = (BYTE)(((input[0] & 0x03) << 4) + (input[1] >> 4)); pDest[index_dest++] = Encode_GetChar(output[1]); } else { output[1] = (BYTE)((input[0] & 0x03) << 4); pDest[index_dest++] = Encode_GetChar(output[1]); pDest[index_dest++] = '='; pDest[index_dest++] = '='; break; } //char [2] if(index_src < srclen) { input[2] = pSrc[index_src++]; output[2] = (BYTE)(((input[1] & 0x0f) << 2) + (input[2] >> 6)); pDest[index_dest++] = Encode_GetChar(output[2]); } else { output[2] = (BYTE)((input[1] & 0x0f) << 2); pDest[index_dest++] = Encode_GetChar(output[2]); pDest[index_dest++] = '='; break; } //char [3] output[3] = (BYTE)(input[2] & 0x3f); pDest[index_dest++] = Encode_GetChar(output[3]); } //null-terminator pDest[index_dest] = 0; return index_dest; }
(2)UrlEncode (百分號編碼)。方法是把輸入的字符串先用 UTF-8 編碼,然后把基本字符以外的字節用百分號加16進制的形式編碼。UrlEncode 的最后一個參數含義是,編碼結果中的16進制字符是否采用大寫字母表示。

BOOL UrlEncode(const char* szSrc, char* pBuf, int cbBufLen, BOOL bUpperCase); BOOL UrlDecode(const char* szSrc, char* pBuf, int cbBufLen); //百分號編碼 //http://zh.wikipedia.org/zh-cn/%E7%99%BE%E5%88%86%E5%8F%B7%E7%BC%96%E7%A0%81 BOOL UrlEncode(const char* szSrc, char* pBuf, int cbBufLen, BOOL bUpperCase) { if(szSrc == NULL || pBuf == NULL || cbBufLen <= 0) return FALSE; size_t len_ascii = strlen(szSrc); if(len_ascii == 0) { pBuf[0] = 0; return TRUE; } //先轉換到UTF-8 char baseChar = bUpperCase ? 'A' : 'a'; int cchWideChar = MultiByteToWideChar(CP_ACP, 0, szSrc, len_ascii, NULL, 0); LPWSTR pUnicode = (LPWSTR)malloc((cchWideChar + 1) * sizeof(WCHAR)); if(pUnicode == NULL) return FALSE; MultiByteToWideChar(CP_ACP, 0, szSrc, len_ascii, pUnicode, cchWideChar + 1); int cbUTF8 = WideCharToMultiByte(CP_UTF8, 0, pUnicode, cchWideChar, NULL, 0, NULL, NULL); LPSTR pUTF8 = (LPSTR)malloc((cbUTF8 + 1) * sizeof(CHAR)); if(pUTF8 == NULL) { free(pUnicode); return FALSE; } WideCharToMultiByte(CP_UTF8, 0, pUnicode, cchWideChar, pUTF8, cbUTF8 + 1, NULL, NULL); pUTF8[cbUTF8] = '\0'; unsigned char c; int cbDest = 0; //累加 unsigned char *pSrc = (unsigned char*)pUTF8; unsigned char *pDest = (unsigned char*)pBuf; while(*pSrc && cbDest < cbBufLen - 1) { c = *pSrc; if(isalpha(c) || isdigit(c) || c == '-' || c == '.' || c == '~') { *pDest = c; ++pDest; ++cbDest; } else if(c == ' ') { *pDest = '+'; ++pDest; ++cbDest; } else { //檢查緩沖區大小是否夠用? if(cbDest + 3 > cbBufLen - 1) break; pDest[0] = '%'; pDest[1] = (c >= 0xA0) ? ((c >> 4) - 10 + baseChar) : ((c >> 4) + '0'); pDest[2] = ((c & 0xF) >= 0xA)? ((c & 0xF) - 10 + baseChar) : ((c & 0xF) + '0'); pDest += 3; cbDest += 3; } ++pSrc; } //null-terminator *pDest = '\0'; free(pUnicode); free(pUTF8); return TRUE; } //解碼后是utf-8編碼 BOOL UrlDecode(const char* szSrc, char* pBuf, int cbBufLen) { if(szSrc == NULL || pBuf == NULL || cbBufLen <= 0) return FALSE; size_t len_ascii = strlen(szSrc); if(len_ascii == 0) { pBuf[0] = 0; return TRUE; } char *pUTF8 = (char*)malloc(len_ascii + 1); if(pUTF8 == NULL) return FALSE; int cbDest = 0; //累加 unsigned char *pSrc = (unsigned char*)szSrc; unsigned char *pDest = (unsigned char*)pUTF8; while(*pSrc) { if(*pSrc == '%') { *pDest = 0; //高位 if(pSrc[1] >= 'A' && pSrc[1] <= 'F') *pDest += (pSrc[1] - 'A' + 10) * 0x10; else if(pSrc[1] >= 'a' && pSrc[1] <= 'f') *pDest += (pSrc[1] - 'a' + 10) * 0x10; else *pDest += (pSrc[1] - '0') * 0x10; //低位 if(pSrc[2] >= 'A' && pSrc[2] <= 'F') *pDest += (pSrc[2] - 'A' + 10); else if(pSrc[2] >= 'a' && pSrc[2] <= 'f') *pDest += (pSrc[2] - 'a' + 10); else *pDest += (pSrc[2] - '0'); pSrc += 3; } else if(*pSrc == '+') { *pDest = ' '; ++pSrc; } else { *pDest = *pSrc; ++pSrc; } ++pDest; ++cbDest; } //null-terminator *pDest = '\0'; ++cbDest; int cchWideChar = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pUTF8, cbDest, NULL, 0); LPWSTR pUnicode = (LPWSTR)malloc(cchWideChar * sizeof(WCHAR)); if(pUnicode == NULL) { free(pUTF8); return FALSE; } MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)pUTF8, cbDest, pUnicode, cchWideChar); WideCharToMultiByte(CP_ACP, 0, pUnicode, cchWideChar, pBuf, cbBufLen, NULL, NULL); free(pUTF8); free(pUnicode); return TRUE; }
(3)獲取本機 IP 地址和網卡物理地址。可能有多個網絡適配器,但這里只是給出第一個適配器的結果。根據網絡資料,獲取網卡物理地址有多種方法。已知 IP 地址獲取其物理地址可以使用 SendARP 方法。但對於本機來說,可以直接用 GetAdaptersAddresses 更適合,其返回結果為鏈表(list)形式。這里對 IP 默認為 IPv4(4個字節),網卡物理地址通常為 6 個字節。

#include <Winsock2.h> #include <IPHlpApi.h> //for GetAdaptersAddresses //獲取本機的IP地址 BOOL GetIPAddress(unsigned char* buffer) { int bRet; char hostname[256]; WSADATA wsadata; struct hostent *pHost; struct in_addr *pAddr; WORD dwVersionRequested = MAKEWORD( 2, 2 ); bRet= WSAStartup(dwVersionRequested, &wsadata); bRet= gethostname(hostname, sizeof(hostname)); pHost = gethostbyname(hostname); if(pHost != NULL && pHost->h_addr_list[0] != NULL) { pAddr = (struct in_addr*)(pHost->h_addr_list[0]); buffer[0] = pAddr->s_net; buffer[1] = pAddr->s_host; buffer[2] = pAddr->s_lh; buffer[3] = pAddr->s_impno; bRet = TRUE; } else { //取不到IP地址,則賦值默認值 255.255.255.255 memset(buffer, 0xff, 4); bRet = FALSE; } WSACleanup(); return bRet; } //參數pMacAddr應該是8個字節的數組 BOOL GetMacAddress(unsigned char* pMacAddr) { DWORD nRet; //只查詢物理地址 DWORD nFlags = GAA_FLAG_SKIP_UNICAST | GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_FRIENDLY_NAME | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_DNS_SERVER; ULONG bufLen = 1024; PIP_ADAPTER_ADDRESSES pAdapterAddr = (PIP_ADAPTER_ADDRESSES)malloc(bufLen); if(pAdapterAddr == NULL) return FALSE; //AF_INET: return only IPv4 addresses. nRet = GetAdaptersAddresses(AF_INET, nFlags, NULL, pAdapterAddr, &bufLen); if(nRet == ERROR_BUFFER_OVERFLOW) { pAdapterAddr = (PIP_ADAPTER_ADDRESSES)realloc(pAdapterAddr, bufLen); if(pAdapterAddr == NULL) return FALSE; nRet = GetAdaptersAddresses(AF_INET, nFlags, NULL, pAdapterAddr, &bufLen); } if(nRet == ERROR_SUCCESS) { memcpy(pMacAddr, &pAdapterAddr->PhysicalAddress, pAdapterAddr->PhysicalAddressLength); free(pAdapterAddr); return TRUE; } else { //ff-ff-ff-ff-ff-ff: 表示獲取失敗(未知) memset(pMacAddr, 0xff, 6); free(pAdapterAddr); return FALSE; } }
(4)使用 SMTP 發送郵件。本文是參考看雪論壇上某文章中的代碼。如果發送中文,應該在傳輸時,指定文本使用的編碼,以防止接收端解釋成亂碼。以下代碼引用了(1)中的 Base64 編碼。

#include <Winsock2.h> typedef struct _SMTPINFO { char Server[32]; int Port; char UserName[16]; char Password[16]; char From[32]; char To[32]; char Subject[32]; char Msg[256]; } SMTPINFO, *LPSMTPINFO; BOOL Talk(SOCKET sockid, const char *szOkCode, char *pSend); BOOL SendMail(const LPSMTPINFO pInfo); //szOkCode: 是前一條命令成功時,預期的服務器的返回碼 BOOL Talk(SOCKET sockid, const char *szOkCode, char *pSend) { const int buflen = 256; char buf[buflen]; //接收返回信息 if (recv(sockid, buf, buflen, 0) == SOCKET_ERROR) return FALSE; if (strncmp(buf, szOkCode, strlen(szOkCode)) != 0) return FALSE; //發送命令 if (lstrlen(pSend) > 0) { WSABUF DataBuf; DataBuf.len = lstrlen(pSend); DataBuf.buf = pSend; DWORD dwS; if(WSASend(sockid, &DataBuf, 1, &dwS, 0, 0, 0)) return FALSE; } return TRUE; } BOOL SendMail(const LPSMTPINFO pInfo) { char buf[1024]; //准備網絡連接 WSADATA wsadata; if(WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) return FALSE; //創建套接字 SOCKET sockid; if ((sockid = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { WSACleanup(); return FALSE; } //得到smtp服務器ip struct hostent *phostent = gethostbyname(pInfo->Server); struct sockaddr_in addr; CopyMemory(&addr.sin_addr.S_un.S_addr, phostent->h_addr_list[0], sizeof(addr.sin_addr.S_un.S_addr)); addr.sin_family = AF_INET; addr.sin_port = htons(pInfo->Port); ZeroMemory(&addr.sin_zero, 8); //連接服務器 if(connect(sockid, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == SOCKET_ERROR) goto STOP; //EHLO 是對 HELO 的擴充命令。 strcpy(buf, "EHLO hoodlum1980\r\n"); if(!Talk(sockid, "220", buf)) goto STOP; strcpy(buf, "AUTH LOGIN\r\n"); if(!Talk(sockid, "250", buf)) goto STOP; size_t userlen = lstrlen(pInfo->UserName); size_t passlen = lstrlen(pInfo->Password); Base64_Encode(buf, pInfo->UserName, userlen); strcat(buf, "\r\n"); if(!Talk(sockid, "334", buf)) goto STOP; Base64_Encode(buf, pInfo->Password, passlen); strcat(buf, "\r\n"); if (!Talk(sockid, "334", buf)) goto STOP; //ZeroMemory(buf, buflen); wsprintf(buf, "MAIL FROM:<%s>\r\n", pInfo->From); if(!Talk(sockid, "235", buf)) goto STOP; wsprintf(buf, "RCPT TO:<%s>\r\n", pInfo->To); if(!Talk(sockid, "250", buf)) goto STOP; strcpy(buf, "DATA\r\n"); if (!Talk(sockid, "250", buf)) goto STOP; char szContent[512]; Base64_Encode(szContent, pInfo->Msg, strlen(pInfo->Msg)); wsprintf(buf, "MIME-Version:1.0\r\nContent-Type:text/plain; charset=gb2312\r\n" "Content-Transfer-Encoding:base64\r\nContent-Language:zh-cn\r\n" "TO: %s\r\nFROM: %s\r\nSUBJECT: %s\r\n\r\n%s\r\n.", pInfo->To, pInfo->From, pInfo->Subject, szContent); if(Talk(sockid, "354", buf)) goto STOP; strcpy(buf, "QUIT\r\n"); if(!Talk(sockid, "250", buf)) goto STOP; strcpy(buf, ""); if(!Talk(sockid, "221", buf)) goto STOP; else { closesocket(sockid); WSACleanup(); return TRUE; } STOP: closesocket(sockid); WSACleanup(); return FALSE; } // 使用范例: SMTPINFO info; strcpy(info.Server,"smtp.***.com"); info.Port = 25; strcpy(info.UserName,"XXXXX@***.com"); strcpy(info.Password,"*****"); strcpy(info.From, "XXXXX@***.com"); strcpy(info.To, "YYYYY@YYY.com"); strcpy(info.Subject, "郵件標題"); strcpy(info.Msg, "郵件內容。"); SendMail(&info);
(5)以 Post 形式發送 HTTP 請求。如果使用 C# 則可以使用 HttpRequest。由於是測試版本,以下代碼在讀取服務器響應時假設服務器的響應是短文本,因此沒有判斷服務器的響應是否已全部讀取,並不是非常完善。在實際應用中應完善后使用。

#include <Wininet.h> BOOL SendHttpPost() { HINTERNET hInternet = InternetOpen(_T("HttpPostTest"), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if(hInternet == NULL) return FALSE; HINTERNET hConnect = InternetConnect(hInternet, _T("www.xxx.com"), INTERNET_DEFAULT_HTTP_PORT, NULL, //user NULL, //password INTERNET_SERVICE_HTTP, 0, 0); if(hConnect == NULL) { InternetCloseHandle(hInternet); return FALSE; } LPCTSTR szTypes[] = { _T("*/*"), NULL }; TCHAR szData[1024], szResponse[1024]; TCHAR szContent[1024], szTemp[128]; //全英文的ascii字符集是UTF-8的子集,所以下面的字符串也可以看着是 UTF-8 編碼的。 _tcscpy(szContent, _T("user=xxx&password=xxx")); DWORD dwBytesAvailable = 0, dwBytesRead = 0; HINTERNET hRequest = HttpOpenRequest(hConnect, _T("POST"), _T("/xxx/xxx.aspx"), NULL, //version: "HTTP/1.1" _T("http://www.xxx.com/xxx/xxx.aspx"), //referer szTypes, //types INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_RELOAD, 0); if(hRequest == NULL) { InternetCloseHandle(hConnect); InternetCloseHandle(hInternet); return FALSE; } BOOL bSuccess; TCHAR headers[512]; _stprintf(headers, _T("Content-Length: %ld\r\n"), _tcslen(szContent)); _tcscat(headers, _T("Content-Type: application/x-www-form-urlencoded\r\n") _T("xxx: yyy\r\n") ); bSuccess = HttpSendRequest(hRequest, headers, //additional header _tcslen(headers), //headersLength szContent, _tcslen(szContent)); TCHAR szBuffer[1024]; DWORD bufLen = sizeof(szBuffer); DWORD dwIndex = 0; if(bSuccess) { //查詢服務器返回的headers信息,內含傳輸內容使用的編碼。 HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, szBuffer, &bufLen, &dwIndex); InternetQueryDataAvailable(hRequest, &dwBytesAvailable, 0, 0); bSuccess = InternetReadFile(hRequest, szResponse, sizeof(szResponse), &dwBytesRead); int count = MultiByteToWideChar(CP_UTF8, 0, szResponse, dwBytesRead, szDataUnicode, sizeof(szDataUnicode)/sizeof(szDataUnicode[0])); count = WideCharToMultiByte(CP_ACP, 0, szDataUnicode, count, szData, sizeof(szData)/sizeof(szData[0]), NULL, NULL); szData[count] = '\0'; } InternetCloseHandle(hRequest); InternetCloseHandle(hConnect); InternetCloseHandle(hInternet); return bSuccess; }
以下是 java 版本的類似代碼:

public String SendRequest() { //發送post請求獲取簽名 String postData = "user=%s&password=%s"; byte[] postBytes = postData.getBytes(); URL url = new URL("http://www.xxx.com/xxx/xxx.aspx"); HttpURLConnection connection = (HttpURLConnection)url.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Length", String.valueOf(postBytes.length)); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("xxx", "yyy"); connection.setUseCaches(false); connection.connect(); DataOutputStream out = new DataOutputStream(connection.getOutputStream()); out.write(postBytes); out.flush(); out.close(); StringBuffer buf = new StringBuffer(960); int responseCode = connection.getResponseCode(); if(responseCode == 200) { //獲取服務器返回的數據 InputStream stream = connection.getInputStream(); BufferedReader bufReader = new BufferedReader(new InputStreamReader(stream)); String line = ""; while((line = bufReader.readLine()) != null) buf.append(line); } return buf.toString(); }
【參考資料】: