C++實現HTTP連接


一、介紹下c++客戶端實現的http連接服務端並返回json數據的過程。

http的連接以及獲取json數據過程使用的是多字節編碼實現的:

我們看下http頭文件所包含的參數以及函數 

  1. HttpClient.h
  2. #ifndef HTTPCLIENT_H
  3. #define HTTPCLIENT_H
  4. #include <afxinet.h>
  5. #include <string>
  6. using namespace std;
  7. #define IE_AGENT _T("Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)")
  8. // 操作成功
  9. #define SUCCESS 0
  10. // 操作失敗 
  11. #define FAILURE 1
  12. // 操作超時 www.it165.net
  13. #define OUTTIME 2
  14. class CHttpClient
  15. {
  16. public:
  17. CHttpClient(LPCTSTR strAgent = IE_AGENT);
  18. virtual ~CHttpClient(void);
  19. /*static wchar_t* ANSIToUnicode(const char* str);
  20. static char* UnicodeToANSI(const wchar_t* str);
  21. static char* UnicodeToUTF8(const wchar_t* str);
  22. */ //在下面的ExecuteRequest函數中處理了多字節轉utf8的方法,不需要再使用這三個函數,這里注釋
  23. // 的三句轉碼總覺得在使用時有問題,代碼我一並貼出,供大家查找問題
  24. int HttpGet(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse);
  25. int HttpPost(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse);
  26. private:
  27. int ExecuteRequest(LPCTSTR strMethod, LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse);
  28. void Clear();
  29. private:
  30. CInternetSession *m_pSession;
  31. CHttpConnection *m_pConnection;
  32. CHttpFile *m_pFile;
  33. };
  34. #endif // HTTPCLIENT_H

