在C++中使用HP-Socket
1、簡(jiǎn)介
HP-Socket 是一套通用的高性能 TCP/UDP /HTTP 通信 框架 ,包含服務(wù)端組件、客戶(hù)端組件和 Agent 組件,廣泛適用于各種不同應(yīng)用場(chǎng)景的 TCP/UDP /HTTP 通信系統(tǒng),提供 C/C++ 、 C# 、 Delphi 、 E (易語(yǔ)言)、 Java 、 Python 等編程語(yǔ)言接口。
HP-Socket是一套國(guó)產(chǎn)的開(kāi)源通訊庫(kù),使用C++語(yǔ)言實(shí)現(xiàn),提供多種編程語(yǔ)言的接口,支持 Windows 和 Linux 平臺(tái):
- 官網(wǎng):http://www.hpsocket.net/
- github:https://github.com/ldcsaa/HP-Socket
HP-Socket包含30多個(gè)組件 ,可根據(jù)通信角色Client/Server)、通信協(xié)議TCP/UDP/HTTP)和接收模型PUSH/PULL/PACK)進(jìn)行歸類(lèi),這里只簡(jiǎn)單介紹一下:
Server組件:基于IOCP/EPOLL通信模型 ,并結(jié)合緩存池 、私有堆等技術(shù)實(shí)現(xiàn)高效內(nèi)存管理,支持超大規(guī)模、高并發(fā)通信場(chǎng)景。Agent組件:實(shí)質(zhì)上是Multi-Client組件,與Server組件采用相同的技術(shù)架構(gòu),可同時(shí)建立和高效處理大規(guī)模Socket連接 。Client組件:基于Event Select/POLL通信模型,每個(gè)組件對(duì)象創(chuàng)建一個(gè)通信線(xiàn)程并管理一個(gè)Socket連接, 適用于小規(guī)??蛻?hù)端場(chǎng)景。Thread Pool組件:HP-Socket實(shí)現(xiàn)的高效易用的線(xiàn)程池組件,當(dāng)成普通的第三方線(xiàn)程池庫(kù)使用即可。
HP-Socket的TCP組件支持PUSH、PULL和PACK三種接收模型:
PUSH模型:組件接收到數(shù)據(jù)時(shí)會(huì)觸發(fā)監(jiān)聽(tīng)器對(duì)象的OnReceive(pSender,dwConnID,pData,iLength)事件,把數(shù)據(jù)“推”給應(yīng)用程序,這種模型使用起來(lái)是最自由的。PULL模型:組件接收到數(shù)據(jù)時(shí)會(huì)觸發(fā)監(jiān)聽(tīng)器對(duì)象的OnReceive(pSender,dwConnID,iTotalLength)事件 ,告訴應(yīng)用程序當(dāng)前已經(jīng)接收到多少數(shù)據(jù),應(yīng)用程序檢查數(shù)據(jù)的長(zhǎng)度,如果滿(mǎn)足需要?jiǎng)t調(diào)用組件的**Fetch(dwConnID,pData,iDataLength)方法把需- 要的數(shù)據(jù)“拉”出來(lái)。
PACK模型:PACK模型系列組件是PUSH和PULL模型的結(jié)合體,應(yīng)用程序不必處理分包與數(shù)據(jù)抓取,組件保證每個(gè)OnReceive事件都向應(yīng)用程序提供一個(gè)完整數(shù)據(jù)包。
注:PACK模型組件會(huì)對(duì)應(yīng)用程序發(fā)送的每個(gè)數(shù)據(jù)包自動(dòng)加上 4 字節(jié)(32位的包頭),前10位為用于數(shù)據(jù)包校驗(yàn)的包頭標(biāo)識(shí)位,后22位為記錄包體長(zhǎng)度的長(zhǎng)度位。
2、使用方式
HP-Socket支持MBCS和Unicode字符集,支持32位和64位應(yīng)用程序??梢酝ㄟ^(guò)源代碼、 DLL或LIB方式使用HP-Socket。 HP-Socket發(fā)行包中已經(jīng)提供了HPSocket DLL和HPSocket4C DLL。
HP-Socket提供了各種情況下的dll文件,不需要我們重新編譯,dll文件按編程接口分為兩大類(lèi):
HPSocket DLL:導(dǎo)出C++編程接口 ,C++程序的首選方式,使用時(shí)需要把SocketInterface.h(及其依賴(lài)文件HPTypeDef.h) 、HPSocket.h以及 DLL 對(duì)應(yīng)的 *.lib 文件加入到工程項(xiàng)目,用到SSL組件還需要HPSocket-SSL.h文件。
HPSocket4C DLL:導(dǎo)出C編程接口,提供給C語(yǔ)言或其它編程語(yǔ)言使用,使用時(shí)需要把HPSocket4C.h以及 DLL 對(duì)應(yīng)的 *.lib 文件加入到工程項(xiàng)目,用到SSL組件還需要HPSocket4C-SSL.h文件。

