基于zmq_poll实现双向非阻塞通信(双向异步聊天)

最近在使用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)
  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值