ZeroMQ:08---基礎篇之(ØMQ的消息處理、多部分消息、及消息接口(zmq_msg_t))

一、ØMQ的消息處理

使用套接字來傳輸數據

  • 但ØMQ的I/O模型與TCP模型有很大區別,你需要時間來轉變觀念
  • 處理數據時,TCP套接字和ØMQ套接字之間的差異:
    • ØMQ套接字像UDP那樣傳遞信息,而不是像TCP那樣傳遞字節流。ØMQ消息是指定長度的二進制數據,因爲它們的設計針對性能進行了優化,所以有點棘手
    • ØMQ套接字在一個後臺線程執行自己的IO。這意味着消息到達本地輸入隊列並從本地輸出隊列被髮送,不會影響到你的應用程序運行
    • ØMQ套接字根據套接字類型具有內置的1對N的路由行爲

zmq_msg_xxx()消息處理接口

  • 在內存中,ØMQ消息是zmq_msg_t表示的結構(或類,取決於你採用的語言)
  • 下面是C語言中使用ØMQ消息的基本規則:
    • 創建並創建zmq_msg_t對象,使用zmq_msg_t來表示消息,而不是使用普通的數據塊(char*)來交互數據
    • 要讀取消息,可使用zmq_msg_int()創建一個空的消息,然後傳遞給zmq_msg_recv()
    • 要寫入消息,可以使用zmq_msg_init_size()來創建消息,並分配某個大小的數據塊數據,使用memcpy()將數據塊的數據拷貝給zmq_msg_t,然後將zmq_msg_t傳遞給zmq_msg_send()進行發送
    • 要釋放消息,則調用zmq_msg_close(),這會刪除一個引用,當消息引用爲0時,ØMQ會最終自動幫你銷燬該消息
    • 要訪問消息內容,可以使用zmq_msg_data()
    • 要知道消息包含多少數據,可以使用zmq_msg_size()
    • 一般不建議使用zmq_msg_move()、zmq_msg_copy()、zmq_msg_init_data(),除非你的目標很明確就是要用這些函數
    • zmq_msg_send()傳遞一個消息時候,會把該消息清除(把它的大小設置爲0),因此消息發送之後需要關閉(zmq_msg_close())並且不再使用。如果你想多次發送相同的數據,可以創建兩個zmq_msg_t消息對象發送,或者在調用zmq_msg_init()之前使用zmq_msg_copy()拷貝兩份一樣的數據並同時發送
  • 此處給出的只是個大概,更多的細節參閱下面的接口介紹和演示案例

ØMQ對字符串的處理

  • 在前面的文章我們介紹瞭如何處理ØMQ的字符串並封裝了下面兩個字符串處理函數,文章可以參閱:https://blog.csdn.net/qq_41453285/article/details/105991716
  • 下面是自定義的兩個函數:
    • 一個是字符串接收函數:其從網絡中接收一個ØMQ字符串,並申請多1個字節空間的內存保存該字符串,然後在尾部要添加0,以終止該字符串
    • 一個是字符串發送函數:向網絡中發送一個字符串,單發送的字符串不含尾部的空字符
// 從套接字接收ØMQ字符串,並將其轉換爲C/C++字符串(在尾部添加0)
static char *s_recv(void* socket)
{
    // 此處使用zmq_msg_init()初始化即可, zmq_msg_recv()在內部會自動對zmq_msg_t對象進行大小設定
    zmq_msg_t message;
    zmq_msg_init(&message);

    int size = zmq_msg_recv(&message, socket, 0);
    if(size == -1)
        return NULL;

    char *string = (char*)malloc(size + 1);
    memcpy(string, zmq_msg_data(&message), size);

    zmq_msg_close(&message);
    string[size] = 0;
    return string;
}
// 將C字符串轉換爲ØMQ字符串(去掉尾部的'\0'),併發送到指定的套接字上
static int s_send(void *socket, char *string)
{
    // 因爲是將數據拷貝給zmq_msg_t對象, 因此需要使用zmq_msg_init_size進行初始化
    zmq_msg_t msg;
    zmq_msg_init_size(&msg, strlen(string));
    memcpy(zmq_msg_data(&msg), string, strlen(string));

    // 發送數據
    int rc = zmq_msg_send(&msg, socket, 0);

    // 關閉zmq_msg_t對象
    zmq_msg_close(&msg);

    return rc;
}

“部件”和“幀”的概念

  • 幀(Frame)(在ØMQ參考手冊中也稱爲“消息部件”)是ØMQ消息的基本線路格式。幀是已指定長度的數據塊,此長度可以從零向上。如果做過任何TCP編程工作,你就會明白,爲什麼幀是“現在我應該從網絡套接字讀出多少數據?”這個問題的一個有用的答案

  • 最初,一個ØMQ消息是一幀,像UDP一樣。後面我們採用多部分消息來擴展了這一點,這是相當簡單的帶有被設置爲1的“more”位的幀的序列,接着是一個該位被設置爲零的幀。ØMQ API然後讓你寫入一個“more”標誌的消息,並且當你讀取消息是,它可以讓你檢查是否存在“more”
  • 因此,在低級別ØMQ API和參考手冊中,有關於消息與部分有一些模糊性。下面用一個有用的詞彙表來說明:
    • 消息可以是一個或多個部件
    • 這些部件也稱爲幀
    • 每個部件都是一個zmq_msg_t對象
    • 你用低級別的API分別發送和接收各個部件
    • 高級別API爲發送整個多部分消息提供包裝
  • 其他與消息相關的內容:

    • 你可以發送長度爲0的消息。例如,用於從一個線程把一個信號發送給另一個線程
    • ØMQ保證提供一個消息所有的部分(一個或多個)或者一個部分也沒有
    • ØMQ不立刻發送消息(單個或多部分),而在以後某些不確定的時間發送。因此,一個多部分消息必須在內存中裝入
    • 單部分消息也必須裝入內存。如果你想發送任意大小的文件,應該把它們分解成片,並把每一片作爲獨立的單部分消息發送
    • 在作用域關閉時不自動銷燬對象的語言中,在完成消息時,必須調用zmq_msg_close()
    • 輕易不要使用zmq_msg_init_data(),這是一個零拷貝的方法,如果使用不好會帶來麻煩。零拷貝可參閱:https://blog.csdn.net/qq_41453285/article/details/106845900