3、實(shí)現(xiàn)簡(jiǎn)單線(xiàn)程池
使用HP-Socket的線(xiàn)程池組件可以在程序中實(shí)現(xiàn)一個(gè)簡(jiǎn)單的、公用的線(xiàn)程池,TCP通訊的斷線(xiàn)重連、發(fā)送心跳都會(huì)用到線(xiàn)程池。

線(xiàn)程池組件的主要函數(shù)如下:
Start:?jiǎn)?dòng)線(xiàn)程池,具體的使用可以參考源代碼的注釋。Submit:提交任務(wù),主要使用BOOL Submit(fnTaskProc,pvArg,dwMaxWait=INFINITE),另一個(gè)函數(shù)重載是使用一個(gè)特殊的數(shù)據(jù)類(lèi)型(把Socket任務(wù)參數(shù)和任務(wù)函數(shù)封裝成一個(gè)數(shù)據(jù)結(jié)構(gòu))作為參數(shù)。Stop:關(guān)閉線(xiàn)程池,參數(shù)dwMaxWait代表最大等待時(shí)間(毫秒,默認(rèn): INFINITE ,一直等待)。
先實(shí)現(xiàn)線(xiàn)程池的CHPThreadPoolListener接口,然后構(gòu)造IHPThreadPool智能指針,后面線(xiàn)程池的操作都通過(guò)智能指針操作,
代碼如下:
class CHPThreadPoolListenerImpl : public CHPThreadPoolListener
{
private:
void LogInfo(string logStr)
{
cout <<"ThreadPool " <<logStr << endl;
}
public:
virtual void OnStartup(IHPThreadPool* pThreadPool)
{
LogInfo("線(xiàn)程池啟動(dòng)");
}
virtual void OnShutdown(IHPThreadPool* pThreadPool)
{
LogInfo("線(xiàn)程池啟動(dòng)關(guān)閉");
}
virtual void OnWorkerThreadStart(IHPThreadPool* pThreadPool, THR_ID dwThreadID)
{
LogInfo("[" + to_string(dwThreadID) + "] " + "工作線(xiàn)程啟動(dòng)");
}
virtual void OnWorkerThreadEnd(IHPThreadPool* pThreadPool, THR_ID dwThreadID)
{
LogInfo("[" + to_string(dwThreadID) + "] " + "工作線(xiàn)程退出");
}
};
CHPThreadPoolListenerImpl ThreadPoolListener;
//全局共享變量使用extern關(guān)鍵字修飾
extern CHPThreadPoolPtr ThreadPool(&ThreadPoolListener);
4、實(shí)現(xiàn)TCP客戶(hù)端
先實(shí)現(xiàn)一個(gè)打印函數(shù),顯示客戶(hù)端相關(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 << " ";
}
實(shí)現(xiàn)CTcpClientListener監(jiān)聽(tīng)接口,客戶(hù)端斷線(xiàn)后自動(dòng)重連,以換行符分割接收到的字符串,
代碼如下:
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 << "連接斷開(kāi),"<< enOperation <<"操作導(dǎo)致錯(cuò)誤,錯(cuò)誤碼 " << iErrorCode<< endl;
if (!SysExit)
{
ThreadPool->Submit((Fn_TaskProc)(&ReConnect), (PVOID)pSender);
}
return HR_OK;
}
};
循環(huán)輸入字符串發(fā)送服務(wù)端,代碼如下:
int main()
{
//啟動(dòng)線(xiàn)程池
ThreadPool->Start();
CClientListenerImpl listener;
CTcpClientPtr client(&listener);
if (!client->Start("127.0.0.1", 60000))
{
cout << "連接錯(cuò)誤:" << 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ā)送失敗,錯(cuò)誤描述 " << client->GetLastError() << "-" << client->GetLastErrorDesc() << endl;
}
}
else
{
PrintInfo(client, client->GetConnectionID());
cout << "無(wú)法發(fā)送,當(dāng)前狀態(tài) " <<client->GetState()<< endl;
}
}
client->Stop();
//關(guān)閉線(xiàn)程池
ThreadPool->Stop();
return 0;
}
5、實(shí)現(xiàn)TCP服務(wù)端
先實(shí)現(xiàn)一個(gè)打印函數(shù),基本上和客戶(hù)端的相同,只有獲取本地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 << " ";
}
為了演示客戶(hù)端和應(yīng)用數(shù)據(jù)的綁定,定義一個(gè)用戶(hù)數(shù)據(jù)類(lèi)型并創(chuàng)建一個(gè)隊(duì)列,代碼如下:
class UserData
{
public:
UserData(string name="")
{
Name = name;
}
string Name;
};
queue<UserData*> qName; //創(chuàng)建隊(duì)列對(duì)象
實(shí)現(xiàn)CTcpServerListener監(jiān)聽(tīng)接口,收到字符串后加上用戶(hù)名再發(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 << "斷開(kāi)連接"<< endl;
pSender->SetConnectionExtra(dwConnID, NULL);
return HR_OK;
}
};
循環(huán)輸入字符串發(fā)送到客戶(hù)端,自動(dòng)回復(fù)客戶(hù)端發(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 << "啟動(dòng)錯(cuò)誤:" << server->GetLastError() << "-" << server->GetLastErrorDesc();
}
string sendMsg;
while (!SysExit)
{
cin >> sendMsg;
if (sendMsg == "esc")
{
SysExit = true;
break;
}
//如果數(shù)組長(zhǎng)度小于當(dāng)前連接數(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ā)送失敗,錯(cuò)誤描述 " << server->GetLastError() << "-" << server->GetLastErrorDesc() << endl;
}
}
}
else
{
cout << "無(wú)法發(fā)送,當(dāng)前連接數(shù) " << count << endl;
}
}
server->Stop();
}
注:獲取連接時(shí)指針數(shù)組的長(zhǎng)度一定要大于當(dāng)前連接數(shù)量,否則會(huì)失敗。
6、實(shí)現(xiàn)Http客戶(hù)端
HP-Socket的Http客戶(hù)端有同步、異步兩種,同步客戶(hù)端不需要綁定監(jiān)聽(tīng)器,這里使用同步客戶(hù)端演示。
Sync Client:同步HTTP客戶(hù)端組件(CHttpSyncClient和CHttpsSyncClient)內(nèi)部會(huì)處理所有事件,因此,它們不需要綁定監(jiān)聽(tīng)器(構(gòu)造方法的監(jiān)聽(tīng)器參數(shù)傳入null); 如果綁定了監(jiān)聽(tīng)器則可以跟蹤組件的通信過(guò)程。
測(cè)試客戶(hù)端可以使用實(shí)時(shí)天氣接口上面的測(cè)試示例,當(dāng)前的測(cè)試示例為:
http://api.k780.com/?app=weather.today&weaId=1&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json
直接開(kāi)始測(cè)試,代碼如下:
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 << "打開(kāi)失?。?<<SyncClient->GetLastError()<<"-"<< SyncClient->GetLastErrorDesc()<<endl;
}
}
上面的StringToUtf和UtfToString函數(shù)是轉(zhuǎn)載至C++ 中文亂碼的問(wèn)題,該函數(shù)實(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ù)實(shí)現(xiàn)需放在main函數(shù)之前。
到此這篇關(guān)于在C++中使用HP-Socket的文章就介紹到這了,更多相關(guān)C++ 使用HP-Socket內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
附件:
HP-Socket-5.8.5 源碼+dll+文檔 提取碼: 2uyv
項(xiàng)目源碼 提取碼: 2uyv
到此這篇關(guān)于在C++中使用HP-Socket的文章就介紹到這了,更多相關(guān)C++ 使用HP-Socket內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
clion最新激活碼+漢化的步驟詳解(親測(cè)可用激活到2089)
這篇文章主要介紹了clion最新版下載安裝+破解+漢化的步驟詳解,本文分步驟給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
C++?超詳細(xì)分析多態(tài)的原理與實(shí)現(xiàn)
這篇文章主要介紹了C++多態(tài)的原理與實(shí)現(xiàn),多態(tài)是一種面向?qū)ο蟮脑O(shè)計(jì)思路,本身和C++不是強(qiáng)綁定的,其他語(yǔ)言當(dāng)中一樣有多態(tài),只不過(guò)實(shí)現(xiàn)的方式可能有所不同。下面來(lái)一起了解更多詳細(xì)內(nèi)容吧2022-03-03
c語(yǔ)言實(shí)現(xiàn)24小時(shí)制轉(zhuǎn)換為12小時(shí)制示例
這篇文章主要介紹了c語(yǔ)言實(shí)現(xiàn)24小時(shí)制轉(zhuǎn)換為12小時(shí)制示例,需要的朋友可以參考下2014-04-04
C++實(shí)現(xiàn)折半插入排序(BinaryInsertSort)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)折半插入排序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04
C++中使用FFmpeg適配自定義編碼器的實(shí)現(xiàn)方法
本文介紹了在C++中使用FFmpeg庫(kù)進(jìn)行自定義編碼器適配的實(shí)現(xiàn)方法。文章通過(guò)具體的代碼示例,介紹了FFmpeg的基本使用方法和自定義編碼器的實(shí)現(xiàn)過(guò)程,幫助讀者了解如何在C++中進(jìn)行音視頻編碼和解碼的開(kāi)發(fā)工作,并能夠?qū)崿F(xiàn)自定義的編碼器適配2023-04-04
數(shù)據(jù)結(jié)構(gòu)與算法中二叉樹(shù)子結(jié)構(gòu)的詳解
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)與算法中二叉樹(shù)子結(jié)構(gòu)的詳解的相關(guān)資料,需要的朋友可以參考下2017-04-04

