最近在使用ZMQ来做进程间通信,想起之前有看到基于Socket的select实现非阻塞的异步聊天,好奇能否基于ZMQ实现相同的功能,于是就查了一下相关的资料,写了一个简单的demo验证了一下。期间还因为zmq_poll不能直接监控标准输入,因此还查找了一下C++实现非阻塞输入的方法,相关代码直接引用了大佬的实现《C++ 中非阻塞式的用户输入》。好记性不如烂笔头,浅记一下。
废话不多说,直接贴代码
ZMQ源码地址
本文采用的是当前最新的版本4.3.6。
https://github.com/zeromq/libzmq.git
ZMQ接口API文档
https://libzmq.readthedocs.io/en/latest/
C++非阻塞式输入
// file: Async_input.h
#ifndef ASYNC_INPUT_H
#define ASYNC_INPUT_H
#include <iostream>
#include <optional>
std::optional<std::string> noblock_input(std::istream& in = std::cin);
#endif // ASYNC_INPUT_H
// file: Async_input.cpp
#include "Async_input.h"
std::optional<std::string> noblock_input(std::istream& in)
{
// 下面这个很关键
in.sync_with_stdio(false);
if (in.rdbuf()->in_avail() > 0) {
char buffer[1024];
in.getline(buffer, sizeof(buffer));
return buffer;
}
return {};
}
Server端
// file: Server.cpp
#include <iostream>
#include <thread>
#include <cstring>
#include <cassert>
#include "zmq.h"
#include "Async_input.h"
int main(int argc, char **argv)
{
void *context = zmq_ctx_new();
assert(context != NULL);
void *socket1 = zmq_socket(context, ZMQ_PAIR);
assert(socket1 != NULL);
void *socket2 = zmq_socket(context, ZMQ_PAIR);
assert(socket2 != NULL);
auto result = zmq_bind(socket1, "tcp://*:5000");
assert(result == 0);
result = zmq_bind(socket2, "tcp://*:6000");
assert(result == 0);
zmq_pollitem_t poll_items[2];
poll_items[0].socket = socket1;
poll_items[0].events = ZMQ_POLLIN;
poll_items[1].socket = socket2;
poll_items[1].events = ZMQ_POLLIN;
// std::cout << "sizeof(poll_items) / sizeof(zmq_pollitem_t) = " << sizeof(poll_items) / sizeof(zmq_pollitem_t) << std::endl;
while (1)
{
int poll_result = zmq_poll(poll_items, sizeof(poll_items) / sizeof(zmq_pollitem_t), 100);
if (poll_result == -1)
{
std::cout << "zmq_poll error: " << zmq_strerror(errno) << std::endl;
}
else if (poll_result > 0)
{
if ((poll_items[0].revents & ZMQ_POLLIN) != 0)
{
zmq_msg_t msg;
zmq_msg_init(&msg);
zmq_msg_recv(&msg, socket1, 0);
std::cout << "Socket1 recv : " << std::string(reinterpret_cast<char *>(zmq_msg_data(&msg)), zmq_msg_size(&msg)) << std::endl;
zmq_msg_close(&msg);
}
if ((poll_items[1].revents & ZMQ_POLLIN) != 0)
{
zmq_msg_t msg;
zmq_msg_init(&msg);
zmq_msg_recv(&msg, socket2, 0);
std::cout << "Socket2 recv : " << std::string(reinterpret_cast<char *>(zmq_msg_data(&msg)), zmq_msg_size(&msg)) << std::endl;
zmq_msg_close(&msg);
}
}
else
{
// poll_result == 0 时不做处理,按照ZMQ的API说明,zmq_poll不会返回除-1以外的小于0的值
}
std::string input = noblock_input().value_or("");
// 有输入
if (input.size() > 0)
{
zmq_msg_t msg_1;
zmq_msg_init_size(&msg_1, input.size());
memcpy(zmq_msg_data(&msg_1), input.c_str(), input.size());
zmq_msg_send(&msg_1, socket1, 0);
zmq_msg_t msg_2;
zmq_msg_init_size(&msg_2, input.size());
memcpy(zmq_msg_data(&msg_2), input.c_str(), input.size());
zmq_msg_send(&msg_2, socket2, 0);
std::cout << "Message Sent : " << input << std::endl;
}
}
return 0;
}
Client端
#include <iostream>
#include <cstring>
#include <cassert>
#include "zmq.h"
#include "Async_input.h"
int main(int argc, char **argv)
{
if (argc < 2)
{
std::cout << "Please input the protocol, target address and port (i.e tcp://127.0.0.1:5000)" << std::endl;
return -1;
}
std::string target{argv[1]};
std::cout << target << std::endl;
void *context = zmq_ctx_new();
assert(context != NULL);
void *socket = zmq_socket(context, ZMQ_PAIR);
assert(socket != NULL);
auto connect_result = zmq_connect(socket, target.c_str());
assert(connect_result == 0);
zmq_pollitem_t poll_items[1];
poll_items[0].socket = socket;
poll_items[0].events = ZMQ_POLLIN;
while (1)
{
auto poll_result = zmq_poll(poll_items, sizeof(poll_items) / sizeof(zmq_pollitem_t), 100);
if (poll_result == -1)
{
std::cout << "zmq_poll error: " << zmq_strerror(errno) << std::endl;
}
else if (poll_result > 0)
{
if ((poll_items[0].revents & ZMQ_POLLIN) > 0)
{
zmq_msg_t msg;
zmq_msg_init(&msg);
zmq_msg_recv(&msg, socket, 0);
std::cout << "Message Received : " << std::string(reinterpret_cast<char *>(zmq_msg_data(&msg)), zmq_msg_size(&msg)) << std::endl;
}
}
else
{
// poll_result == 0 时不做处理,按照ZMQ的API说明,zmq_poll不会返回除-1以外的小于0的值
}
std::string input = noblock_input().value_or("");
// 标准输入有信息
if (input.size() > 0)
{
zmq_msg_t msg;
zmq_msg_init_size(&msg, input.size());
memcpy(zmq_msg_data(&msg), input.c_str(), input.size());
zmq_msg_send(&msg, socket, 0);
std::cout << "Message Sent : " << input << std::endl;
}
}
return 0;
}
编译CMakeLists.txt
cmake_minimum_required(VERSION 3.19)
set(CMAKE_CXX_STANDARD 17)
project(ZMQ_Poll CXX)
add_executable(Client Client.cpp Async_input.cpp)
add_executable(Server Server.cpp Async_input.cpp)
target_include_directories(Client PUBLIC
${CMAKE_SOURCE_DIR}/../libzmq/include)
target_include_directories(Server PUBLIC
${CMAKE_SOURCE_DIR}/../libzmq/include)
target_link_directories(Client PUBLIC
${CMAKE_SOURCE_DIR}/../libzmq/_build/lib)
target_link_libraries(Client
zmq)
target_link_directories(Server PUBLIC
${CMAKE_SOURCE_DIR}/../libzmq/_build/lib)
target_link_libraries(Server
zmq)