以上是http實現的頭文件,以下為.cpp中代碼實現過程,內容有點多,若只是使用的話粘貼復制即可

  1. // HttpClient.cpp
  2. #include "StdAfx.h"
  3. #include "HttpClient.h"
  4. #define BUFFER_SIZE 1024 
  5. #define NORMAL_CONNECT INTERNET_FLAG_KEEP_CONNECTION
  6. #define SECURE_CONNECT NORMAL_CONNECT | INTERNET_FLAG_SECURE
  7. #define NORMAL_REQUEST INTERNET_FLAG_RELOAD | INTERNET_FLAG_DONT_CACHE
  8. #define SECURE_REQUEST NORMAL_REQUEST | INTERNET_FLAG_SECURE | INTERNET_FLAG_IGNORE_CERT_CN_INVALID
  9. CHttpClient::CHttpClient(LPCTSTR strAgent)
  10. {
  11. m_pSession = new CInternetSession(strAgent);
  12. m_pConnection = NULL;
  13. m_pFile = NULL;
  14. }
  15. CHttpClient::~CHttpClient( void)
  16. {
  17. Clear();
  18. if(NULL != m_pSession)
  19. {
  20. m_pSession->Close();
  21. delete m_pSession;
  22. m_pSession = NULL;
  23. }
  24. }
  25. void CHttpClient::Clear()
  26. {
  27. if(NULL != m_pFile)
  28. {
  29. m_pFile->Close();
  30. delete m_pFile;
  31. m_pFile = NULL;
  32. }
  33. if(NULL != m_pConnection)
  34. {
  35. m_pConnection->Close();
  36. delete m_pConnection;
  37. m_pConnection = NULL;
  38. }
  39. }
  40. int CHttpClient::ExecuteRequest(LPCTSTR strMethod, LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse)
  41. {
  42. DWORD dwFlags;
  43. DWORD dwStatus = 0;
  44. DWORD dwStatusLen = sizeof(dwStatus);
  45. CString strLine;
  46. CString strServer;
  47. CString strObject;
  48. DWORD dwServiceType;
  49. INTERNET_PORT nPort;
  50. strResponse = "";
  51. AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort);
  52. try
  53. {
  54. if (dwServiceType == AFX_INET_SERVICE_HTTP)
  55. {
  56. m_pConnection = m_pSession->GetHttpConnection(strServer,NORMAL_CONNECT,nPort);
  57. }
  58. else
  59. {
  60. m_pConnection = m_pSession->GetHttpConnection(strServer, INTERNET_FLAG_SECURE, nPort,
  61. NULL, NULL);
  62. }
  63. if(m_pConnection)
  64. {
  65. if (dwServiceType == AFX_INET_SERVICE_HTTP)
  66. {
  67. m_pFile = m_pConnection->OpenRequest(strMethod, strObject,
  68. NULL, 1, NULL, NULL, NORMAL_REQUEST);
  69. }
  70. else
  71. {
  72. m_pFile = (CHttpFile*)m_pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject, NULL, 1,
  73. NULL, NULL,
  74. INTERNET_FLAG_SECURE |
  75. INTERNET_FLAG_EXISTING_CONNECT |
  76. INTERNET_FLAG_RELOAD |
  77. INTERNET_FLAG_NO_CACHE_WRITE |
  78. INTERNET_FLAG_IGNORE_CERT_DATE_INVALID |
  79. INTERNET_FLAG_IGNORE_CERT_CN_INVALID
  80. );
  81. //get web server option
  82. m_pFile->QueryOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
  83. dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
  84. //set web server option
  85. m_pFile->SetOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
  86. }
  87. m_pFile->AddRequestHeaders( "Accept: *,*/*");
  88. m_pFile->AddRequestHeaders( "Accept-Language: zh-cn");
  89. m_pFile->AddRequestHeaders( "Content-Type: application/x-www-form-urlencoded");
  90. m_pFile->AddRequestHeaders( "Accept-Encoding: gzip, deflate");
  91. if(m_pFile->SendRequest(NULL, 0, (LPVOID)(LPCTSTR)strPostData, strPostData == NULL ? 0 : _tcslen(strPostData)))
  92. {
  93. //get response status if success, return 200
  94. if (dwServiceType != AFX_INET_SERVICE_HTTP)
  95. {
  96. m_pFile->QueryInfo(HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE,
  97. &dwStatus, &dwStatusLen, 0);
  98. }
  99. // while(m_pFile->ReadString(strLine))
  100. // {
  101. // //m_strHtml += Convert(strLine, CP_ACP);
  102. // m_strHtml += strLine + char(13) + char(10);
  103. // }
  104. char szChars[BUFFER_SIZE + 1] = {0};
  105. string strRawResponse = "";
  106. UINT nReaded = 0;
  107. while ((nReaded = m_pFile->Read((void*)szChars, BUFFER_SIZE)) > 0)
  108. {
  109. szChars[nReaded] = '\0';
  110. strRawResponse += szChars;
  111. memset(szChars, 0, BUFFER_SIZE + 1);
  112. }
  113. int unicodeLen = MultiByteToWideChar(CP_UTF8, 0, strRawResponse.c_str(), -1, NULL, 0);
  114. WCHAR *pUnicode = new WCHAR[unicodeLen + 1];
  115. memset(pUnicode,0,(unicodeLen+1)*sizeof(wchar_t));
  116. MultiByteToWideChar(CP_UTF8, 0,strRawResponse.c_str(),-1, pUnicode,unicodeLen);
  117. DWORD dwNum = WideCharToMultiByte(CP_OEMCP, NULL,pUnicode,-1,NULL,0,NULL,FALSE);// WideCharToMultiByte的運用
  118. char *psText; // psText為char*的臨時數組,作為賦值給std::string的中間變量
  119. psText = new char[dwNum];
  120. WideCharToMultiByte (CP_OEMCP, NULL,pUnicode,-1,psText,dwNum,NULL,FALSE);// WideCharToMultiByte的再次運用
  121. string szDst = psText;// std::string賦值
  122. delete []psText;// psText的清除
  123. strResponse = szDst;
  124. // char *ansi_str = UnicodeToUTF8(pUnicode);
  125. //
  126. // string str = ansi_str;
  127. // free(ansi_str);
  128. //
  129. // CString cs(str.c_str());
  130. delete []pUnicode;
  131. pUnicode = NULL;
  132. // strResponse = cs;
  133. Clear();
  134. }
  135. else
  136. {
  137. return FAILURE;
  138. }
  139. }
  140. else
  141. {
  142. return FAILURE;
  143. }
  144. }
  145. catch (CInternetException* e)
  146. {
  147. Clear();
  148. DWORD dwErrorCode = e->m_dwError;
  149. e->Delete();
  150. DWORD dwError = GetLastError();
  151. // PRINT_LOG("dwError = %d", dwError, 0);
  152. if (ERROR_INTERNET_TIMEOUT == dwErrorCode)
  153. {
  154. return OUTTIME;
  155. }
  156. else
  157. {
  158. return FAILURE;
  159. }
  160. }
  161. return SUCCESS;
  162. }
  163. int CHttpClient::HttpGet(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse)
  164. {
  165. return ExecuteRequest("GET", strUrl, strPostData, strResponse);
  166. }
  167. int CHttpClient::HttpPost(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse)
  168. {
  169. return ExecuteRequest("POST", strUrl, strPostData, strResponse);
  170. }
  171. wchar_t* CHttpClient::ANSIToUnicode(const char* str)
  172. {
  173. int textlen;
  174. wchar_t * result;
  175. textlen = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
  176. result = ( wchar_t *)malloc((textlen + 1) * sizeof(wchar_t));
  177. memset(result, 0, (textlen + 1) * sizeof(wchar_t));
  178. MultiByteToWideChar(CP_ACP, 0, str, -1, (LPWSTR)result, textlen);
  179. return result;
  180. }
  181. char* CHttpClient::UnicodeToUTF8(const wchar_t* str)
  182. {
  183. char* result;
  184. int textlen;
  185. textlen = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
  186. result = ( char *)malloc((textlen + 1) * sizeof(char));
  187. memset(result, 0, sizeof(char) * (textlen + 1));
  188. WideCharToMultiByte(CP_UTF8, 0, str, -1, result, textlen, NULL, NULL);
  189. return result;
  190. }
  191. char* CHttpClient::UnicodeToANSI(const wchar_t* str)
  192. {
  193. char* result;
  194. int textlen;
  195. textlen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);
  196. result = ( char *)malloc((textlen + 1) * sizeof(char));
  197. memset(result, 0, sizeof(char) * (textlen + 1));
  198. WideCharToMultiByte(CP_ACP, 0, str, -1, result, textlen, NULL, NULL);
  199. return result;
  200. }