二、多部分消息

  • ØMQ允許我們撰寫由多個幀組成的單個消息,從而給我們一個“多部分消息”。實際的應用程序中相當多地使用了多部分消息,無論是包裝帶地址信息的消息,還是進行簡單的序列化
  • 關於多部分消息,你需要了解的是:
    • 當發送一個多部分消息時,僅當你發送最後的部分時,所有的消息纔會整整在線路上實際發送
    • 如果你使用的是zmq_poll(),當你收到一條消息的第一部分時,其餘部分也都是已經到達了
    • ØMQ確保消息的原子傳遞:對等方應該收到消息的所有消息部分,或者根本不收到任何消息。除非關閉套接字,否則沒有辦法取消部分發送的消息
    • 消息部分的總數不受可用存儲空間的限制
    • 在使用多部分消息時,每個部分都是一個zmq_msg_t條目。例如,如果你要發送的消息具有5個部分,你就必須構造5個zmq_msg_t對象
    • 在發送時,ØMQ消息幀都在內存中排隊,直到最後一個小熊被接收到位置,然後再一起發送它們
    • 在接收時,無論你是否設置RCVMORE選項,都將受到一條消息的所有部分
  • 在後面的文章中我們會研究應答封包。現在我們學習如何安全地(但一位地)在需要轉發消息但不檢查它們的任何應用程序(如代理)中讀寫多部分消息

寫多部分消息

  • 例如,下面發送三條消息,三條消息組成一條多部分消息,並且調用三次zmq_msg_send()發送出去,注意其中用到了ZMQ_SNDMORE選項
zmq_msg_t message1;
zmq_msg_t message2;
zmq_msg_t message3;

//初始化這三條消息

//發送第一條, 指定ZMQ_SNDMORE選項, 表示發送的是多部分消息的其中一部分, 後面還要消息要發送
zmq_msg_send(socket, &message1, ZMQ_SNDMORE);
//發送第二條,同上
zmq_msg_send(socket, &message2, ZMQ_SNDMORE);
//發送最後一條消息, 因爲後面沒有消息要發送了, 因此最後一個參數爲0即可
zmq_msg_send(socket, &message3, 0);
  • 更多詳細細節可以參閱下面“zmq_msg_send()的介紹及其演示案例②”

讀多部分消息

  • 在接收消息時,可以使用ZMQ_RCVMORE調用zmq_getsockopt()函數來判斷套接字是否還有更多的消息要接收
  • 下面是一個即可以處理單部分消息又可以處理多部分消息的代碼
while(1)
{
    zmq_msg_t message;
    zmq_msg_init(&message);

    zmq_msg_recv(socket, &message, 0);

    zmq_msg_close(&message);

    int more;
    size_t more_size = sizeof(more);
    zmq_getsockopt(socket, ZMQ_RCVMORE, &more, &more_size);
    if(!more)
        break;
}

三、接口使用的幾點說明

關於zmq_msg_init()和zmq_msg_init_size()的踩坑記錄

  • 這兩個函數曾經騷擾我半天,由於ØMQ操作文檔說明的不想洗,我搞了半天才弄好
  • ①在發送數據的時候:我們需要調用memcpy()將數據拷貝到zmq_msg_t中進行發送,不可以調用zmq_msg_init()初始化的zmq_msg_t對象進行存儲,因爲zmq_msg_init()初始化的對象其大小被設定爲0,在調用zmq_msg_send()的時候會報錯的。見下面代碼
/*******下面這種情況是錯誤的*******/

zmq_msg_t msg;
zmq_msg_init(&msg);

// 將str拷貝給msg
char *str= "HelloWolrd";
memcpy(zmq_msg_data(&msg), str, 11);

// 打印的是HelloWolrd, 但是大小爲0, 大小爲0就代表該zmq_msg_t對象不可用
printf("%s %ld\n", (char*)zmq_msg_data(&msg), zmq_msg_size(&msg));

// zmq_msg_send()會出錯, msg雖然有內容, 但是其大小爲0
// zmq_msg_send(&msg, socket, 0);

 

  • 發送數據的時候請使用zmq_msg_init_size()初始化對象,這樣發送出去的zmq_msg_t對象是有大小的,不會被zmq_msg_send()判斷爲是錯的
/*******下面這種情況是正確的*******/

char *str= "HelloWolrd";

// 初始化時指定其大小
zmq_msg_t msg;
zmq_msg_init_size(&msg, strlen(str) + 1);

// 將str拷貝給msg
memcpy(zmq_msg_data(&msg), str, 11);

// 打印HelloWorld, 大小爲10
printf("%s %ld\n", (char*)zmq_msg_data(&msg), zmq_msg_size(&msg));

// zmq_msg_send()調用成功
// zmq_msg_send(&msg, socket, 0);

  • ②在接收數據的時候:接收數據時,可以使用zmq_msg_init()定義的zmq_msg_t對象來保存數據,zmq_msg_recv()函數內部會自動的設置zmq_msg_t對象的大小
// 初始化時指定其大小
zmq_msg_t msg;
zmq_msg_init(&msg);

// 接收數據, zmq_msg_recv()內部會自動
zmq_msg_recv(&msg, socket, 0);

關於拷貝

  • 當把數據拷貝給zmq_msg_t對象時,如果數據的長度超過zmq_msg_t對象的大小,zmq_msg_t對象仍然可以獲取完整數據,但是使用起來時只能使用其指定的大小
#include <stdio.h>
#include <zmq.h>

int main()
{
    // 初始化msg時, 指定其大小爲5
    zmq_msg_t msg;
    zmq_msg_init_size(&msg, 5);

    // 拷貝11字節給msg
    memcpy(zmq_msg_data(&msg), "HelloWorld", 11);

    // 打印的大小將爲5
    printf("%s %ld\n", (char*)zmq_msg_data(&msg), zmq_msg_size(&msg));

    zmq_msg_close(&msg);
   
    return 0;
}

四、zmq_msg_t結構及源碼分析

  • 本文使用的源碼爲zeromq4.1.7

zmq_msg_t的結構

  • ØMQ用zmq_msg_t結構來表示一條小消息,其源碼定義如下: 
