ZeroMQ:24---模式之(節點協調)

一、節點協調

  • 在上一篇文章我們介紹了PAIR套接字以及“線程協調”的概念,請參閱:https://blog.csdn.net/qq_41453285/article/details/106946623
  • 當你想“協調節點”時,PAIR套接字就無法正常工作了,這是線程和節點的策略不同的少數地方之一
  • 線程和節點之間的區別主要有:
    • 節點可以加入或退出,是動態的;而線程的數量通常是固定的,,是靜態的
    • 節點要求退出後重新打開還可以建立連接,而PAIR套接字不能重新連接

二、節點協調演示案例(以發佈-訂閱爲例)

  • 在以往的發佈-訂閱代碼中,發佈者對訂閱者的數量沒有限制,其只負責發佈消息,根本不關心訂閱者的數量與身份
  • 我們設計的這個應用程序的工作原理是:
    • 在發佈者的代碼中定義一個宏,來用指定訂閱者的數量
    • 發佈者啓動之後,創建PUB套接字和一個REP套接字其中REP套接字用來接收訂閱者給自己發來消息(請求訂閱的消息)
    • 訂閱者也啓動程序,創建SUB套接字和一個REQ套接字,其中REQ套接字用來給發佈者發送訂閱請求
    • 當發佈者接收到指定數量的訂閱者發來請求訂閱消息之後,開始使用PUB套接字發佈數據;訂閱者使用SUB套接字接收發布數據

代碼如下

  • 同步的發佈者代碼如下:
// syncpub.c
// 源碼鏈接: https://github.com/dongyusheng/csdn-code/blob/master/ZeroMQ/syncpub.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <zmq.h>

// 我們等待10個訂閱者
#define SUBSCRIBERS_EXPECTED 10

// 從socket套接字上接收消息
static char *s_recv(void *socket);

// 向socket套接字發送消息string
static int s_send(void *socket, char *string);

int main()
{
    int rc;
    
    // 1.創建新的上下文
    void *context = zmq_ctx_new();
    assert(context != NULL);

    // 2.創建PUB套接字, 用來發布數據
    void *publisher = zmq_socket(context, ZMQ_PUB);
    assert(publisher != NULL);
    rc = zmq_bind(publisher, "tcp://*:5561");
    assert(rc != -1);

    // 3.創建REP套接字, 用來接收訂閱者發來訂閱請求
    void *syncservice = zmq_socket(context, ZMQ_REP);
    assert(syncservice != NULL);
    rc = zmq_bind(syncservice, "tcp://*:5562");
    assert(rc != -1);

    // 4.等待10個訂閱者發來訂閱請求才開始發佈數據
    printf("Waiting for subscribers\n");
    int subscribers = 0;
    while(subscribers < SUBSCRIBERS_EXPECTED)
    {
        // 從REP套接字上接收訂閱者發來的訂閱請求
        char *string = s_recv(syncservice);
        free(string);

        // 收到訂閱請求, 回送應答給訂閱者
        rc = s_send(syncservice, "");
        assert(rc != -1);
        subscribers++;
    }

    // 5.下面纔開始真正的發佈數據
    //    下面廣播數據, 然後以一個END字符串表示發佈數據結束
    printf("Broadcasting messages\n");
    int update_nbr;
    for(update_nbr = 0; update_nbr < 10; update_nbr++)
        s_send(publisher, "Rhubarb");
    s_send(publisher, "END");

    // 6.關閉套接字、銷燬上下文
    zmq_close(publisher);
    zmq_close(syncservice);
    zmq_ctx_destroy(context);

    return 0;
}

static char *s_recv(void *socket)
{
    int size;
    
    zmq_msg_t msg;
    zmq_msg_init(&msg);
    
    size = zmq_msg_recv(&msg, socket, 0);
    if(size == -1)
        return NULL;

    char *string = (char*)malloc(size + 1);
    if(string == NULL)
        return NULL;
    memcpy(string, zmq_msg_data(&msg), size);

    zmq_msg_close(&msg);

    string[size] = 0;

    return string;
}