仔細觀察上面所說函數,就可以看到在ExecuteRequest函數中處理了多字節轉為utf8格式,后面單獨寫了幾種其他轉碼方式,這幾種方式在轉碼時總覺得有問題,詳細介紹大家參考以上內容。

后面說下json數據,這里我們的http使用的是get請求方式,貼一份我查詢會議的代碼,內容相對全些,后面再介紹。

  1. int CDatabaseManage::QueryMRInBooked( CString roomId,CString data ,HWND hwnd)
  2. {
  3. TMRBookedInfo tmrBookedInfo;
  4. TMRBookedInfoArray tmrBookedInfoArray;
  5. tmrBookedInfoArray.clear();
  6. UINT uiRet = -1;
  7. if (m_httpCtrl == NULL)
  8. {
  9. m_httpCtrl = new CHttpClient();
  10. }
  11. CString strUrl,strPostData ;
  12. strUrl.Format(_T( "http://此處是ip地址:端口號/QueryConferenceRecord?content={\"Conference_Index\":\"%s\",\"RecordTime\":\"%s\"}"),roomId,data);
  13. string strResponse="";
  14. uiRet = m_httpCtrl->HttpGet(strUrl,strPostData,strResponse);
  15. if ((uiRet == SUCCESS) &&(!strResponse.empty()))
  16. {
  17. Json::Value root;
  18. Json::Reader reader;
  19. if (reader.parse(strResponse,root,false))
  20. {
  21. if (root.isMember("content"))
  22. {
  23. Json::Value jsResult=root[ "content"];
  24. if (jsResult.size()==0)
  25. {
  26. return 0;
  27. }
  28. uiRet = jsResult.size();
  29. for(int i=0;i<jsResult.size();i++)
  30. {
  31. if (jsResult[i].isMember("Index"))
  32. {
  33. tmrBookedInfo.uiIdx = jsResult[i][ "Index"].asInt();
  34. }
  35. if (jsResult[i].isMember("Subject"))
  36. {}
  37. tmrBookedInfo.strObj = jsResult[i][ "Subject"].asCString();
  38. }
  39. if (jsResult[i].isMember("BeginTime"))
  40. {
  41. tmrBookedInfo.uiBeginTime = jsResult[i][ "BeginTime"].asCString();
  42. }
  43. if (jsResult[i].isMember("EndTime"))
  44. {
  45. tmrBookedInfo.uiEndTime = jsResult[i][ "EndTime"].asCString();
  46. }
  47. if (jsResult[i].isMember("UserName"))
  48. {
  49. tmrBookedInfo.uiUserName =jsResult[i][ "UserName"].asCString();
  50. }
  51. tmrBookedInfoArray.push_back(tmrBookedInfo);
  52. }
  53. ::SendMessage(hwnd,CM_SHOWRECORD,(WPARAM)&tmrBookedInfoArray, 0);
  54. }
  55. }
  56. }
  57. return uiRet;
  58. }

    在strUrl中包含了我需要發送到服務端得ip、端口號,以及打包的json數據,后面使用HttpGet請求,此時就可以獲取到服務端響應的json數據,保存在了strResponse中,這些都是在HttpClient.cpp中實現的,后面就是進行解析了。