//zmq.h
typedef struct zmq_msg_t {
#if defined (__GNUC__) || defined ( __INTEL_COMPILER) || \
        (defined (__SUNPRO_C) && __SUNPRO_C >= 0x590) || \
        (defined (__SUNPRO_CC) && __SUNPRO_CC >= 0x590)
    unsigned char _ [64] __attribute__ ((aligned (sizeof (void *))));
#elif defined (_MSC_VER) && (defined (_M_X64) || defined (_M_ARM64))
    __declspec (align (8)) unsigned char _ [64];
#elif defined (_MSC_VER) && (defined (_M_IX86) || defined (_M_ARM_ARMV7VE))
    __declspec (align (4)) unsigned char _ [64];
#else
    unsigned char _ [64];
#endif
} zmq_msg_t;
  • zmq_msg_t並不是真正存儲數據的地方我們在操作的時候zmq_msg_t的時候,實際上將其轉換爲zmq命令空間中的一種msg_t類來使用的。例如下圖所示:調用zmq_msg_int()初始化zmq_msg_t的時候實際調用的就是msg_t類的init()方法 

msg_t類

  • msg_t類是真正存儲數據的地方,如下圖所示(代碼被縮減了,完整的定義見msg.hpp)
  • 結構如下:
    • base結構體:其有一個type成員用來表示這個消息屬於什麼類型的,不同類型的消息會用下面不同的結構體存儲消息
    • vsm、lmsg、cmsg等結構體:代表消息的不同類型,當前msg_t屬於哪一種類型,哪一個結構體就會被初始化。其中這些結構體中的data字段存儲的是消息的真正值、size存儲消息的大小等
//msg.hpp
namespace zmq
{
    class msg_t
    {
    public:
        bool check ();
        int init ();
        int init_size (size_t size_);
        int init_data (void *data_, size_t size_, msg_free_fn *ffn_,
            void *hint_);
        int init_delimiter ();
        int close ();
        int move (msg_t &src_);
        int copy (msg_t &src_);
        void *data ();
        size_t size ();

    private:
        struct content_t
        {
            void *data;  //真正存儲數據的複方
            size_t size; //數據的大小
            msg_free_fn *ffn; //數據釋放函數, 見zmq_msg_init_data()函數
            void *hint; //傳遞給ffn的參數
            zmq::atomic_counter_t refcnt; //消息的引用計數
        };

        union {
            struct {
                metadata_t *metadata;
                unsigned char unused [msg_t_size - (8 + sizeof (metadata_t *) + 2)];
                unsigned char type;
                unsigned char flags;
            } base;
            struct {
                metadata_t *metadata;//元數據
                unsigned char data [max_vsm_size];//消息數據
                unsigned char size;
                unsigned char type;
                unsigned char flags;
            } vsm; //vsm消息類型
            struct {
                metadata_t *metadata;//元數據
                content_t *content;//消息數據
                unsigned char unused [msg_t_size - (8 + sizeof (metadata_t *) + sizeof (content_t*) + 2)];
                unsigned char type;
                unsigned char flags;
            } lmsg; //lmsg消息類型
            struct {
                metadata_t *metadata;
                void* data;//消息數據
                size_t size;
                unsigned char unused
                    [msg_t_size - (8 + sizeof (metadata_t *) + sizeof (void*) + sizeof (size_t) + 2)];
                unsigned char type;
                unsigned char flags;
            } cmsg; //cmsg消息類型
            struct {
                metadata_t *metadata;
                unsigned char unused [msg_t_size - (8 + sizeof (metadata_t *) + 2)];
                unsigned char type;
                unsigned char flags;
            } delimiter;
        } u;
    };
}

API分析

  • 以zmq_msg_init()爲例,它會在內部調用msg_t的init_size()函數,
    • init_size()函數會根據消息的大小來初始化msg_t的不同類型(vsm、lmsg、cmsg等類型),同時將結構的數據初始化

  • 以zmq_msg_data()爲例,它會在內部調用msg_t的data()函數,
    • data()函數會根據base結構體中的type字段來判斷當前數據屬於什麼類型,進而將不同結構的數據(返回data字段)進行返回

五、初始化空的ØMQ消息(zmq_msg_init)

int zmq_msg_init (zmq_msg_t *msg);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-init
  • 功能:初始化空的ØMQ消息
  • 參數:要初始化的ØMQ消息結構
  • 返回值:該函數總是返回0,沒有錯誤定義
  • 相關描述:
    • zmq_msg_init()函數將初始化msg引用的消息對象,以表示一條空消息。在使用zmq_msg_recv()接收消息之前調用此函數最有用
    • 永遠不要直接訪問zmq_msg_t成員,而是始終使用zmq_msg_xxx()系列函數
    • 函數zmq_msg_init()、zmq_msg_init_data()和zmq_msg_init_size()是互斥的。調用這三者之一即可,不要初始化相同的zmq_msg_t兩次
    • zmq_msg_init()的zmq_msg_t對象,其大小爲0,因此不能用在類似發送的函數中,但是可以用來接收(詳情參閱上面的“接口使用的幾點說明”)

演示案例

  • 從套接字接收消息
zmq_msg_t msg;
rc = zmq_msg_init(&msg);
assert(rc == 0);
int nbytes = zmq_msg_recv(socket, &msg, 0);
assert(nbytes != -1);

六、初始化指定大小的ØMQ消息(zmq_msg_init_size)

int zmq_msg_init_size (zmq_msg_t *msg, size_t size);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-init-size
  • 功能:初始化指定大小的ØMQ消息
  • 參數:
    • msg:要初始化的ØMQ消息結構
    • size:初始化ØMQ消息結構的大小
  • 返回值:
    • 成功:返回0
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • ENOMEM:可用的存儲空間不足
  • 相關描述:
    • zmq_msg_init_size()函數將分配存儲消息大小字節所需的任何資源,並初始化msg引用的消息對象來表示新分配的消息
    • 實現應該選擇將消息內容存儲在堆棧(小消息)還是堆(大消息)上。出於性能原因,zmq_msg_init_size()不得清除消息數據
    • 永遠不要直接訪問zmq_msg_t成員,而是始終使用zmq_msg_xxx()系列函數
    • 函數zmq_msg_init()、zmq_msg_init_data()和zmq_msg_init_size()是互斥的。調用這三者之一即可,不要初始化相同的zmq_msg_t兩次

