ZeroMQ:23---模式之(獨家對模式:ZMQ_PAIR)

一、ØMQ模式總覽

二、獨家對模式

三、“PAIR”套接字類型

  • ZMQ_PAIR類型的套接字只能一次連接到單個對等方。對通過ZMQ_PAIR套接字發送的消息不執行消息路由或篩選
  • 當ZMQ_PAIR套接字由於已達到連接對等方的高水位線而進入靜音狀態時,或者如果沒有連接任何對等方,則套接字上的任何zmq_send()操作都應阻塞,直到對等方可用於發送;消息不會被丟棄
  • 適用協議:
    • ZMQ_PAIR套接字設計用於通過nproc傳輸進行線程間通信,並且不實現自動重新連接等功能
    • 儘管ZMQ_PAIR套接字可用於inproc以外的其他傳輸協議,但是它們無法自動重新連接,並且當以前存在任何連接(包括關閉狀態的連接)時,新的傳入連接將被終止,這使得它們在大多數情況下不適合TCP
                                                                                                       ZMQ_PAIR特性摘要 
兼容的對等套接字 ZMQ_PAIR
方向 雙向
發送/接收模式 無限制
入網路由策略 不適用(N/A)

外發路由策略

不適用(N/A)
靜音狀態下的操作 阻塞

四、PAIR套接字應用場景:協調線程

  • 在編寫多線程應用程序時,會遇到如何“協調線程”的問題,例如一個線程狀態發生改變時同時另一個線程:
    • 如果使用以往的多線程程序,你可能會使用信號量或互斥等技術
    • 但是在ØMQ中,你可以使用ZMQ_PAIR套接字來進行線程間的通信

編程實例

  • 下面創建三個PAIR套接字:
    • PAIR3:主線程中的PAIR套接字,等待PAIR2發來通知消息
    • PAIR2:在主線程中調用pthread_create()創建線程,在線程的回調函數中創建PAIR2套接字,該套接字等待PAIR1發來通知消息
    • PAIR1:PAIR2所在的線程再調用一次pthread_create(),在線程的回調函數中創建PAIR1套接字,PAIR1會向PAIR2發送消息
  • 整體的流程就是:PAIR1發送消息給PAIR2,PAIR2接收到PAIR1的消息之後再發送消息給PAIR3,PAIR3接收到PAIR2的消息之後退出程序

  • 代碼如下:
// mtrelay.c
// 源碼鏈接: https://github.com/dongyusheng/csdn-code/blob/master/ZeroMQ/mtrelay.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <zmq.h>

// PAIR2套接字執行函數, 參數爲主進程的上下文對象指針
static void *step2(void *arg);

// PAIR1套接字執行函數, 參數爲主進程的上下文對象指針
static void *step1(void *arg);

// 從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.創建、綁定PAIR3套接字, PAIR2會連接該套接字並向該套接字發送消息
    void *receiver = zmq_socket(context, ZMQ_PAIR);
    assert(receiver != NULL);
    rc = zmq_bind(receiver, "inproc://step3");
    assert(rc != -1);

    // 3.創建線程, 線程中創建PAIR2套接字, PAIR2套接字會向PAIR3發送消息
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, step2, context);

    // 4.阻塞等待接收PAIR2發來消息
    char *string = s_recv(receiver);
    assert(string != NULL);
    free(string);

    // 5.打印消息
    printf("Test successful!\n");

    // 6.關閉套接字、銷燬上下文
    zmq_close(receiver);
    zmq_ctx_destroy(context);
    
    return 0;
}

