在C++中使用HP-Socket
1、簡介
HP-Socket
是一套通用的高性能 TCP/UDP /HTTP
通信 框架 ,包含服務(wù)端組件、客戶端組件和 Agent
組件,廣泛適用于各種不同應(yīng)用場景的 TCP/UDP /HTTP
通信系統(tǒng),提供 C/C++
、 C#
、 Delphi
、 E
(易語言)、 Java
、 Python
等編程語言接口。
HP-Socket
是一套國產(chǎn)的開源通訊庫,使用C++語言實現(xiàn),提供多種編程語言的接口,支持 Windows 和 Linux 平臺:
- 官網(wǎng):http://www.hpsocket.net/
- github:https://github.com/ldcsaa/HP-Socket
HP-Socket
包含30多個組件 ,可根據(jù)通信角色Client/Server
)、通信協(xié)議TCP/UDP/HTTP)和接收模型PUSH/PULL/PACK
)進行歸類,這里只簡單介紹一下:
Server
組件:基于IOCP/EPOLL
通信模型 ,并結(jié)合緩存池 、私有堆等技術(shù)實現(xiàn)高效內(nèi)存管理,支持超大規(guī)模、高并發(fā)通信場景。Agent
組件:實質(zhì)上是Multi-Client
組件,與Server
組件采用相同的技術(shù)架構(gòu),可同時建立和高效處理大規(guī)模Socket連接 。Client
組件:基于Event Select/POLL
通信模型,每個組件對象創(chuàng)建一個通信線程并管理一個Socket
連接, 適用于小規(guī)??蛻舳藞鼍?。Thread Pool
組件:HP-Socket
實現(xiàn)的高效易用的線程池組件,當成普通的第三方線程池庫使用即可。
HP-Socket的TCP組件支持PUSH、PULL和PACK三種接收模型:
PUSH
模型:組件接收到數(shù)據(jù)時會觸發(fā)監(jiān)聽器對象的OnReceive(pSender,dwConnID,pData,iLength)事件,把數(shù)據(jù)“推”給應(yīng)用程序,這種模型使用起來是最自由的。PULL
模型:組件接收到數(shù)據(jù)時會觸發(fā)監(jiān)聽器對象的OnReceive(pSender,dwConnID,iTotalLength)事件 ,告訴應(yīng)用程序當前已經(jīng)接收到多少數(shù)據(jù),應(yīng)用程序檢查數(shù)據(jù)的長度,如果滿足需要則調(diào)用組件的**Fetch(dwConnID,pData,iDataLength)方法把需- 要的數(shù)據(jù)“拉”出來。
PACK
模型:PACK模型系列組件是PUSH和PULL模型的結(jié)合體,應(yīng)用程序不必處理分包與數(shù)據(jù)抓取,組件保證每個OnReceive
事件都向應(yīng)用程序提供一個完整數(shù)據(jù)包。
注:PACK模型組件會對應(yīng)用程序發(fā)送的每個數(shù)據(jù)包自動加上 4 字節(jié)(32位的包頭),前10位為用于數(shù)據(jù)包校驗的包頭標識位,后22位為記錄包體長度的長度位。
2、使用方式
HP-Socket支持MBCS和Unicode字符集,支持32位和64位應(yīng)用程序。可以通過源代碼、 DLL或LIB方式使用HP-Socket。 HP-Socket發(fā)行包中已經(jīng)提供了HPSocket DLL和HPSocket4C DLL。
HP-Socket提供了各種情況下的dll文件,不需要我們重新編譯,dll文件按編程接口分為兩大類:
HPSocket DLL
:導出C++編程接口 ,C++程序的首選方式,使用時需要把SocketInterface.h(及其依賴文件HPTypeDef.h) 、HPSocket.h以及 DLL 對應(yīng)的 *.lib 文件加入到工程項目,用到SSL組件還需要HPSocket-SSL.h文件。
HPSocket4C DLL
:導出C編程接口,提供給C語言或其它編程語言使用,使用時需要把HPSocket4C.h以及 DLL 對應(yīng)的 *.lib 文件加入到工程項目,用到SSL組件還需要HPSocket4C-SSL.h文件。
3、實現(xiàn)簡單線程池
使用HP-Socket
的線程池組件可以在程序中實現(xiàn)一個簡單的、公用的線程池,TCP
通訊的斷線重連、發(fā)送心跳都會用到線程池。
線程池組件的主要函數(shù)如下:
Start
:啟動線程池,具體的使用可以參考源代碼的注釋。Submit
:提交任務(wù),主要使用BOOL Submit(fnTaskProc,pvArg,dwMaxWait=INFINITE),另一個函數(shù)重載是使用一個特殊的數(shù)據(jù)類型(把Socket任務(wù)參數(shù)和任務(wù)函數(shù)封裝成一個數(shù)據(jù)結(jié)構(gòu))作為參數(shù)。Stop
:關(guān)閉線程池,參數(shù)dwMaxWait代表最大等待時間(毫秒,默認: INFINITE ,一直等待)。
先實現(xiàn)線程池的CHPThreadPoolListene
r接口,然后構(gòu)造IHPThreadPool智能指針,后面線程池的操作都通過智能指針操作,
代碼如下:
class CHPThreadPoolListenerImpl : public CHPThreadPoolListener { private: void LogInfo(string logStr) { cout <<"ThreadPool " <<logStr << endl; } public: virtual void OnStartup(IHPThreadPool* pThreadPool) { LogInfo("線程池啟動"); } virtual void OnShutdown(IHPThreadPool* pThreadPool) { LogInfo("線程池啟動關(guān)閉"); } virtual void OnWorkerThreadStart(IHPThreadPool* pThreadPool, THR_ID dwThreadID) { LogInfo("[" + to_string(dwThreadID) + "] " + "工作線程啟動"); } virtual void OnWorkerThreadEnd(IHPThreadPool* pThreadPool, THR_ID dwThreadID) { LogInfo("[" + to_string(dwThreadID) + "] " + "工作線程退出"); } }; CHPThreadPoolListenerImpl ThreadPoolListener; //全局共享變量使用extern關(guān)鍵字修飾 extern CHPThreadPoolPtr ThreadPool(&ThreadPoolListener);
4、實現(xiàn)TCP客戶端
先實現(xiàn)一個打印函數(shù),顯示客戶端相關(guān)的信息,代碼如下:
void PrintInfo(ITcpClient* pSender, CONNID dwConnID) { char buffer[20]; TCHAR* ipAddr = buffer; int ipLen; USHORT port; pSender->GetLocalAddress(ipAddr, ipLen, port); cout << string(ipAddr,0,ipLen) << ":" << port << " " << " [" << dwConnID << "] -> "; pSender->GetRemoteHost(ipAddr, ipLen, port); cout << string(ipAddr, 0, ipLen) << ":" << port << " "; }
實現(xiàn)CTcpClientListener
監(jiān)聽接口,客戶端斷線后自動重連,以換行符分割接收到的字符串,
代碼如下:
bool SysExit = false; void ReConnect(ITcpClient* pSender) { while (pSender->GetState() != SS_STOPPED) { Sleep(10); } pSender->Start("127.0.0.1", 60000); } class CClientListenerImpl : public CTcpClientListener { public: virtual EnHandleResult OnConnect(ITcpClient* pSender, CONNID dwConnID) { PrintInfo(pSender, dwConnID); cout << "連接成功" << endl; return HR_OK; } string resStr = ""; string commStr=""; virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) { string str((char*)pData,0, iLength); resStr.append(str); int index; while (true) { index = resStr.find("\r\n"); if (index == -1)break; commStr = resStr.substr(0, index); resStr = resStr.substr(index +2, resStr.length() - (index +2)); if (commStr!="") { PrintInfo(pSender, dwConnID); cout << "收到分割字符串 " << commStr << endl; } } PrintInfo(pSender, dwConnID); cout << "數(shù)據(jù)接受 " << str << endl; return HR_OK; } virtual EnHandleResult OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) { resStr = ""; PrintInfo(pSender, dwConnID); cout << "連接斷開,"<< enOperation <<"操作導致錯誤,錯誤碼 " << iErrorCode<< endl; if (!SysExit) { ThreadPool->Submit((Fn_TaskProc)(&ReConnect), (PVOID)pSender); } return HR_OK; } };
循環(huán)輸入字符串發(fā)送服務(wù)端,代碼如下:
int main() { //啟動線程池 ThreadPool->Start(); CClientListenerImpl listener; CTcpClientPtr client(&listener); if (!client->Start("127.0.0.1", 60000)) { cout << "連接錯誤:" << client->GetLastError() << "-" << client->GetLastErrorDesc(); } string sendMsg; while (!SysExit) { cin >> sendMsg; if (sendMsg == "esc") { SysExit = true; break; } if (client->GetState() == SS_STARTED) { const BYTE* data = (BYTE*)(sendMsg.c_str()); if (client->Send(data, sizeof(data))) { PrintInfo(client, client->GetConnectionID()); cout << "發(fā)送成功 "<<sendMsg<<endl; } else { PrintInfo(client, client->GetConnectionID()); cout << "發(fā)送失敗,錯誤描述 " << client->GetLastError() << "-" << client->GetLastErrorDesc() << endl; } } else { PrintInfo(client, client->GetConnectionID()); cout << "無法發(fā)送,當前狀態(tài) " <<client->GetState()<< endl; } } client->Stop(); //關(guān)閉線程池 ThreadPool->Stop(); return 0; }
5、實現(xiàn)TCP服務(wù)端
先實現(xiàn)一個打印函數(shù),基本上和客戶端的相同,只有獲取本地IP的地方不同,
代碼如下:
void PrintInfo(ITcpServer* pSender, CONNID dwConnID) { char buffer[20]; TCHAR* ipAddr = buffer; int ipLen; USHORT port; pSender->GetListenAddress(ipAddr, ipLen, port); cout << string(ipAddr, 0, ipLen) << ":" << port << " " << "<- [" << dwConnID << "] "; pSender->GetRemoteAddress(dwConnID, ipAddr, ipLen, port); cout << string(ipAddr, 0, ipLen) << ":" << port << " "; }
為了演示客戶端和應(yīng)用數(shù)據(jù)的綁定,定義一個用戶數(shù)據(jù)類型并創(chuàng)建一個隊列,代碼如下:
class UserData { public: UserData(string name="") { Name = name; } string Name; }; queue<UserData*> qName; //創(chuàng)建隊列對象
實現(xiàn)CTcpServerListener
監(jiān)聽接口,收到字符串后加上用戶名再發(fā)送回去,
代碼如下:
class CTcpServerListenerImpl : public CTcpServerListener { public: virtual EnHandleResult OnAccept(ITcpServer* pSender, CONNID dwConnID, UINT_PTR soClient) { pSender->SetConnectionExtra(dwConnID,qName.front()); qName.pop(); PrintInfo(pSender, dwConnID); cout << "連接成功" << endl; return HR_OK; } virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) { string str((char*)pData, 0, iLength); PrintInfo(pSender, dwConnID); cout << "數(shù)據(jù)接受 " << str<<endl; PVOID pInfo = nullptr; pSender->GetConnectionExtra(dwConnID, &pInfo); str = "reply-" + ((UserData*)pInfo)->Name + str; const BYTE* data = (BYTE*)(str.c_str()); pSender->Send(dwConnID, data,str.size()); return HR_OK; } virtual EnHandleResult OnClose(ITcpServer* pSender, CONNID dwConnID, EnSocketOperation enOperation, int iErrorCode) { PVOID pInfo = nullptr; pSender->GetConnectionExtra(dwConnID, &pInfo); qName.push((UserData*)pInfo); PrintInfo(pSender, dwConnID); cout << "斷開連接"<< endl; pSender->SetConnectionExtra(dwConnID, NULL); return HR_OK; } };
循環(huán)輸入字符串發(fā)送到客戶端,自動回復客戶端發(fā)送的消息,代碼如下:
bool SysExit = false; int main() { UserData user1("NO1-User"); UserData user2("NO2-User"); UserData user3("NO3-User"); UserData user4("NO4-User"); qName.push(&user1); qName.push(&user2); qName.push(&user3); qName.push(&user4); CTcpServerListenerImpl listener; CTcpServerPtr server(&listener); if (!server->Start("127.0.0.1", 60000)) { cout << "啟動錯誤:" << server->GetLastError() << "-" << server->GetLastErrorDesc(); } string sendMsg; while (!SysExit) { cin >> sendMsg; if (sendMsg == "esc") { SysExit = true; break; } //如果數(shù)組長度小于當前連接數(shù)量,則獲取失敗 DWORD count= 1000; CONNID pIDs[1000]; ZeroMemory(pIDs, 1000);; if (server->GetAllConnectionIDs(pIDs, count)&& count >0) { for (size_t i = 0; i < count; i++) { const BYTE* data = (BYTE*)(sendMsg.c_str()); if (server->Send(*(pIDs+i),data, sendMsg.size())) { PrintInfo(server, pIDs[i]); cout << "發(fā)送成功 " << sendMsg << endl; } else { PrintInfo(server, pIDs[i]); cout << "發(fā)送失敗,錯誤描述 " << server->GetLastError() << "-" << server->GetLastErrorDesc() << endl; } } } else { cout << "無法發(fā)送,當前連接數(shù) " << count << endl; } } server->Stop(); }
注:獲取連接時指針數(shù)組的長度一定要大于當前連接數(shù)量,否則會失敗。
6、實現(xiàn)Http客戶端
HP-Socket
的Http
客戶端有同步、異步兩種,同步客戶端不需要綁定監(jiān)聽器,這里使用同步客戶端演示。
Sync Client:同步HTTP客戶端組件(CHttpSyncClient和CHttpsSyncClient)內(nèi)部會處理所有事件,因此,它們不需要綁定監(jiān)聽器(構(gòu)造方法的監(jiān)聽器參數(shù)傳入null); 如果綁定了監(jiān)聽器則可以跟蹤組件的通信過程。
測試客戶端可以使用實時天氣接口上面的測試示例,當前的測試示例為:
http://api.k780.com/?app=weather.today&weaId=1&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json
直接開始測試,代碼如下:
int main() { CHttpSyncClientPtr SyncClient; THeader type; type.name = "Content-Type"; type.value = "text/html;charset=UTF-8"; if (SyncClient->OpenUrl("GET", "http://api.k780.com/?app=weather.today&weaId=1&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json",&type)) { LPCBYTE pData = nullptr; int iLength = 0; SyncClient->GetResponseBody(&pData, &iLength); string body((char*)pData, iLength); //返回的有中文,需要轉(zhuǎn)化編碼格式 cout << body << endl; cout << endl; cout << StringToUtf(body) << endl; cout << endl; cout << UtfToString(StringToUtf(body)) << endl; } else { cout << "打開失敗:"<<SyncClient->GetLastError()<<"-"<< SyncClient->GetLastErrorDesc()<<endl; } }
上面的StringToUtf
和UtfToString
函數(shù)是轉(zhuǎn)載至C++
中文亂碼的問題,該函數(shù)實現(xiàn)UTF-8
和ANSI
編碼格式的轉(zhuǎn)化,
代碼如下:
string UtfToString(string strValue) { int nwLen = ::MultiByteToWideChar(CP_ACP, 0, strValue.c_str(), -1, NULL, 0); wchar_t* pwBuf = new wchar_t[nwLen + 1];//加上末尾'\0' ZeroMemory(pwBuf, nwLen * 2 + 2); ::MultiByteToWideChar(CP_ACP, 0, strValue.c_str(), strValue.length(), pwBuf, nwLen); int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL); char* pBuf = new char[nLen + 1]; ZeroMemory(pBuf, nLen + 1); ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL); std::string retStr(pBuf); delete[]pwBuf; delete[]pBuf; pwBuf = NULL; pBuf = NULL; return retStr; } string StringToUtf(string strValue) { int nwLen = MultiByteToWideChar(CP_UTF8, 0, strValue.c_str(), -1, NULL, 0); wchar_t* pwBuf = new wchar_t[nwLen + 1];//加上末尾'\0' memset(pwBuf, 0, nwLen * 2 + 2); MultiByteToWideChar(CP_UTF8, 0, strValue.c_str(), strValue.length(), pwBuf, nwLen); int nLen = WideCharToMultiByte(CP_ACP, 0, pwBuf, -1, NULL, NULL, NULL, NULL); char* pBuf = new char[nLen + 1]; memset(pBuf, 0, nLen + 1); WideCharToMultiByte(CP_ACP, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL); std::string retStr = pBuf; delete[]pBuf; delete[]pwBuf; return retStr; }
注:函數(shù)實現(xiàn)需放在main函數(shù)之前。
到此這篇關(guān)于在C++中使用HP-Socket的文章就介紹到這了,更多相關(guān)C++ 使用HP-Socket內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
附件:
HP-Socket-5.8.5 源碼+dll+文檔 提取碼: 2uyv
項目源碼 提取碼: 2uyv
到此這篇關(guān)于在C++中使用HP-Socket的文章就介紹到這了,更多相關(guān)C++ 使用HP-Socket內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
clion最新激活碼+漢化的步驟詳解(親測可用激活到2089)
這篇文章主要介紹了clion最新版下載安裝+破解+漢化的步驟詳解,本文分步驟給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11c語言實現(xiàn)24小時制轉(zhuǎn)換為12小時制示例
這篇文章主要介紹了c語言實現(xiàn)24小時制轉(zhuǎn)換為12小時制示例,需要的朋友可以參考下2014-04-04C++實現(xiàn)折半插入排序(BinaryInsertSort)
這篇文章主要為大家詳細介紹了C++實現(xiàn)折半插入排序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-04-04C++中使用FFmpeg適配自定義編碼器的實現(xiàn)方法
本文介紹了在C++中使用FFmpeg庫進行自定義編碼器適配的實現(xiàn)方法。文章通過具體的代碼示例,介紹了FFmpeg的基本使用方法和自定義編碼器的實現(xiàn)過程,幫助讀者了解如何在C++中進行音視頻編碼和解碼的開發(fā)工作,并能夠?qū)崿F(xiàn)自定義的編碼器適配2023-04-04數(shù)據(jù)結(jié)構(gòu)與算法中二叉樹子結(jié)構(gòu)的詳解
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)與算法中二叉樹子結(jié)構(gòu)的詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04