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. }
                                                                                         
                                                                                                                      改变自己,从现在做起-----------久馆
       
        

posted on 2020-12-18 20:46  DanielandCalf  阅读(1236)  评论(0编辑  收藏  举报

导航