static void *step2(void *arg)
{
    // 參數爲上下文對象指針

    int rc;
    
    // 1.創建、綁定PAIR2套接字, PAIR1會連接該套接字並向該套接字發送消息
    void *receiver = zmq_socket(arg, ZMQ_PAIR);
    assert(receiver != NULL);
    rc = zmq_bind(receiver, "inproc://step2");
    assert(rc != -1);

    // 2.創建線程, 線程中創建PAIR1套接字, PAIR1套接字會向PAIR2發送消息
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, step1, arg);
    
    // 3.等待接收PAIR1發來消息
    char *string = s_recv(receiver);
    assert(string != NULL);
    free(string);
    // 關閉PAIR2套接字
    zmq_close(receiver);


    // 接收到PAIR1發來消息之後, 再開始向PAIR3發送通知

    
    // 4.重新創建一個PAIR2套接字, 該套接連接PAIR3
    void *xmitter = zmq_socket(arg, ZMQ_PAIR);
    assert(xmitter != NULL);
    rc = zmq_connect(xmitter, "inproc://step3");
    assert(rc != -1);

    // 5.向PAIR3發送通知
    printf("Step 2 ready, signaling step 3\n");
    rc = s_send(xmitter, "READY");
    assert(rc != -1);
    zmq_close(xmitter);

    return NULL;
}

static void *step1(void *arg)
{
    // 參數爲上下文對象指針
    int rc;
    
    // 創建一個PAIR1套接字, 然後連接PAIR2套接字
    void *xmitter = zmq_socket(arg, ZMQ_PAIR);
    assert(xmitter != NULL);
    rc = zmq_connect(xmitter, "inproc://step2");
    assert(rc != -1);

    // 5.向PAIR2發送通知
    printf("Step 1 ready, signaling step 2\n");
    rc = s_send(xmitter, "READY");
    assert(rc != -1);
    zmq_close(xmitter);

    return NULL;
}

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 mtrelay mtrelay.c -lzmq

案例分析

  • 這是使用ØMQ進行多線程編程的一個經典模式:
    • 1.兩個線程通過inproc通信,使用的是共享的上下文
    • 2.父線程創建一個套接字,將其綁定到一個inproc端點,然後啓動子線程,將上下文傳遞給它
    • 3.子線程創建第二個套接字,將它連接到該inproc端點,然後發信號告訴父線程,它已準備就緒
  • 使用這種模式的多線程嗲嗎是不可擴展到進程的。如果你使用inproc和套接字對,你就正在構建一個緊耦合的應用程序,也就是說,其中你的線程在結構是相互依存的,只有在低延遲真的很重要的時候才這樣做。另一種設計模式是一個松耦合的應用程序,其中的線程有自己的上下文並通過ipc或tcp通信。你可以輕鬆地將松耦合的線程分解爲單獨的進程

爲什麼選擇的是PAIR?

  • 此處使用的是PAIR套接字,其他套接字組合也能夠完成上面相同的工作,但是其他臺套接字都有副作用,可能會干擾信令:
    • 你可以讓發送者使用PUSH並讓接收者使用PULL,但是PUSH會把消息發送給所有存在的接收者,假設你啓動了2個接收者,那麼就會“丟失”一半的信號。PAIR具有拒絕多個連接,兩個連接的組成的對是獨佔的
    • 你可以讓發送者使用DEALER並讓接收者使用ROUTER,但是ROUTER將你的信息包裝在一個“封包”中,這意味着你的大小爲0的信號變成了一個多部分消息。如果你不關心數據並把任何東西都當做一個有效的信號,並且如果你不會不止一次地從套接字上讀取,這並不重要。但是,如果你決定要發送實際數據時,你會突然發現ROUTER爲你提供了“錯誤”的消息。DEALER也分發傳出消息,這帶來與PUSH相同的風險
    • 你可以讓發送者使用PUB,而讓接收者使用SUB,這將完全按照你發送它們的原樣正確地傳遞你的消息,而且PUB不像PUSH或DEALER那樣分發消息。但是,你需要用空訂閱配置訂閱者,這比較麻煩,更糟的是,PUB-SUB鏈接的可靠性是與實踐相關的,並且如果在PUB套接字發送消息時,SUB套接字正在連接,信息就有可能會丟失
  • 綜合以上的原因,使得PAIR成爲線程對之間協調的最佳選擇

五、節點協調

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章