Poco 与 libevent 网络编程
C++ 网络编程是后端开发的核心能力之一。本文将介绍两个重要的 C++ 网络框架------Poco C++ 框架和 libevent 网络库,涵盖 Poco 框架的安装、字符串格式化、SocketReactor 网络编程模型,以及 libevent 的事件驱动网络 I/O 实战。
一、Poco C++ 框架
1.1 为什么选择 Poco
Poco(Portable Components,便携式组件)是一个被低估但非常实用的 C++ 框架。与其他主流框架相比:
| 框架 | 优点 | 缺点 |
|---|---|---|
| Boost | 大而全,标准库后备 | 臃肿,学习成本高,没有数据库/Redis 等支持 |
| Qt | 强大的 GUI 和工具类 | 始终离不开客户端开发,不适合后端 |
| STL | C++ 标准 | 太底层,开发应用需要编写的代码太多 |
| Poco | 实用、轻量、功能全面 | 国内使用较少,资料不多 |
Poco 的最大优势在于一个库覆盖大部分后端开发需求:数据库、Redis、串口、网络、HTTP 服务器/客户端,应有尽有。
1.2 安装 Poco
Linux(CMake 编译,推荐):
bash
mkdir cmake-build && cd cmake-build
cmake .. -DCMAKE_BUILD_TYPE=Release
gmake -j8
sudo gmake install
注意关注 CMake 中的
Could NOT find XXX提示,需要安装对应的依赖库。
Windows(vcpkg 安装,推荐新手):
bash
vcpkg install POCO
Android(交叉编译):
bash
cmake .. \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_ABI=armeabi-v7a \
-DANDROID_PLATFORM=android-23 \
-DBUILD_SHARED_LIBS=NO \
-DCMAKE_BUILD_TYPE=Release
1.3 Poco 字符串格式化
Poco 的格式化对类型要求非常严格,必须严格匹配类型控制符:
cpp
#include <Poco/Format.h>
// 类型控制符
// %d -> int, %u -> unsigned int
// %ld -> long, %Ld -> long long
// %f -> double, %s -> std::string(注意:不支持 const char*)
Int8 num1 = 45;
format("%d", num1); // 错误!
format("%d", (int)num1); // 正确
Int16 num2 = 45;
format("%d", num2); // 错误!
format("%d", (int)num2); // 正确
关键规则 :
%c只能用于 char,%d只能用于 int/int32,%s只能用于 std::string。
1.4 Poco 网络编程
Poco 的网络编程支持多种模型,其中 SocketReactor 是高性能方案:
cpp
#include <Poco/Net/StreamSocket.h>
#include <Poco/Net/SocketReactor.h>
#include <Poco/Net/SocketAddress.h>
using namespace Poco::Net;
// 创建套接字并连接
SocketAddress addr("192.168.1.1", 8999);
StreamSocket socket(addr);
// 使用 Reactor 模式
SocketReactor reactor;
// 添加事件处理器
reactor.addEventHandler(socket,
NObserver<EchoHandler, ReadableNotification>(
*this, &EchoHandler::onSocketReadable));
reactor.addEventHandler(socket,
NObserver<EchoHandler, WritableNotification>(
*this, &EchoHandler::onSocketWritable));
// 运行事件循环
reactor.run();
三个关键回调的理解:
| 回调 | 触发条件 | 注意事项 |
|---|---|---|
onSocketReadable |
缓冲区有数据时 | 如果数据未读完会持续回调;receiveData 返回 <=0 表示套接字异常 |
onSocketWritable |
套接字处于连接状态时 | 会持续回调,应配合发送队列使用,无数据时延时释放 CPU |
onSocketShutdown |
reactor.stop() 后调用 | 不是套接字关闭的回调,要看注释而非字面意思 |
1.5 Poco 智能指针
cpp
// AutoPtr:需要类继承 RefCountedObject 实现引用计数
class MyClass : public Poco::RefCountedObject {
// ...
};
Poco::AutoPtr<MyClass> ptr(new MyClass);
// AutoPtr 可实现 enable_shared_from_this 功能
// AutoPtr(C* ptr, bool shared) 构造函数是关键
// SharedPtr:类似 std::shared_ptr,但缺少 enable_shared_from_this
Poco::SharedPtr<MyClass> ptr(new MyClass);
二、libevent 网络库
libevent 是一个轻量级的事件驱动网络库,适合需要高性能网络 I/O 的场景。
2.1 TCP 客户端实现
cpp
#include <event2/event.h>
#include <event2/bufferevent.h>
// 创建事件基础结构
struct event_base *base = event_base_new();
// 创建 bufferevent(-1 表示尚未连接)
struct bufferevent *bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
// 解析服务器地址
struct sockaddr_in addr;
int len = sizeof(struct sockaddr);
evutil_parse_sockaddr_port("10.18.140.10:44992", (struct sockaddr*)&addr, &len);
// 设置回调函数
bufferevent_setcb(bev, read_cb, write_cb, event_cb, nullptr);
bufferevent_enable(bev, EV_READ | EV_WRITE);
// 发起连接
bufferevent_socket_connect(bev, (struct sockaddr*)&addr, len);
// 事件循环
event_base_dispatch(base);
// 清理资源
bufferevent_free(bev);
event_base_free(base);
2.2 数据读取回调
cpp
void read_cb(struct bufferevent *bev, void *ctx) {
auto input = bufferevent_get_input(bev);
// 检查数据长度是否满足最小包长度
if (evbuffer_get_length(input) < sizeof(package_head)) {
return;
}
// 读取包头
package_head head{};
evbuffer_copyout(input, &head, sizeof(head));
// 检查完整包是否到达
if (evbuffer_get_length(input) < sizeof(head) + head.message_len + 1) {
return;
}
// 读取完整数据包
int total = sizeof(head) + head.message_len + 1;
char *buf = new char[total]{};
evbuffer_copyout(input, buf, total);
// 移除已处理的数据
evbuffer_drain(input, total);
delete[] buf;
}
2.3 事件回调
cpp
void event_cb(struct bufferevent *bev, short what, void *ctx) {
if (what & BEV_EVENT_CONNECTED) {
printf("连接成功\n");
// 可以在这里创建定时器发送数据
}
if (what & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
printf("连接错误或断开\n");
event_base_loopexit(bufferevent_get_base(bev), nullptr);
}
}
总结
Poco 和 libevent 各有定位,适用于不同的网络编程场景:
| 对比维度 | Poco | libevent |
|---|---|---|
| 定位 | 全功能后端框架 | 轻量级事件驱动库 |
| 学习成本 | 中等 | 较低 |
| 功能覆盖 | 数据库、Redis、HTTP、串口等 | 专注网络 I/O 和定时器 |
| 性能模型 | SocketReactor(Reactor 模式) | 事件驱动(epoll/kqueue) |
| 适用场景 | 后端服务全栈开发 | 高性能网络 I/O 场景 |
在实际项目中,可以根据需求选择:如果需要完整的后端框架(含数据库、HTTP 等),选择 Poco;如果只需要高性能的网络 I/O 处理,libevent 是更轻量的选择。
来源:整理自 zyh/LearnPoco.cc、zyh/learn_libevent.cc 学习笔记,原载于《C++ 第三方库与框架实战》