http通信的優點很多,當然也有局限性。一般我們開發使用restful形式提供接口,這樣可以完成RPC遠程調用。

https = http + ssl
ssl是一種安全的傳輸層協議。通信前先建立安全通道,之后在傳遞數據。

libcurl庫,一個c寫的http庫,想要支持ssl,編譯時需要加入編譯選項。

編譯庫

編譯windows版的dll  
linux版本的library :編譯時加入 ./configure --with-ssl 即可
windows dll已經編譯好的庫 

編程介紹

簡單同步編程 (easy mode)
使用流程(套路)
1.       調用curl_global_init()初始化libcurl
2.       調用curl_easy_init()函數得到 easy interface型指針
3.       調用curl_easy_setopt()設置傳輸選項
4.       根據curl_easy_setopt()設置的傳輸選項,實現回調函數以完成用戶特定任務
5.       調用curl_easy_perform()函數完成傳輸任務,返回碼
6.       調用curl_easy_cleanup()釋放內存
7.       調用curl_global_cleanup() (可以不用調用)
在整過過程中設置curl_easy_setopt()參數是最關鍵的,了解相關參數及對應作用很重要

重要函數介紹
1.CURLcode curl_global_init(long flags);
描述:
這個函數只能用一次。(其實在調用curl_global_cleanup 函數后仍然可再用)
如果這個函數在curl_easy_init函數調用時還沒調用,它講由libcurl庫自動調用,所以多線程下最好主動調用該函數以防止在線程中curl_easy_init時多次調用。

注意:雖然libcurl是線程安全的,但curl_global_init是不能保證線程安全的,所以不要在每個線程中都調用curl_global_init,應該將該函數的調用放在主線程中。
參數:flags
CURL_GLOBAL_ALL                      //初始化所有的可能的調用。(常用)
CURL_GLOBAL_SSL                      //初始化支持 安全套接字層。
CURL_GLOBAL_WIN32            //初始化win32套接字庫。
CURL_GLOBAL_NOTHING         //沒有額外的初始化。

2.void curl_global_cleanup(void);
描述:
在結束libcurl使用的時候,用來對curl_global_init做的工作清理。類似於close的函數。

注意:雖然libcurl是線程安全的,但curl_global_cleanup是不能保證線程安全的,所以不要在每個線程中都調用curl_global_init,應該將該函數的調用放在主線程中。

3.char *curl_version( );
描述: 打印當前libcurl庫的版本。

4.CURL *curl_easy_init( );
描述:
curl_easy_init用來初始化一個CURL的指針(有些像返回FILE類型的指針一樣). 相應的在調用結束時要用curl_easy_cleanup函數清理. 一般curl_easy_init意味着一個會話的開始. 它會返回一個easy_handle(CURL*對象), 一般都用在easy系列的函數中.

5.void curl_easy_cleanup(CURL *handle);
描述:
這個調用用來結束一個會話.與curl_easy_init配合着用. 
參數:
CURL類型的指針.

6.CURLcode curl_easy_setopt(CURL *handle, CURLoption option, parameter);
描述:
這個函數最重要了.幾乎所有的curl 程序都要頻繁的使用它.它告訴curl庫.程序將有如何的行為. 比如要查看一個網頁的html代碼等.(這個函數有些像ioctl函數)
參數:
1 CURL類型的指針
2 各種CURLoption類型的選項.(都在curl.h庫里有定義,
3 parameter 這個參數 既可以是個函數的指針,也可以是某個對象的指針,也可以是個long型的變量.它用什么這取決於第二個參數.
CURLoption 這個參數的取值很多.具體的可以查看man手冊.

7.CURLcode curl_easy_perform(CURL *handle);
描述:
這個函數在初始化CURL類型的指針 以及curl_easy_setopt完成后調用. 就像字面的意思所說perform就像是個舞台.讓我們設置的option 運作起來.
參數:
CURL類型的指針.

8.消息頭設置

  1. struct curl_slist *headers=NULL; /* init to NULL is important */
  2. headers = curl_slist_append(headers, "Hey-server-hey: how are you?");
  3. headers = curl_slist_append(headers, "X-silly-content: yes");
  4. /* pass our list of custom made headers */
  5. curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers);
  6. curl_easy_perform(easyhandle); /* transfer http */
  7. curl_slist_free_all(headers); /* free the header list */

