C++ 中使用 HP-Socket 实现高并发 TCP/HTTP 通信
HP-Socket 是一套适用于 Windows 和 Linux 的高性能通信组件库。本文详细讲解如何将其引入 C++ 项目,包括引入 DLL 或源码、初始化线程池、构建具备自动重连功能的 TCP 客户端,以及支持多用户绑定的 TCP 服务端,同时演示如何通过同步 HTTP 客户端请求第三方接口并解决编码问题。
1. 简介与核心概念
HP-Socket 是一个国产开源的通用 TCP/UDP/HTTP 通信框架,采用 C++ 编写。它利用 IOCP(Windows)和 EPOLL(Linux)等底层模型,结合内存池技术,能够支撑大规模并发连接场景。该框架提供多种语言绑定,其中 C++ 版本提供了三种接收数据的模型:
- PUSH 模型:数据到达后直接推送到应用层,触发
OnReceive回调,灵活性最高。 - PULL 模型:仅通知数据已到达及其长度,需应用层主动调用
Fetch方法拉取数据。 - PACK 模型:封装了 TCP 粘包拆包逻辑,每收到一个完整数据包就触发一次
OnReceive。注意其会在每个包前自动附加 4 字节包头进行校验和长度记录。
在架构上,Server 端基于高性能异步模型;Agent 端本质上是一个管理多个连接的客户端代理;而 Client 端通常适用于一对一的小规模连接场景,基于 Event Select 或 POLL 模型。
2. 准备工作与环境集成
HP-Socket 支持 MBCS 和 Unicode 字符集,且提供 HPSocket.dll(C++ 接口)和 HPSocket4C.dll(C 接口)。对于 C++ 项目,推荐使用前者。
第1步:获取并引入文件
从官方仓库下载发行包,将以下头文件和库文件添加到工程中:
HPSocket.h及依赖项SocketInterface.h、HPTypeDef.h。- 对应平台的静态库文件(如
.lib),若不使用动态链接方式编译则必须引用。 - 如需 SSL 加密功能,额外添加
HPSocket-SSL.h。
3. 实现简单的公用线程池
在处理长连接时,断线重连和发送心跳通常需要异步操作。HP-Socket 内置的线程池组件 IHPThreadPool 可简化此过程。
第2步:定义线程池监听器
实现 CHPThreadPoolListener 接口以捕获生命周期事件,并通过智能指针管理线程池实例。
class CHPThreadPoolListenerImpl : public CHPThreadPoolListener {
private:
void LogInfo(string logStr) { cout << "ThreadPool " << logStr << endl; }
public:
virtual void OnStartup(IHPThreadPool* pThreadPool) override { LogInfo("启动"); }
virtual void OnShutdown(IHPThreadPool* pThreadPool) override { LogInfo("关闭"); }
virtual void OnWorkerThreadStart(IHPThreadPool* pThreadPool, THR_ID dwThreadID) override {
LogInfo("工作线程启动 ID:" + to_string(dwThreadID));
}
virtual void OnWorkerThreadEnd(IHPThreadPool* pThreadPool, THR_ID dwThreadID) override {
LogInfo("工作线程退出 ID:" + to_string(dwThreadID));
}
};
CHPThreadPoolListenerImpl ThreadPoolListener;
extern CHPThreadPoolPtr ThreadPool(&ThreadPoolListener);
第3步:控制线程池状态
调用 Start() 激活线程池;通过 Submit(fnTaskProc, pvArg) 提交任务(如重连逻辑);程序结束前调用 Stop(dwMaxWait) 等待未完成任务执行完毕。
4. 构建 TCP 客户端(含断线重连)
TCP 客户端主要关注连接的维持和数据包的解析。本例展示如何分割字符串以及在断开后自动调度重连任务。
第4步:定义辅助打印函数
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 << " -> ";
pSender->GetRemoteHost(ipAddr, ipLen, port);
cout << string(ipAddr, 0, ipLen) << ":" << port << endl;
}
第5步:实现监听器逻辑
在 OnClose 中判断非正常退出后,将重连任务提交至线程池。这里假设服务器地址为 127.0.0.1:60000。
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) override {
PrintInfo(pSender, dwConnID);
return HR_OK;
}
string resStr = "", commStr="";
virtual EnHandleResult OnReceive(ITcpClient* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {
string str((char*)pData, iLength);
resStr.append(str);
size_t index;
while ((index = resStr.find("\r\n")) != -1) {
commStr = resStr.substr(0, index);
resStr = resStr.substr(index + 2);
if (!commStr.empty()) PrintInfo(pSender, dwConnID); // 处理收到的分包
}
return HR_OK;
}
virtual EnHandleResult OnClose(ITcpClient* pSender, CONNID dwConnID, EnSocketOperation op, int errCode) override {
resStr.clear();
if (!SysExit) ThreadPool->Submit((Fn_TaskProc)&ReConnect, (PVOID)pSender);
return HR_OK;
}
};
第6步:主循环运行测试
连接成功后,通过标准输入发送消息。若当前状态不是开启状态,则无法发送。
int main() {
ThreadPool->Start();
CClientListenerImpl listener;
CTcpClientPtr client(&listener);
if (!client->Start("127.0.0.1", 60000)) {
// 处理启动失败
}
string msg; cin >> msg;
while (msg != "esc") {
const BYTE* data = (BYTE*)(msg.c_str());
client->Send(data, msg.size());
cin >> msg;
}
client->Stop();
ThreadPool->Stop();
return 0;
}
5. 构建 TCP 服务端
服务端需要管理多个客户端连接,并可为每个连接绑定自定义数据(Connection Extra)。
第7步:建立用户数据结构
class UserData { public: string Name; UserData(string n=""):Name(n){} };
queue<UserData*> qName; // 用于模拟分发不同用户的数据缓冲区
第8步:实现服务端的接收与响应逻辑
利用 SetConnectionExtra 在接入时绑定用户对象,在返回数据时将用户名拼接进回复内容。
class CTcpServerListenerImpl : public CTcpServerListener {
public:
virtual EnHandleResult OnAccept(ITcpServer* pSender, CONNID dwConnID, UINT_PTR socket) override {
pSender->SetConnectionExtra(dwConnID, qName.front());
qName.pop();
return HR_OK;
}
virtual EnHandleResult OnReceive(ITcpServer* pSender, CONNID dwConnID, const BYTE* pData, int iLength) override {
string raw((char*)pData, iLength);
PVOID pInfo = nullptr;
pSender->GetConnectionExtra(dwConnID, &pInfo);
string reply = "reply-" + ((UserData*)pInfo)->Name + raw;
pSender->Send(dwConnID, (const BYTE*)reply.c_str(), reply.size());
return HR_OK;
}
virtual EnHandleResult OnClose(ITcpServer* pSender, CONNID dwConnID, EnSocketOperation op, int errCode) override {
PVOID pInfo = nullptr;
pSender->GetConnectionExtra(dwConnID, &pInfo);
qName.push((UserData*)pInfo);
pSender->SetConnectionExtra(dwConnID, NULL);
return HR_OK;
}
};
服务端启动后,可通过 GetAllConnectionIDs 获取所有在线连接 ID,遍历向各个节点广播消息。
6. 同步 HTTP 客户端与编码转换
如果仅需访问 RESTful API 或简单网页,HP-Socket 提供了无需绑定复杂接口的同步 HTTP 客户端 CHttpSyncClient。
第9步:发起 HTTPS 请求
以下代码展示了使用公开天气 API 接口获取 JSON 数据的过程:
int main() {
CHttpSyncClientPtr SyncClient;
THeader type;
type.name = "Content-Type"; type.value = "text/html;charset=UTF-8";
// 注意 URL 编码中的特殊符号在运行时会被正确转义
if (SyncClient->OpenUrl("GET", "http://api.k780.com/?app=weather.today&weaId=1...", &type)) {
LPCBYTE pData = nullptr; int iLength = 0;
SyncClient->GetResponseBody(&pData, &iLength);
string body((char*)pData, iLength);
// 由于 Windows 控制台默认 ANSI,此处需进行 UTF8 到本地编码的转换
cout << UtfToString(body) << endl;
} else {
cout << "打开失败" << SyncClient->GetLastError() << endl;
}
}
第10步:解决中文乱码问题
API 返回通常为 UTF-8 格式,而 Visual Studio 环境下默认的 `string` 交互可能会遇到乱码。可借助 Windows API 进行如下转换:
string UtfToString(string strValue) {
int nwLen = MultiByteToWideChar(CP_ACP, 0, strValue.c_str(), -1, NULL, 0);
wchar_t* pwBuf = new wchar_t[nwLen + 1];
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;
return retStr;
}
// 反向转换 StringToUtf 类似,只是 CP_ACP 与 CP_UTF8 参数互换
以上就是在 C++ 中使用 HP-Socket 实现通信服务的详细内容,更多关于 C++ 高效 Socket 编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













