文章目录
核心流程
ZeroMQ 检测完整消息可读并通知用户的完整流程:
- 网络数据接收 :
stream_engine从网络读取数据 - 消息解析 :
decoder解析数据,检测完整消息 - 消息传递 :通过
pipe传递给session - 消息存储 :
socket将消息存储在内部队列 - 通知用户:通过文件描述符的可读事件通知用户
详细实现
1. 网络数据接收与解析
stream_engine 接收数据:
cpp
bool zmq::stream_engine_base_t::in_event_internal ()
{
// 读取网络数据
const int rc = read (_inpos, bufsize);
// 解码数据
int rc = _decoder->decode (_inpos, _insize);
// 消息就绪时
if (_decoder->message_ready ()) {
msg_t *msg = _decoder->msg ();
// 写入 pipe
_pipe->write (msg);
_pipe->flush ();
}
}
2. 消息传递到 Socket
session 处理:
cpp
void zmq::session_base_t::read_activated (zmq::pipe_t *pipe_)
{
// 从 pipe 读取消息
msg_t msg;
while (pull_msg (&msg) == 0) {
// 处理消息
// 写入 socket 的 pipe
}
}
socket 接收:
cpp
void zmq::socket_base_t::read_activated (zmq::pipe_t *pipe_)
{
// 从 pipe 读取消息
msg_t msg;
while (pipe_->read (&msg)) {
// 存储到内部队列
_fq.push (&msg);
// 通知用户(关键步骤)
if (_fq.size () == 1) {
// 第一次有消息时,通知用户
signaler->send ();
}
}
}
3. 用户层通知机制
文件描述符通知:
- ZeroMQ 为每个 socket 创建一个通知文件描述符
- 当有消息可读时,通过
signaler向该文件描述符写入数据 - 用户可以通过
ZMQ_FD获取该文件描述符 - 使用
poll/select等系统调用监听该文件描述符
用户 API:
cpp
// 获取通知文件描述符
zmq_fd_t fd;
size_t fd_size = sizeof (fd);
zmq_getsockopt (socket, ZMQ_FD, &fd, &fd_size);
// 监听文件描述符
struct pollfd pfd;
pfd.fd = fd;
pfd.events = POLLIN;
poll (&pfd, 1, -1);
// 检查是否有消息
uint32_t events;
size_t events_size = sizeof (events);
zmq_getsockopt (socket, ZMQ_EVENTS, &events, &events_size);
if (events & ZMQ_POLLIN) {
// 有消息可读
zmq_recv (socket, buffer, sizeof (buffer), 0);
}
4. zmq_poll 实现
socket_poller 检测:
cpp
int zmq::socket_poller_t::check_events (zmq::socket_poller_t::event_t *events_,
int n_events_)
{
int found = 0;
for (items_t::iterator it = _items.begin (), end = _items.end ();
it != end && found < n_events_; ++it) {
if (it->socket) {
// 检查 socket 事件
uint32_t events;
it->socket->getsockopt (ZMQ_EVENTS, &events, &events_size);
if (it->events & events) {
// 有事件发生
events_[found].socket = it->socket;
events_[found].events = it->events & events;
++found;
}
}
}
return found;
}
技术要点
1. 消息完整性检测
- decoder:负责解析网络数据,检测消息边界
- message_ready:当解析出完整消息时返回 true
- 帧边界:ZeroMQ 消息由多个帧组成,decoder 负责检测帧边界
2. 通知机制
- signaler:跨平台的信号机制,用于通知用户
- 文件描述符:每个 socket 对应一个通知文件描述符
- 事件状态 :通过
ZMQ_EVENTS获取 socket 的事件状态
3. 线程安全
- 线程安全 socket:使用 signaler 机制
- 非线程安全 socket:直接使用文件描述符
- poller:支持同时监听多个 socket
完整流程图示
┌─────────────────────────────────────────────────────────────────────┐
│ 消息可读通知流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 网络数据到达 │
│ └─> stream_engine::in_event() │
│ └─> 读取网络数据 │
│ └─> decoder::decode() 解析数据 │
│ └─> 检测消息完整性 │
│ └─> 完整消息就绪 │
│ │
│ 2. 消息传递 │
│ └─> _pipe->write(msg) 写入 pipe │
│ └─> session::read_activated() 处理 │
│ └─> socket::read_activated() 接收 │
│ └─> _fq.push(msg) 存储到队列 │
│ └─> signaler->send() 发送通知 │
│ │
│ 3. 用户检测 │
│ └─> poll() 检测文件描述符可读 │
│ └─> zmq_getsockopt(ZMQ_EVENTS) 检查事件 │
│ └─> zmq_recv() 读取消息 │
│ │
└─────────────────────────────────────────────────────────────────────┘
总结
ZeroMQ 检测完整消息可读并通知用户的机制:
- 消息解析 :
decoder解析网络数据,检测完整消息 - 消息存储:消息存储在 socket 的内部队列
- 通知触发 :通过
signaler触发文件描述符的可读事件 - 用户检测 :用户通过
poll或zmq_poll检测事件 - 消息读取 :调用
zmq_recv读取消息
这种设计确保了用户能够及时得知有消息可读,同时避免了忙等带来的性能损耗,是 ZeroMQ 高效通信的重要基础。