static int s_send(void *socket, char *string)
{
    int rc;
    
    zmq_msg_t msg;
    zmq_msg_init_size(&msg, strlen(string));

    memcpy(zmq_msg_data(&msg), string, strlen(string));
    
    rc = zmq_msg_send(&msg, socket, 0);
    
    zmq_msg_close(&msg);

    return rc;
}
  • 同步的訂閱者代碼如下:
// syncsub.c
// 源碼鏈接: https://github.com/dongyusheng/csdn-code/blob/master/ZeroMQ/syncsub.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <zmq.h>

// 從socket套接字上接收消息
static char *s_recv(void *socket);

// 向socket套接字發送消息string
static int s_send(void *socket, char *string);

int main()
{
    int rc;
    
    // 1.創建新的上下文
    void *context = zmq_ctx_new();
    assert(context != NULL);

    // 2.創建SUB套接字, 連接發佈者
    void *subscriber = zmq_socket(context, ZMQ_SUB);
    assert(subscriber != NULL);
    rc = zmq_connect(subscriber, "tcp://localhost:5561");
    assert(rc != -1);

    // 3.設置訂閱過濾
    rc = zmq_setsockopt(subscriber, ZMQ_SUBSCRIBE, "", 0);
    assert(rc != -1);
    
    // 休眠1秒
    // 我們不能保證SUB在下面REQ/REP對話完成的時候就能建立連接, 因此此處休眠一秒, 保證SUB一定能與PUB建立連接
    sleep(1);

    // 4.創建REQ套接字, 用來給發佈者發送訂閱請求, 只需要發送一次即可
    void *syncclient = zmq_socket(context, ZMQ_REQ);
    assert(syncclient != NULL);
    rc = zmq_connect(syncclient, "tcp://localhost:5562");
    assert(rc != -1);

    // 5.給發佈者發送訂閱請求, 並接收發布者給自己返回確認
    rc = s_send(syncclient, "");
    assert(rc != -1);
    char *string = s_recv(syncclient);
    assert(string != NULL);
    free(string);

    // 6.下面纔開始真正的接收發布者的發佈數據
    int update_nbr = 0;
    while(1)
    {
        // 接收數據
        char *string = s_recv(subscriber);
        // 如果接收到END說明發布數據全部接收完了, 退出循環
        if(strcmp(string , "END") == 0)
        {
            free(string);
            break;
        }
        free(string);
        update_nbr++;
    }
    printf("Received %d updates\n", update_nbr);

    // 7.關閉套接字、銷燬上下文
    zmq_close(subscriber);
    zmq_close(syncclient);
    zmq_ctx_destroy(context);

    return 0;
}

static char *s_recv(void *socket)
{
    int size;
    
    zmq_msg_t msg;
    zmq_msg_init(&msg);
    
    size = zmq_msg_recv(&msg, socket, 0);
    if(size == -1)
        return NULL;

    char *string = (char*)malloc(size + 1);
    if(string == NULL)
        return NULL;
    memcpy(string, zmq_msg_data(&msg), size);

    zmq_msg_close(&msg);

    string[size] = 0;

    return string;
}

static int s_send(void *socket, char *string)
{
    int rc;
    
    zmq_msg_t msg;
    zmq_msg_init_size(&msg, strlen(string));

    memcpy(zmq_msg_data(&msg), string, strlen(string));
    
    rc = zmq_msg_send(&msg, socket, 0);
    
    zmq_msg_close(&msg);

    return rc;
}
  • 編譯:
gcc -o syncpub syncpub.c -lzmq
gcc -o syncsub syncsub.c -lzmq
  • 編寫下面的腳本sync_bash,腳本可以啓動10個訂閱者和一個發佈者
# 啓動10個訂閱者
echo "Starting subscribers..."
for((a=0; a<10; a++)); do
    ./syncsub &
done

# 啓動發佈者
echo "Starting publisher..."
./syncpub &
  • 給予腳本可執行權限,然後運行腳本,結果如下所示:
chmod +x sync_bash

./sync_bash

  • 上面的模式還可以更改爲一個更強大的模式:
    • 發佈者打開PUB套接字並開始發送“Hello”消息(不是數據)
    • 訂閱者連接到SUB套接字,當它接收到“Hello”消息時,它們通過REQ/REP套接字對告訴發佈者
    • 當發佈者已經得到所有必要的確認時,它就開始發送數據
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章