• 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] = '';
    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. }
                                                                                           
                                                                                                                        改变自己,从现在做起-----------久馆
         
          
  • 相关阅读:
    一些好用的小工具
    App随机测试之Monkey和Maxim
    Appium如何自动判断浏览器驱动
    最简单的一个Appium测试Android Web APP的代码demo
    pytest使用allure生成测试报告的2种命令
    使用order by in()将快到期的数据排到最上方。
    关于jQuery click()方法重复提交的问题
    关于List removeAll失效的问题
    根据年和月计算对应的天数
    jquery通过监听输入框实现值的自动计算
  • 原文地址:https://www.cnblogs.com/zzw19940404/p/14156968.html
Copyright © 2020-2023  润新知