七、從緩衝區中初始化ØMQ消息(zmq_msg_init_data)

typedef void (zmq_free_fn) (void *data, void *hint);

int zmq_msg_init_data (zmq_msg_t *msg, void *data, size_t size, zmq_free_fn *ffn, void *hint);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-init-data
  • 功能:從提供的緩衝區初始化ØMQ消息
  • 參數:
    • msg:要初始化的ØMQ消息結構
    • data:緩衝區的數據,是用來初始化msg的
    • size:參數data緩衝區數據對應的大小
    • ffn:如果data是動態申請的,那麼可以添加這個函數用來回收內存,其參數1data就是zmq_msg_init_data()函數的參數1,參數2hint是zmq_msg_init_data()函數的參數4
    • hint:傳遞給ffn函數的參數2
  • 返回值:
    • 成功:返回0
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • ENOMEM:可用的存儲空間不足
  • 相關描述:
    • zmq_msg_init_data()函數將初始化msg引用的消息對象。不得進行復制任何數據,ØMQ應擁有所提供緩衝區的所有權
    • 如果提供釋放功能(參數4),則一旦ØMQ不再需要緩衝區中的數據,就應該調用釋放函數ffn釋放內存
    • 釋放函數ffn需要是線程安全的,因爲它將從任意線程調用。如果沒有提供釋放函數,則分配的內存將不會被釋放,這可能會導致內存泄漏
    • 永遠不要直接訪問zmq_msg_t成員,而是始終使用zmq_msg_xxx()系列函數
    • 函數zmq_msg_init()、zmq_msg_init_data()和zmq_msg_init_size()是互斥的。調用這三者之一即可,不要初始化相同的zmq_msg_t兩次
    • ØMQ使用zmq_msg_init_data()來實現零拷貝,可參閱:https://blog.csdn.net/qq_41453285/article/details/106845900

演示案例

  • 從提供的緩衝區初始化消息
//釋放函數
void my_free (void *data, void *hint)
{
    free (data);
}

/* ... */

//申請數據
void *data = malloc (6);
assert (data);
memcpy (data, "ABCDEF", 6);

//用data初始化msg
zmq_msg_t msg;
rc = zmq_msg_init_data (&msg, data, 6, my_free, NULL);
assert (rc == 0);

八、釋放ØMQ消息(zmq_msg_close)

int zmq_msg_close (zmq_msg_t *msg);
  •  API參考文檔:http://api.zeromq.org/master:zmq-msg-close
  • 功能:將消息的引用計數減1
  • 參數:
    • msg:要釋放的消息結構
  • 返回值:
    • 成功:返回0
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EFAULT:無效的信息
  • 相關描述:
    • 應用程序應該確保在不再需要消息時調用zmq_msg_close(),否則可能發生內存泄漏
    • 該函數只是將zmq_msg_t對象所指數據的引用計數減1,並把自己的大小設置爲0而已。當一個zmq_msg_t對象調用zmq_msg_close()之後,如果其之前所指的數據還有其他zmq_msg_t對象引用,那麼該zmq_msg_t對象所指的數據不會真正的被釋放,只有數據的最後一個zmq_msg_t引用對象調用zmq_msg_close()時才真正的釋放內存(見下面演示案例①)
    • 當一個zmq_msg_t對象調用zmq_msg_close()之後就不能再對這個zmq_msg_t對象進行操作了,如果操作會報錯(見下面演示案例②)。即使它所指的數據還有其它zmq_msg_t對象引用也不行
    • 注意,在zmq_msg_send()成功之後,zmq_msg_send()會把zmq_msg_t對象的大小設置爲0(變爲0之後就標記這個zmq_msg_t對象不需要再去使用了),但是沒有關閉該對象,因此zmq_msg_send()之後需要關閉zmq_msg_t對象(更多詳細的細節見下面zmq_msg_send()函數的介紹和演示案例)

演示案例①

  • 下面創建兩個消息msg1和msg2,其中msg2拷貝於msg1
#include <stdio.h>
#include <string.h>
#include <zmq.h>

int main()
{
    // 1.初始化第一個消息
    printf("第一步: 初始化msg1:\n");
    zmq_msg_t msg1;
    zmq_msg_init_size(&msg1, 6);
    memcpy(zmq_msg_data(&msg1), "Hello", 6);
    printf("\tmsg1: %s, size: %d\n\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));

    // 2.將msg1拷貝給msg2
    printf("第二步:將msg1拷貝給msg2:\n");
    zmq_msg_t msg2;
    zmq_msg_init_size(&msg2, 6);
    zmq_msg_copy(&msg2, &msg1);
    printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));

    // 3.關閉msg1
    printf("第三步:關閉msg1:\n");
    zmq_msg_close(&msg1);
    //printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1)); msg1已經關閉了不能再進行訪問了
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));

    // 4.關閉msg2
    printf("第四步:關閉msg2:\n");
    zmq_msg_close(&msg2);

    //msg2已經關閉了不能再進行訪問了
    //printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));

    return 0;
}
  • 編譯運行效果如下:

演示案例②

  • 下面創建一個消息msg1,然後初始化msg1消息,最後把它釋放,釋放之後就不能再操作該zmq_msg_t對象了,如果操作就會報錯
#include <stdio.h>
#include <string.h>
#include <zmq.h>

int main()
{
    // 1.初始化第一個消息
    printf("第一步: 初始化msg:\n");
    zmq_msg_t msg;
    zmq_msg_init_size(&msg, 6);
    memcpy(zmq_msg_data(&msg), "Hello", 6);
    printf("\tmsg1: %s, size: %d\n\n", (char*)zmq_msg_data(&msg), (int)zmq_msg_size(&msg));

    // 2.關閉msg1
    printf("第二步:關閉msg:\n");
    zmq_msg_close(&msg);

    // 3.msg已經關閉了, 再去訪問就會報錯
    printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg), (int)zmq_msg_size(&msg));

    return 0;
}

九、設置/獲取消息屬性(zmq_msg_set、zmq_msg_get)

zmq_msg_set

int zmq_msg_set (zmq_msg_t *message, int property, int value);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-set
  • 功能:設置消息屬性
  • 參數:
    • message:要設置的消息
    • property:要設置的屬性
    • value:屬性值
  • 返回值:
    • 成功:返回0
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EINVAL:請求的屬性是未知的
  • 相關描述:當前zmq_msg_set()函數不能設置任何屬性