如果修改已經存在的消息頭,直接設置即可;如果刪除消息頭信息,直接設置對應內容為空即可。

發送http消息后,服務器會返回消息頭,如果只是查看消息頭內容,使用如下函數
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, 打印函數)
如果想獲取特定信息,使用如下方法:
CURLcode curl_easy_getinfo(CURL *curl, CURLINFO info, ... );
最后一個參數是接收變量。具體詳情查看官網API

下面是完整通信設置代碼
 

    1. //http回調寫函數
    2. static size_t CurlWriteBuffer(char *buffer, size_t size, size_t nmemb, std::string* stream)
    3. {
    4. //第二個參數為每個數據的大小,第三個為數據個數,最后一個為接收變量
    5. size_t sizes = size*nmemb;
    6. if(stream == NULL)
    7. return 0;
    8. stream->append(buffer,sizes);
    9. return sizes;
    10. }
    11. //http發送封裝
    12. int CPrinterDlg::posturl(std::string& msg, std::string& url, bool IsSSL)
    13. {
    14. CURL* pCurl= NULL; //一個libcurl的handle
    15. CURLcode res; //返回狀態碼
    16. std::string response; //返回信息
    17. curl_global_init(CURL_GLOBAL_ALL); //全局初始化
    18. pCurl = curl_easy_init(); //創建一個handle
    19. //設置請求頭
    20. struct curl_slist* header_ = NULL;
    21. header_ = curl_slist_append(header_, "Content-Type: application/json;charset=utf-8");
    22. //添加請求頭到handle
    23. curl_easy_setopt(pCurl, CURLOPT_HTTPHEADER, header_);
    24. //設置URL
    25. curl_easy_setopt(pCurl, CURLOPT_URL, url.c_str());
    26. CURLOPT_WRITEFUNCTION 將后繼的動作交給write_data函數處理
    27. curl_easy_setopt(pCurl,CURLOPT_POSTFIELDS,msg.c_str()); //post請求消息數據
    28. curl_easy_setopt(pCurl,CURLOPT_POSTFIELDSIZE,msg.length()); //消息長度
    29. curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, CurlWriteBuffer); //回調函數
    30. curl_easy_setopt(pCurl,CURLOPT_WRITEDATA,&response); //數據接收變量
    31. curl_easy_setopt(pCurl,CURLOPT_TIMEOUT,m_settinginfo.m_http_timeout); //連接超時時間
    32. //不支持ssl驗證
    33. if(m_settinginfo.m_ssl == 0)
    34. {
    35. curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0);//設定為不驗證證書和HOST
    36. curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0);
    37. }
    38. else
    39. {
    40. // 配置 https 請求所需證書
    41. if (m_settinginfo.m_ssl == 1) //ssl單向驗證,不驗證服務器
    42. {
    43. curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
    44. } else
    45. { //雙向驗證
    46. curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 1L);
    47. curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0);
    48. curl_easy_setopt(pCurl,CURLOPT_CAINFO,ca_info.ca_path.c_str());
    49. }
    50. //設置客戶端信息
    51. curl_easy_setopt(pCurl, CURLOPT_SSLCERT, ca_info.client_cert_path.c_str());
    52. curl_easy_setopt(pCurl,CURLOPT_SSLCERTTYPE, "PEM");
    53. curl_easy_setopt(pCurl, CURLOPT_SSLKEY, ca_info.client_key_path.c_str());
    54. curl_easy_setopt(pCurl,CURLOPT_SSLKEYTYPE, "PEM");
    55. //如果客戶端證書密鑰使用密碼加密,設置加密密碼
    56. //curl_easy_setopt(pCurl, CURLOPT_KEYPASSWD, "your_key_password");
    57. }
    58. //執行http連接
    59. res = curl_easy_perform(pCurl);
    60. //清除消息頭
    61. curl_slist_free_all(header_);
    62. //清除handle
    63. curl_easy_cleanup(pCurl);
    64. return 0;
    65. }
                                                                                         
                                                                                                                      改變自己,從現在做起-----------久館
       
        


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM