一、介紹下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;
-
}改變自己,從現在做起-----------久館