zmq_msg_get

int zmq_msg_get (zmq_msg_t *message, int property);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-get
  • 功能:獲取消息屬性
  • 參數:
    • message:要獲取的消息
    • property:要獲取的屬性
  • 返回值:
    • 成功:返回參數2所指定的屬性的值
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EINVAL:請求的屬性是未知的
  • 可以獲取的屬性如下:
    • ZMQ_MORE:指示消息後面有更多消息幀要跟隨
    • ZMQ_SRCFD:返回從套接字讀取消息的文件描述符。這允許應用程序通過getpeername()檢索遠程端點。請注意,相應的套接字可能已經關閉,甚至可以重用。目前只針對TCP套接字實現
    • ZMQ_SHARED:指示消息可以與該消息的另一個副本共享底層存儲

十、獲取消息元數據屬性(zmq_msg_gets)

const char *zmq_msg_gets (zmq_msg_t *message, const char *property);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-gets
  • 功能:獲取消息元數據屬性
  • 參數:
    • message:要獲取的消息
    • property:要獲取的屬性
  • 返回值:
    • 成功:返回屬性的字符串值。調用方不得修改或釋放返回的值,該值應歸消息所有。屬性和值的編碼應爲UTF8
    • 失敗:返回NULL,並將errno設置爲以下定義的值之一:
      • EINVAL:請求的屬性是未知的
  • 相關描述:
    • 該函數返回message的元數據,要獲取的元數據屬性爲參數2所指定的屬性,是一個字符串形式。參數2應該是以NULL結尾的UTF8編碼字符串
    • https://rfc.zeromq.org/spec/37/中所指定的,在ZeroMQ連接握手期間,將基於每個連接定義元數據。應用程序可以使用zmq_setsockopt()設置ZMQ_METADATA設置元數據屬性。應用程序元數據屬性必須以X-爲前綴
    • 另外,當底層傳輸可用時,Peer-Address屬性將返回由getnameinfo()返回的遠程端點的IP地址
    • 這些屬性的名稱也在zmq.h中定義爲:ZMQ_MSG_PROPERTY_SOCKET_TYPE ZMQ_MSG_PROPERTY_ROUTING_ID、ZMQ_MSG_PROPERTY_PEER_ADDRESS。目前,這些定義僅作爲API草案提供
    • 可以根據底層安全機制定義其他屬性,請參閱下面的ZAP身份驗證連接示例
    • 除了應用程序元數據外,還可以使用該函數檢索以下ZMTP屬性:
Socket-Type
Routing-Id

# 注意:Identity是Routing-Id的一個不贊成使用的別名

演示案例

  • 獲取消息的zap身份驗證用戶ID:
zmq_msg_t msg;
zmq_msg_init (&msg);
rc = zmq_msg_recv (&msg, dealer, 0);
assert (rc != -1);

const char *user_id = zmq_msg_gets (&msg, ZMQ_MSG_PROPERTY_USER_ID);
zmq_msg_close (&msg);

十一、獲取指向消息內容的指針(zmq_msg_data)

void *zmq_msg_data (zmq_msg_t *msg);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-data
  • 功能:獲取指向消息內容的指針,會將msg的內容以以指針的形式返回
  • 參數:
    • msg:要檢索的消息結構
  • 返回值:
    • 成功:返回一個指針,指向於msg的消息內容
    • 失敗:沒有出錯的情況

十二、獲取消息內容大小(zmq_msg_size)

size_t zmq_msg_size (zmq_msg_t *msg);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-size
  • 功能:獲取消息內容的大小,以字節爲單位
  • 參數:
    • msg:要檢索的消息結構
  • 返回值:
    • 成功:返回msg消息內容的大小(以字節爲單位)
    • 失敗:沒有出錯的情況

十三、複製消息內容(zmq_msg_copy)

int zmq_msg_copy (zmq_msg_t *dest, zmq_msg_t *src);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-copy
  • 功能:將一條消息的內容複製給另一條消息,此時兩條消息同指一塊緩衝區數據
  • 參數:
    • dest:目標消息結構
    • src:源消息結構
  • 返回值:
    • 成功:返回0
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EFAULT:無效的信息
  • 相關描述:
    • zmq_msg_copy()函數將src所引用的消息對象複製到dest所引用的消息對象中,如果dest之前有內容,則將其釋放。在複製之前你必須初始化dest
    • 引用計數:zmq_msg_copy()的實現並不是在內存中創建一份dest新實例,然後將src拷貝給dest,而是將dest指向於src所指的內容,因此,dest和src是共享底層緩衝區中的數據的
    • 因此在複製之後要避免修改消息的內容,因爲可能有多者共享這一條消息,其他修改會導致其它消息結構使用時產生未定義的行爲
    • 如果您需要的是一個實際的硬拷貝,那麼就不要使用該函數,可以使用zmq_msg_init_size()分配一個新消息,並使用memcpy()複製消息內容
    • 關於zmq_msg_copy()的演示案例可以看上面“zmq_msg_close()”函數的演示案例①

演示案例

  • 複製消息
//初始化msg
zmq_msg_t msg;
zmq_msg_init_size (&msg, 255);
memcpy(zmq_msg_data(&msg), "Hello, World", 12);

//將msg拷貝給copy, 此時copy與msg指向於同一塊數據
zmq_msg_t copy;
zmq_msg_init(&copy);
zmq_msg_copy(&copy, &msg);

//...

zmq_msg_close (&copy); //關閉copy, 此時msg的內容只有自己引用
zmq_msg_close (&msg);  //再關閉msg, 此時msg指向的內容才真正釋放

十四、移動消息內容(zmq_msg_move)

