C++实现HTTP连接
一、介绍下c++客户端实现的http连接服务端并返回json数据的过程。
http的连接以及获取json数据过程使用的是多字节编码实现的:
我们看下http头文件所包含的参数以及函数
-
HttpClient.h
-
-
-
-
-
using namespace std;
-
-
// 操作成功
-
-
// 操作失败
-
-
// 操作超时 www.it165.net
-
-
class CHttpClient
-
{
-
public:
-
CHttpClient(LPCTSTR strAgent = IE_AGENT);
-
virtual ~CHttpClient(void);
-
/*static wchar_t* ANSIToUnicode(const char* str);
-
static char* UnicodeToANSI(const wchar_t* str);
-
static char* UnicodeToUTF8(const wchar_t* str);
-
*/ //在下面的ExecuteRequest函数中处理了多字节转utf8的方法,不需要再使用这三个函数,这里注释
-
// 的三句转码总觉得在使用时有问题,代码我一并贴出,供大家查找问题
-
int HttpGet(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse);
-
int HttpPost(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse);
-
private:
-
int ExecuteRequest(LPCTSTR strMethod, LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse);
-
void Clear();
-
private:
-
CInternetSession *m_pSession;
-
CHttpConnection *m_pConnection;
-
CHttpFile *m_pFile;
-
};
-
以上是http实现的头文件,以下为.cpp中代码实现过程,内容有点多,若只是使用的话粘贴复制即可
-
// HttpClient.cpp
-
-
-
-
-
-
-
-
CHttpClient::CHttpClient(LPCTSTR strAgent)
-
{
-
m_pSession = new CInternetSession(strAgent);
-
m_pConnection = NULL;
-
m_pFile = NULL;
-
}
-
CHttpClient::~CHttpClient(void)
-
{
-
Clear();
-
if(NULL != m_pSession)
-
{
-
m_pSession->Close();
-
delete m_pSession;
-
m_pSession = NULL;
-
}
-
}
-
void CHttpClient::Clear()
-
{
-
if(NULL != m_pFile)
-
{
-
m_pFile->Close();
-
delete m_pFile;
-
m_pFile = NULL;
-
}
-
if(NULL != m_pConnection)
-
{
-
m_pConnection->Close();
-
delete m_pConnection;
-
m_pConnection = NULL;
-
}
-
}
-
int CHttpClient::ExecuteRequest(LPCTSTR strMethod, LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse)
-
{
-
DWORD dwFlags;
-
DWORD dwStatus = 0;
-
DWORD dwStatusLen = sizeof(dwStatus);
-
CString strLine;
-
CString strServer;
-
CString strObject;
-
DWORD dwServiceType;
-
INTERNET_PORT nPort;
-
strResponse = "";
-
AfxParseURL(strUrl, dwServiceType, strServer, strObject, nPort);
-
try
-
{
-
if (dwServiceType == AFX_INET_SERVICE_HTTP)
-
{
-
m_pConnection = m_pSession->GetHttpConnection(strServer,NORMAL_CONNECT,nPort);
-
}
-
else
-
{
-
m_pConnection = m_pSession->GetHttpConnection(strServer, INTERNET_FLAG_SECURE, nPort,
-
NULL, NULL);
-
}
-
if(m_pConnection)
-
{
-
if (dwServiceType == AFX_INET_SERVICE_HTTP)
-
{
-
m_pFile = m_pConnection->OpenRequest(strMethod, strObject,
-
NULL, 1, NULL, NULL, NORMAL_REQUEST);
-
}
-
else
-
{
-
m_pFile = (CHttpFile*)m_pConnection->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject, NULL, 1,
-
NULL, NULL,
-
INTERNET_FLAG_SECURE |
-
INTERNET_FLAG_EXISTING_CONNECT |
-
INTERNET_FLAG_RELOAD |
-
INTERNET_FLAG_NO_CACHE_WRITE |
-
INTERNET_FLAG_IGNORE_CERT_DATE_INVALID |
-
INTERNET_FLAG_IGNORE_CERT_CN_INVALID
-
);
-
//get web server option
-
m_pFile->QueryOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
-
dwFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;
-
//set web server option
-
m_pFile->SetOption(INTERNET_OPTION_SECURITY_FLAGS, dwFlags);
-
}
-
m_pFile->AddRequestHeaders("Accept: *,*/*");
-
m_pFile->AddRequestHeaders("Accept-Language: zh-cn");
-
m_pFile->AddRequestHeaders("Content-Type: application/x-www-form-urlencoded");
-
m_pFile->AddRequestHeaders("Accept-Encoding: gzip, deflate");
-
if(m_pFile->SendRequest(NULL, 0, (LPVOID)(LPCTSTR)strPostData, strPostData == NULL ? 0 : _tcslen(strPostData)))
-
{
-
//get response status if success, return 200
-
if (dwServiceType != AFX_INET_SERVICE_HTTP)
-
{
-
m_pFile->QueryInfo(HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE,
-
&dwStatus, &dwStatusLen, 0);
-
}
-
// while(m_pFile->ReadString(strLine))
-
// {
-
// //m_strHtml += Convert(strLine, CP_ACP);
-
// m_strHtml += strLine + char(13) + char(10);
-
// }
-
char szChars[BUFFER_SIZE + 1] = {0};
-
string strRawResponse = "";
-
UINT nReaded = 0;
-
while ((nReaded = m_pFile->Read((void*)szChars, BUFFER_SIZE)) > 0)
-
{
-
szChars[nReaded] = '\0';
-
strRawResponse += szChars;
-
memset(szChars, 0, BUFFER_SIZE + 1);
-
}
-
int unicodeLen = MultiByteToWideChar(CP_UTF8, 0, strRawResponse.c_str(), -1, NULL, 0);
-
WCHAR *pUnicode = new WCHAR[unicodeLen + 1];
-
memset(pUnicode,0,(unicodeLen+1)*sizeof(wchar_t));
-
MultiByteToWideChar(CP_UTF8,0,strRawResponse.c_str(),-1, pUnicode,unicodeLen);
-
DWORD dwNum = WideCharToMultiByte(CP_OEMCP,NULL,pUnicode,-1,NULL,0,NULL,FALSE);// WideCharToMultiByte的运用
-
char *psText; // psText为char*的临时数组,作为赋值给std::string的中间变量
-
psText = new char[dwNum];
-
WideCharToMultiByte (CP_OEMCP,NULL,pUnicode,-1,psText,dwNum,NULL,FALSE);// WideCharToMultiByte的再次运用
-
string szDst = psText;// std::string赋值
-
delete []psText;// psText的清除
-
strResponse = szDst;
-
// char *ansi_str = UnicodeToUTF8(pUnicode);
-
//
-
// string str = ansi_str;
-
// free(ansi_str);
-
//
-
// CString cs(str.c_str());
-
delete []pUnicode;
-
pUnicode = NULL;
-
// strResponse = cs;
-
Clear();
-
}
-
else
-
{
-
return FAILURE;
-
}
-
}
-
else
-
{
-
return FAILURE;
-
}
-
}
-
catch (CInternetException* e)
-
{
-
Clear();
-
DWORD dwErrorCode = e->m_dwError;
-
e->Delete();
-
DWORD dwError = GetLastError();
-
// PRINT_LOG("dwError = %d", dwError, 0);
-
if (ERROR_INTERNET_TIMEOUT == dwErrorCode)
-
{
-
return OUTTIME;
-
}
-
else
-
{
-
return FAILURE;
-
}
-
}
-
return SUCCESS;
-
}
-
int CHttpClient::HttpGet(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse)
-
{
-
return ExecuteRequest("GET", strUrl, strPostData, strResponse);
-
}
-
int CHttpClient::HttpPost(LPCTSTR strUrl, LPCTSTR strPostData, string &strResponse)
-
{
-
return ExecuteRequest("POST", strUrl, strPostData, strResponse);
-
}
-
wchar_t* CHttpClient::ANSIToUnicode(const char* str)
-
{
-
int textlen;
-
wchar_t * result;
-
textlen = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
-
result = (wchar_t *)malloc((textlen + 1) * sizeof(wchar_t));
-
memset(result, 0, (textlen + 1) * sizeof(wchar_t));
-
MultiByteToWideChar(CP_ACP, 0, str, -1, (LPWSTR)result, textlen);
-
return result;
-
}
-
char* CHttpClient::UnicodeToUTF8(const wchar_t* str)
-
{
-
char* result;
-
int textlen;
-
textlen = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
-
result = (char *)malloc((textlen + 1) * sizeof(char));
-
memset(result, 0, sizeof(char) * (textlen + 1));
-
WideCharToMultiByte(CP_UTF8, 0, str, -1, result, textlen, NULL, NULL);
-
return result;
-
}
-
char* CHttpClient::UnicodeToANSI(const wchar_t* str)
-
{
-
char* result;
-
int textlen;
-
textlen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);
-
result = (char *)malloc((textlen + 1) * sizeof(char));
-
memset(result, 0, sizeof(char) * (textlen + 1));
-
WideCharToMultiByte(CP_ACP, 0, str, -1, result, textlen, NULL, NULL);
-
return result;
-
}
仔细观察上面所说函数,就可以看到在ExecuteRequest函数中处理了多字节转为utf8格式,后面单独写了几种其他转码方式,这几种方式在转码时总觉得有问题,详细介绍大家参考以上内容。
后面说下json数据,这里我们的http使用的是get请求方式,贴一份我查询会议的代码,内容相对全些,后面再介绍。
-
int CDatabaseManage::QueryMRInBooked( CString roomId,CString data ,HWND hwnd)
-
{
-
TMRBookedInfo tmrBookedInfo;
-
TMRBookedInfoArray tmrBookedInfoArray;
-
tmrBookedInfoArray.clear();
-
UINT uiRet = -1;
-
if (m_httpCtrl == NULL)
-
{
-
m_httpCtrl = new CHttpClient();
-
}
-
CString strUrl,strPostData ;
-
strUrl.Format(_T("http://此处是ip地址:端口号/QueryConferenceRecord?content={\"Conference_Index\":\"%s\",\"RecordTime\":\"%s\"}"),roomId,data);
-
string strResponse="";
-
uiRet = m_httpCtrl->HttpGet(strUrl,strPostData,strResponse);
-
if ((uiRet == SUCCESS) &&(!strResponse.empty()))
-
{
-
Json::Value root;
-
Json::Reader reader;
-
if (reader.parse(strResponse,root,false))
-
{
-
if (root.isMember("content"))
-
{
-
Json::Value jsResult=root["content"];
-
if (jsResult.size()==0)
-
{
-
return 0;
-
}
-
uiRet = jsResult.size();
-
for(int i=0;i<jsResult.size();i++)
-
{
-
if (jsResult[i].isMember("Index"))
-
{
-
tmrBookedInfo.uiIdx = jsResult[i]["Index"].asInt();
-
}
-
if (jsResult[i].isMember("Subject"))
-
{}
-
tmrBookedInfo.strObj = jsResult[i]["Subject"].asCString();
-
}
-
if (jsResult[i].isMember("BeginTime"))
-
{
-
tmrBookedInfo.uiBeginTime = jsResult[i]["BeginTime"].asCString();
-
}
-
if (jsResult[i].isMember("EndTime"))
-
{
-
tmrBookedInfo.uiEndTime = jsResult[i]["EndTime"].asCString();
-
}
-
if (jsResult[i].isMember("UserName"))
-
{
-
tmrBookedInfo.uiUserName =jsResult[i]["UserName"].asCString();
-
}
-
tmrBookedInfoArray.push_back(tmrBookedInfo);
-
}
-
::SendMessage(hwnd,CM_SHOWRECORD,(WPARAM)&tmrBookedInfoArray,0);
-
}
-
}
-
}
-
return uiRet;
-
}
在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.消息头设置
-
struct curl_slist *headers=NULL; /* init to NULL is important */
-
headers = curl_slist_append(headers, "Hey-server-hey: how are you?");
-
headers = curl_slist_append(headers, "X-silly-content: yes");
-
/* pass our list of custom made headers */
-
curl_easy_setopt(easyhandle, CURLOPT_HTTPHEADER, headers);
-
curl_easy_perform(easyhandle); /* transfer http */
-
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
下面是完整通信设置代码
-
//http回调写函数
-
static size_t CurlWriteBuffer(char *buffer, size_t size, size_t nmemb, std::string* stream)
-
{
-
//第二个参数为每个数据的大小,第三个为数据个数,最后一个为接收变量
-
size_t sizes = size*nmemb;
-
if(stream == NULL)
-
return 0;
-
stream->append(buffer,sizes);
-
return sizes;
-
}
-
//http发送封装
-
int CPrinterDlg::posturl(std::string& msg, std::string& url, bool IsSSL)
-
{
-
CURL* pCurl=NULL; //一个libcurl的handle
-
CURLcode res; //返回状态码
-
std::string response; //返回信息
-
curl_global_init(CURL_GLOBAL_ALL); //全局初始化
-
pCurl = curl_easy_init(); //创建一个handle
-
//设置请求头
-
struct curl_slist* header_ = NULL;
-
header_ = curl_slist_append(header_,"Content-Type: application/json;charset=utf-8");
-
//添加请求头到handle
-
curl_easy_setopt(pCurl, CURLOPT_HTTPHEADER, header_);
-
//设置URL
-
curl_easy_setopt(pCurl, CURLOPT_URL, url.c_str());
-
CURLOPT_WRITEFUNCTION 将后继的动作交给write_data函数处理
-
curl_easy_setopt(pCurl,CURLOPT_POSTFIELDS,msg.c_str()); //post请求消息数据
-
curl_easy_setopt(pCurl,CURLOPT_POSTFIELDSIZE,msg.length()); //消息长度
-
curl_easy_setopt(pCurl, CURLOPT_WRITEFUNCTION, CurlWriteBuffer); //回调函数
-
curl_easy_setopt(pCurl,CURLOPT_WRITEDATA,&response); //数据接收变量
-
curl_easy_setopt(pCurl,CURLOPT_TIMEOUT,m_settinginfo.m_http_timeout); //连接超时时间
-
//不支持ssl验证
-
if(m_settinginfo.m_ssl == 0)
-
{
-
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0);//设定为不验证证书和HOST
-
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0);
-
}
-
else
-
{
-
// 配置 https 请求所需证书
-
if (m_settinginfo.m_ssl == 1) //ssl单向验证,不验证服务器
-
{
-
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 0L);
-
}else
-
{//双向验证
-
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYPEER, 1L);
-
curl_easy_setopt(pCurl, CURLOPT_SSL_VERIFYHOST, 0);
-
curl_easy_setopt(pCurl,CURLOPT_CAINFO,ca_info.ca_path.c_str());
-
}
-
//设置客户端信息
-
curl_easy_setopt(pCurl, CURLOPT_SSLCERT, ca_info.client_cert_path.c_str());
-
curl_easy_setopt(pCurl,CURLOPT_SSLCERTTYPE,"PEM");
-
curl_easy_setopt(pCurl, CURLOPT_SSLKEY, ca_info.client_key_path.c_str());
-
curl_easy_setopt(pCurl,CURLOPT_SSLKEYTYPE,"PEM");
-
//如果客户端证书密钥使用密码加密,设置加密密码
-
//curl_easy_setopt(pCurl, CURLOPT_KEYPASSWD, "your_key_password");
-
}
-
//执行http连接
-
res = curl_easy_perform(pCurl);
-
//清除消息头
-
curl_slist_free_all(header_);
-
//清除handle
-
curl_easy_cleanup(pCurl);
-
return 0;
-
}改变自己,从现在做起-----------久馆
posted on 2020-12-18 20:46 DanielandCalf 阅读(1236) 评论(0) 编辑 收藏 举报