int zmq_msg_move (zmq_msg_t *dest, zmq_msg_t *src);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-move
  • 功能:將一條消息的內容移動給另一條消息
  • 參數:
    • dest:目標消息結構
    • src:源消息結構
  • 返回值:
    • 成功:返回0
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EFAULT:無效的信息
  • 相關描述:
    • zmq_msg_move()函數將把src引用的消息對象的內容移動到dest引用的消息對象,在調用zmq_msg_move()後,src變成一個空消息,如果dest之前有內容,則將其釋放然後更改爲src的內容
    • 一個zmq_msg_t對象在調用zmq_msg_move()之後只是將自己的數據拷貝給其它zmq_msg_t對象,並把自己的大小設置爲0。因此在移動之後其還可以訪問之前的數據,但是大小變爲0了(變爲0之後就標記這個zmq_msg_t對象不需要再去使用了),因此在一個zmq_msg_t對象調用zmq_msg_move()之後建議調用zmq_msg_close()關閉自己不要再使用了(見下面演示案例)
    • 與msg_msg_copy()的不同:msg_msg_copy()是將一個zmq_msg_t對象的數據拷貝給其他zmq_msg_t對象,導致數據的引用計數加1;zmq_msg_move()是將一個zmq_msg_t對象的數據移動到另外一個zmq_msg_t對象上

演示案例

  • 下面創建並初始化一個msg1對象,之後把msg1對象的內容移動給msg2對象
#include <stdio.h>
#include <string.h>
#include <zmq.h>

int main()
{
    // 1.初始化第一個消息
    printf("第一步: 初始化msg1:\n");
    zmq_msg_t msg1;
    zmq_msg_init_size(&msg1, 6);
    memcpy(zmq_msg_data(&msg1), "Hello", 6);
    printf("\tmsg1: %s, size: %d\n\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));

    // 2.將msg1內容移動到給msg2
    printf("第二步:將msg1數據移動給msg2:\n");
    zmq_msg_t msg2;
    zmq_msg_init_size(&msg2, 6);
    zmq_msg_move(&msg2, &msg1);
    //雖然msg1的內容進行移動了, 但是其還可以訪問數據, 只是大小變爲0了
    printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));

    // 3.關閉msg1, 因爲msg1數據進行移動了, 因此建議關閉不要再去使用
    printf("第三步:關閉msg1:\n");
    zmq_msg_close(&msg1);
    //printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1)); //msg1已經關閉了, 不能再去操作了
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));

    // 4.關閉msg2
    printf("第四步:關閉msg2:\n");
    zmq_msg_close(&msg2);
    //printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2)); //msg2已經關閉了不能再進行訪問了

    return 0;
}

十五、消息路由ID的設置/獲取(zmq_msg_set_routing_id、zmq_msg_routing_id)

設置路由ID(zmq_msg_set_routing_id)

int zmq_msg_set_routing_id (zmq_msg_t *message, uint32_t routing_id);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-set-routing-id
  • 功能:在消息上設置路由ID屬性
  • 參數:
    • message:設置的消息結構
    • routing_id:路由ID
  • 返回值:
    • 成功:返回0
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EINVAL:提供的routing_id爲零
  • 相關描述:
    • 函數的作用是:在消息參數所指向的消息上設置指定的routing_id
    • routing_id必須大於零
    • 要獲得有效的路由ID,您必須從ZMQ_SERVER套接字接收一條消息,並使用libzmq:zmq_msg_routing_id方法
    • 路由id是臨時的

獲取路由ID(zmq_msg_routing_id)

uint32_t zmq_msg_routing_id (zmq_msg_t *message);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-routing-id
  • 功能:獲取消息的路由ID(如果有)
  • 參數:
    • message:要獲取的消息結構
  • 返回值:
    • 沒有路由ID:返回0
    • 否則:返回一個大於0的32位無符號整數
  • 相關描述:
    • 函數的作用是:返回消息的路由ID(如果有的話)
    • 在從ZMQ_SERVER套接字接收的所有消息上設置路由ID
    • 要向ZMQ_SERVER套接字發送消息,必須設置已連接的ZMQ_CLIENT對等點的路由ID
    • 路由id是臨時的

演示案例

  • 接收客戶端消息和路由ID
// 1.創建上下文
void *ctx = zmq_ctx_new ();
assert (ctx);

// 2.創建一個ZMQ_SERVER服務端, 並開啓服務
void *server = zmq_socket (ctx, ZMQ_SERVER);
assert (server);
int rc = zmq_bind (server, "tcp://127.0.0.1:8080");
assert (rc == 0);

// 3.初始化一個消息結構
zmq_msg_t message;
rc = zmq_msg_init (&message);
assert (rc == 0);

// 4.接收消息
rc = zmq_msg_recv (server, &message, 0);
assert (rc != -1);

// 5.接收之後, 獲取路由ID
uint32_t routing_id = zmq_msg_routing_id (&message);
assert (routing_id);

十六、發送消息(zmq_msg_send)

int zmq_msg_send (zmq_msg_t *msg, void *socket, int flags);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-send
  • 功能:在指定的套接字上發送消息
  • 相關描述:
    • 該函數替換了zmq_sendmsg()函數,zmq_sendmsg()不推薦使用了,關於zmq_sendmsg()可以參閱:http://api.zeromq.org/master:zmq-sendmsg
    • zmq_msg_send()函數將把消息發送給socket,並且把msg消息在socket的消息隊列中進行排隊
    • 當把msg傳遞給該函數之後,msg所對應的zmq_msg_t結構就失效了,zmq_msg_send()會把zmq_msg_t對象的大小設置爲0(變爲0之後就標記這個zmq_msg_t對象不需要再去使用了),但是沒有關閉該對象,因此在zmq_msg_send()之後建議調用zmq_close()關閉msg參數對應的zmq_msg_t對象
    • 根據上面的特性,如果你想重複使用zmq_msg_send()之前msg參數對應的數據,那麼可以在調用zmq_msg_send()之前使用zmq_msg_copy()拷貝msg參數,這樣就有多個zmq_msg_t對象引用msg對引用的數據
    • 成功調用zmq_msg_send()並不表示消息已傳輸到網絡中,僅表名它已在套接字上排隊並且ØMQ承擔了對該消息的責任
  • 參數:
    • msg:要發送的消息
    • socket:操作的套接字
    • flags:一些標誌,如下所示:
      • ZMQ_DONTWAIT:對於套接字類型(DEALER,PUSH),當沒有可用的對等點(或所有的對等點有完整的高水位標記)時阻塞,指定該操作應該在非阻塞模式下執行。如果消息不能在套接字上排隊,則zmq_msg_send()函數將失敗,errno設置爲EAGAIN
      • ZMQ_SNDMORE:指定要發送的消息是多部分消息,並且後面還將有其他消息部分。詳情參閱下面“多部分消息”介紹和演示案例②
  • 返回值:
    • 成功:返回消息中的字節數(如果字節數大於MAX_INT,函數將返回MAX_INT)
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EAGAIN:zmq_msg_send()在非阻塞模式(設置了ZMQ_DONTWAIT)下發送消息,但無法發送消息(詳情見上面的ZMQ_DONTWAIT套接字選項)
      • ENOTSUP:此套接字類型不支持zmq_msg_send()操作
      • EINVAL:發送方試圖發送多部分數據,這是套接字類型不允許的
      • EFSM:由於套接字不處於適當的狀態,目前無法在該套接字上執行zmq_msg_send()操作。如果套接字類型在多個狀態之間切換,比如ZMQ_REP,可能會發生此錯誤。有關更多信息,請參閱zmq_socket()的消息傳遞模式部分
      • ETERM:與指定套接字關聯的ØMQ 上下文已終止(可以參閱zmq_ctx_destroy():https://blog.csdn.net/qq_41453285/article/details/105993260
      • ENOTSOCK:提供的套接字無效
      • EINTR:在消息被髮送之前,一個信號的發送中斷了操作
      • EFAULT:無效的消息
      • EHOSTUNREACH:該消息無法路由

多部分消息

  • 多部分消息在文章最初已經介紹過了,詳情見文章最開始即可,此處做一個簡單的介紹就可以了
  • ØMQ消息由1或更多的消息部分。每個消息部分本身是一個獨立的zmq_msg_t
  • 發送多部分消息的應用程序在發送每個消息部分(最後一個消息除外)時必須使用ZMQ_SNDMORE標誌(見下面演示案例②)

演示案例①

  • 下面是一個客戶端程序,其創建一個msg1對象和一個msg2對象,然後把msg1的內容拷貝給msg2,然後再將msg1的數據發送給服務端(這裏想測一下zmq_msg_send()對參數1的影響)
#include <stdio.h>
#include <string.h>
#include <zmq.h>

int main()
{
    // 1.初始化上下文
    void *context = zmq_ctx_new();
    if(context == NULL)
    {
        printf("zmq_ctx_new error\n");
        return -1;
    }

    // 2.創建套接字, 綁定地址
    void *requester = zmq_socket(context, ZMQ_REQ);
    if(zmq_connect(requester, "tcp://localhost:5555") == -1)
    {
        printf("zmq_connect error\n");
        return -1;
    }

    // 3.初始化msg1
    printf("初始化第一條消息msg1:\n");
    zmq_msg_t msg1;
    zmq_msg_init_size(&msg1, 6);
    memcpy(zmq_msg_data(&msg1), "Hello", 6);
    printf("\tmsg1: %s, size: %d\n\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));

    // 4.將msg1拷貝給msg2
    printf("將msg1拷貝給msg2:\n");
    zmq_msg_t msg2;
    zmq_msg_init_size(&msg2, 6);
    zmq_msg_copy(&msg2, &msg1);
    printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));
    
    //5.發送數據
    printf("發送msg1:\n");
    if(zmq_msg_send(&msg1, requester, 0) == -1)
    {
        printf("zmq_msg_send error\n");
        return -1;
    }
    //可以看到zmq_msg_send()是把msg1的大小設置爲0(標誌其不能去使用了), 但是仍可以可以訪問msg1的數據
    printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));

    // 6.關閉msg1, 因爲msg1的大小被設置爲0, 不再使用了
    printf("關閉msg1:\n");
    zmq_msg_close(&msg1);
    //printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1)); //msg1被關閉了, 不能再去使用了
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));
    
    // 7. 關閉msg2
    printf("關閉msg2:\n");
    zmq_msg_close(&msg2);
    //printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2)); //msg2被關閉了, 不能再去使用了
    
    // 8.關閉套接字, 釋放上下文
    zmq_close(requester);
    zmq_ctx_term(context);

    return 0;
}
  • 運行結果如下所示:
    • 左側就是我們上面這個程序,右側是服務端代碼(服務端代碼這裏就不給出了,想看的可以參閱https://github.com/dongyusheng/csdn-code/blob/master/ZeroMQ/hwserver.c) 
    • 可以看到在zmq_msg_send()之後,函數會把參數1的zmq_msg_t對象的大小設置爲0,但是沒有關閉(zmq_msg_close())該對象,因此我們還可以訪問該對象
    • 但是在 zmq_msg_send()之後,建議手動關閉(zmq_msg_close())參數1的zmq_msg_t對象大小

演示案例②

  • 下面發送多部分信息,我們同時像服務端發送了msg1和msg2兩個消息,備註:
#include <stdio.h>
#include <string.h>
#include <zmq.h>

int main()
{
    // 1.初始化上下文
    void *context = zmq_ctx_new();
    if(context == NULL)
    {
        printf("zmq_ctx_new error\n");
        return -1;
    }

    // 2.創建套接字, 綁定地址
    void *requester = zmq_socket(context, ZMQ_REQ);
    if(zmq_connect(requester, "tcp://localhost:5555") == -1)
    {
        printf("zmq_connect error\n");
        return -1;
    }

    // 3.初始化msg1
    printf("初始化第一條消息msg1:\n");
    zmq_msg_t msg1;
    zmq_msg_init_size(&msg1, 6);
    memcpy(zmq_msg_data(&msg1), "Hello", 6);
    printf("\tmsg1: %s, size: %d\n\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));

    // 4.初始化msg2
    printf("初始化第二消息msg2:\n");
    zmq_msg_t msg2;
    zmq_msg_init_size(&msg2, 6);
    memcpy(zmq_msg_data(&msg2), "Hello", 6);
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));
    
    //5.將msg1和msg2原子地發送出去, 其中第一個send需要指明ZMQ_SNDMORE標記
    printf("發送msg1和msg2:\n");
    if(zmq_msg_send(&msg1, requester, ZMQ_SNDMORE) == -1)
    {
        printf("zmq_msg_send error\n");
        return -1;
    }
    if(zmq_msg_send(&msg2, requester, 0) == -1)
    {
        printf("zmq_msg_send error\n");
        return -1;
    }
    //可以看到zmq_msg_send()是把msg1和msg2的大小設置爲0(標誌其不能去使用了), 但是仍可以可以訪問它們的數據
    printf("\tmsg1: %s, size: %d\n", (char*)zmq_msg_data(&msg1), (int)zmq_msg_size(&msg1));
    printf("\tmsg2: %s, size: %d\n\n", (char*)zmq_msg_data(&msg2), (int)zmq_msg_size(&msg2));

    // 6.關閉msg1和msg2, 它們都不再使用了
    printf("關閉msg1和msg2:\n");
    zmq_msg_close(&msg1);
    zmq_msg_close(&msg2);
    
    // 8.關閉套接字, 釋放上下文
    zmq_close(requester);
    zmq_ctx_term(context);

    return 0;
}
  • 效果如下所示:
    • 左側發送了兩條消息給服務端,右側服務端收到兩條消息
    • 注意:服務端是通過調用兩次zmq_msg_recv()接收數據的,不是一次zmq_msg_recv(),見下面解析

  • 服務端代碼解析:服務端核心代碼如下,先是接收客戶端數據,然後再向服務端發送數據。通過上圖我們知道,服務端打印了兩次“Received Hello”,所以服務端執行了兩次while(1),每次執行while循環的時候接收數據,然後發送數據,發送數據的時候由於客戶端沒有接收函數,所以其直接返回繼續執行下一次循環,因此上面打印了兩次“Received Hello”

十七、接收消息(zmq_msg_recv)

int zmq_msg_recv (zmq_msg_t *msg, void *socket, int flags);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-recv
  • 功能:在指定的套接字上接收消息
  • 相關描述:
    • 該函數替換了zmq_recvmsg()函數,zmq_recvmsg()不推薦使用了,關於zmq_recvmsg()可以參閱:http://api.zeromq.org/master:zmq-recvmsg
    • zmq_msg_recv()函數將從套接字參數引用的套接字接收消息部分,並將其存儲在msg參數引用的消息中
    • 如果msg之前存儲有消息則會被正確的釋放
    • 如果指定的套接字上沒有可用的消息部分,則zmq_msg_recv()函數將阻塞,直到滿足請求爲止
  • 參數:
    • msg:用來保存接收的數據
    • socket:操作的套接字
    • flags:一些標誌,如下所示:
      • ZMQ_DONTWAIT:指定該操作在非阻塞模式下執行。如果指定的套接字上沒有可用的消息,則zmq_msg_recv()函數將失敗並將errno設置爲EAGAIN
  • 返回值:
    • 成功:返回接收的消息的字節數。注意,如果消息被截斷,該值可能會超過len參數的值
    • 失敗:返回-1,並將errno設置爲以下定義的值之一:
      • EAGAIN:zmq_msg_recv()函數在非阻塞模式(ZMQ_DONTWAIT)下運行,套接字上沒有數據可接收
      • ENOTSUP:這個套接字類型不支持zmq_msg_recv()操作
      • EFSM:由於套接字不處於適當的狀態,目前無法在該套接字上執行zmq_msg_recv()操作。如果套接字類型在多個狀態之間切換,比如ZMQ_REP,可能會發生此錯誤。有關更多信息,請參閱zmq_socket()的消息傳遞模式部分
      • ETERM:與指定套接字關聯的ØMQ上下文已終止(可以參閱zmq_ctx_destroy():https://blog.csdn.net/qq_41453285/article/details/105993260
      • ENOTSOCK:提供的套接字無效
      • EINTR:在消息被髮送之前,一個信號的發送中斷了操作

多部分消息

  • 多部分消息在文章最初已經介紹過了,詳情見文章最開始即可,此處做一個簡單的介紹就可以了
  • ØMQ消息由1或更多的消息部分。每個消息部分本身是一個獨立的zmq_msg_t
  • 處理多部分消息的應用程序在調用zmq_msg_recv()之後,必須傳遞ZMQ_RCVMORE選項給zmq_getsockopt()來確定是否還有其他部分要接收

演示案例①

  • 從套接字接收消息
// 初始化消息結構
zmq_msg_t msg;
int rc = zmq_msg_init (&msg);
assert (rc == 0);

// 接收消息
rc = zmq_msg_recv (&msg, socket, 0);
assert (rc != -1);

// 釋放消息
zmq_msg_close (&msg);

演示案例②

  • 接收多部分消息
int more;
size_t more_size = sizeof (more);
do {
    // 初始化消息
    zmq_msg_t part;
    int rc = zmq_msg_init (&part);
    assert (rc == 0);
    
    // 接收消息
    rc = zmq_msg_recv (&part, socket, 0);
    assert (rc != -1);
    
    //判斷是否還有消息要接收
    rc = zmq_getsockopt (socket, ZMQ_RCVMORE, &more, &more_size);
    assert (rc == 0);

    // 關閉消息
    zmq_msg_close (&part); 
} while (more);

十八、判斷是否還有很多的消息要接收(zmq_msg_more)

int zmq_msg_more(zmq_msg_t *message);
  • API參考文檔:http://api.zeromq.org/master:zmq-msg-more
  • 功能:查詢是否還有更多消息要接收
  • 相關描述:
    • zmq_msg_more()函數判斷message參數所指的消息是否是由多個部分組成的消息的一部分,以及是否有其他部分要接收
    • 可以在zmq_msg_close()之後安全地調用此方法。該方法與帶有ZMQ_MORE參數的zmq_msg_get()相同
  • 參數:
    • message:要判斷的消息結構
  • 返回值:
    • 成功0:如果這是多部分消息的最後一部分,或者是單部分消息的唯一一部分,返回0
    • 失敗1:如果這是多部分消息的最後一部分,或者是單部分消息的唯一一部分

演示案例

  • 接收多部分信息
zmq_msg_t part;
while (true) {
    
    // 初始化消息
    int rc = zmq_msg_init (&part);
    assert (rc == 0);
    
    // 接收消息
    rc = zmq_msg_recv (socket, &part, 0);
    assert (rc != -1);

    // 判斷是否還有更多消息要接收, 如果繼續循環接收
    if (zmq_msg_more (&part))
        fprintf (stderr, "more\n");
    else { //否則退出循環
        fprintf (stderr, "end\n");
        break;
    }

    //關閉消息
    zmq_msg_close (&part); 
}

十九、其他接口

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