zeromq原始碼分析3------zeromq中的訊息_程式碼分析

來源:互聯網
上載者:User

本文主要講zeromq的訊息體系zmq_msg_t及其相關的操作函數。

因為tcp是一種位元組流類型的協議,木有邊界,所以把該訊息邊界的制定留給了應用程式層。通常有兩種方式實現:

1. 在傳輸的資料中添加分隔字元。

2. 在每條訊息中添加size欄位。

而zeromq可以說選擇了第二種方式。

先來看看zmq_msg_t的基本資料結構

/*  A message. Note that 'content' is not a pointer to the raw data.          *//*  Rather it is pointer to zmq::msg_content_t structure                      *//*  (see src/msg_content.hpp for its definition).                             */typedef struct{       void *content;    unsigned char flags;    unsigned char vsm_size;// 小訊息的大小    unsigned char vsm_data [ZMQ_MAX_VSM_SIZE];// 小訊息的記憶體地區} zmq_msg_t
其中content指標指向的是zmq::msg_content_t的結構, flags表示訊息的一些flags

這邊vsm_size和vsm_data是針對very small message的小訊息做一些最佳化,直接在stack上分配記憶體了,可以看後面的訊息函數的具體操作。

//  Shared message buffer. Message data are either allocated in one    //  continuous block along with this structure - thus avoiding one    //  malloc/free pair or they are stored in used-supplied memory.    //  In the latter case, ffn member stores pointer to the function to be    //  used to deallocate the data. If the buffer is actually shared (there    //  are at least 2 references to it) refcount member contains number of    //  references.        struct msg_content_t    {        void *data;        size_t size;        zmq_free_fn *ffn;        void *hint;        zmq::atomic_counter_t refcnt;    };

其中data指向真正的訊息資料,size表示訊息資料的位元組大小,zmq_free_fn *ffn指向釋放函數,refcnt表示訊息的引用計數,hint目前未知。


下面我們看一下基本的訊息作業函數:

int zmq_msg_init_size (zmq_msg_t *msg_, size_t size_){    if (size_ <= ZMQ_MAX_VSM_SIZE) {        msg_->content = (zmq::msg_content_t*) ZMQ_VSM;        msg_->flags = (unsigned char) ~ZMQ_MSG_MASK;        msg_->vsm_size = (uint8_t) size_;    }    else {        msg_->content =            (zmq::msg_content_t*) malloc (sizeof (zmq::msg_content_t) + size_);        if (!msg_->content) {            errno = ENOMEM;            return -1;        }        msg_->flags = (unsigned char) ~ZMQ_MSG_MASK;            zmq::msg_content_t *content = (zmq::msg_content_t*) msg_->content;        content->data = (void*) (content + 1);        content->size = size_;        content->ffn = NULL;        content->hint = NULL;        new (&content->refcnt) zmq::atomic_counter_t (); // 設定引用計數    }    return 0;}   

對於小訊息來說, 我們設定content指向地址ZMQ_VSM,這個值為32,相當於一個魔法數字,用來表示小訊息。

對於相對來說的大訊息來說,我們malloc (sizeof (zmq::msg_content_t) + size_)的空間,其中size_就是可變長的訊息大小,然後設定content->data = (void *) (content + 1),指向這塊可變長的記憶體地區。

這邊有一個初始化msg的flags的操作:

        msg_->flags = (unsigned char) ~ZMQ_MSG_MASK;
flags的定義:

/*  Message flags. ZMQ_MSG_SHARED is strictly speaking not a message flag     *//*  (it has no equivalent in the wire format), however, making  it a flag     *//*  allows us to pack the stucture tigher and thus improve performance.       */#define ZMQ_MSG_MORE 1 // 00000001#define ZMQ_MSG_SHARED 128 // 10000000#define ZMQ_MSG_MASK 129 /* Merges all the flags */ // 10000001

我們簡單分析下這樣就可以msg的flags的初始值就是01111110,即可以| ZMQ_MSG_MORE或者| ZMQ_MSG_SHARED。

尼瑪。。。感覺在說廢話。。。


我們再來看看另外兩個訊息初始化函數:

int zmq_msg_init (zmq_msg_t *msg_){    msg_->content = (zmq::msg_content_t*) ZMQ_VSM;    msg_->flags = (unsigned char) ~ZMQ_MSG_MASK;    msg_->vsm_size = 0;    return 0;}
初始化一個msg訊息,設定成小訊息類型,初始化flags,並且將小訊息的大小初始化為0.

int zmq_msg_init_data (zmq_msg_t *msg_, void *data_, size_t size_,    zmq_free_fn *ffn_, void *hint_){    msg_->content = (zmq::msg_content_t*) malloc (sizeof (zmq::msg_content_t));    alloc_assert (msg_->content);    msg_->flags = (unsigned char) ~ZMQ_MSG_MASK;    zmq::msg_content_t *content = (zmq::msg_content_t*) msg_->content;    content->data = data_;    content->size = size_;    content->ffn = ffn_;    content->hint = hint_;    new (&content->refcnt) zmq::atomic_counter_t ();    return 0;}

該函數根據已有的data和相應的size,和銷毀的函數,以及hint來初始化訊息。

這邊訊息指向data指標所指的記憶體地區。


接下來我們來看看擷取資料的地址的函數,這樣你就能操作訊息的資料記憶體地區了。

void *zmq_msg_data (zmq_msg_t *msg_){    zmq_assert ((msg_->flags | ZMQ_MSG_MASK) == 0xff);    if (msg_->content == (zmq::msg_content_t*) ZMQ_VSM)        return msg_->vsm_data;    if (msg_->content == (zmq::msg_content_t*) ZMQ_DELIMITER)        return NULL;    return ((zmq::msg_content_t*) msg_->content)->data;}
如果是小訊息那麼就是stack上的vsm_data地址,大訊息的就是heap上的content->data。

如果是delimiter的話,返回的是null 指標。 我們後面會講述delimiter的用處。


而zmq_msg_size(1)函數也是類似的處理方式,只不過返回的是訊息大小。

size_t zmq_msg_size (zmq_msg_t *msg_){    zmq_assert ((msg_->flags | ZMQ_MSG_MASK) == 0xff);    if (msg_->content == (zmq::msg_content_t*) ZMQ_VSM)        return msg_->vsm_size;    if (msg_->content == (zmq::msg_content_t*) ZMQ_DELIMITER)        return 0;    return ((zmq::msg_content_t*) msg_->content)->size;}

接下來我們來看zmq_msg_close(1)函數, 看看如何銷毀訊息的。

int zmq_msg_close (zmq_msg_t *msg_){    //  Check the validity tag.    if (unlikely (msg_->flags | ZMQ_MSG_MASK) != 0xff) {        errno = EFAULT;        return -1;    }    //  For VSMs and delimiters there are no resources to free.    if (msg_->content != (zmq::msg_content_t*) ZMQ_DELIMITER &&          msg_->content != (zmq::msg_content_t*) ZMQ_VSM) {        //  If the content is not shared, or if it is shared and the reference.        //  count has dropped to zero, deallocate it.        zmq::msg_content_t *content = (zmq::msg_content_t*) msg_->content;        if (!(msg_->flags & ZMQ_MSG_SHARED) || !content->refcnt.sub (1)) {            //  We used "placement new" operator to initialize the reference.            //  counter so we call its destructor now.            content->refcnt.~atomic_counter_t ();            if (content->ffn)                content->ffn (content->data, content->hint);            free (content);        }    }    //  Remove the validity tag from the message.    msg_->flags = 0;    return 0;}

訊息的flag檢測和前面類似,該函數末尾除還會移除初始化的flags,將其置成0,表明該訊息已經廢了。

對於小訊息,訊息資料是分配在stack上的,因此不需要手動銷毀。

對於大訊息,訊息資料是分配在heap上的,因此我們查看訊息flags是否採用ZMQ_MSG_SHARED共用模式,如果沒有使用就直接銷毀。假如使用了共用模式,那麼我們遞減訊息的引用計數,一旦引用計數為0,我們就銷毀訊息內容。

銷毀過程如下:

1. 銷毀引用計數,因為在建立引用計數的時候我們使用的是placement new所以這邊我們需要調用它的解構函式。

2. 如果有註冊相應的自訂的銷毀函數,就調用該函數。這種情況主要是用於使用zmq_msg_init_data(),自己來管理data所指向空間。

3. 調用free(1)釋放heap的空間, 注意 如果是使用zmq_msg_init_size()來初始化訊息的,就會釋放掉data的空間,因為這塊空間就是分配時多分配的size大小(在content下方)的那塊。


至於zmq_msg_move和zmq_msg_copy主要是訊息的move和copy。

int zmq_msg_move (zmq_msg_t *dest_, zmq_msg_t *src_){#if 0    //  Check the validity tags.    if (unlikely ((dest_->flags | ZMQ_MSG_MASK) != 0xff ||          (src_->flags | ZMQ_MSG_MASK) != 0xff)) {        errno = EFAULT;        return -1;    }#endif    zmq_msg_close (dest_);    *dest_ = *src_;    zmq_msg_init (src_);    return 0;}int zmq_msg_copy (zmq_msg_t *dest_, zmq_msg_t *src_){    //  Check the validity tags.    if (unlikely ((dest_->flags | ZMQ_MSG_MASK) != 0xff ||          (src_->flags | ZMQ_MSG_MASK) != 0xff)) {        errno = EFAULT;        return -1;    }    zmq_msg_close (dest_);    //  VSMs and delimiters require no special handling.    if (src_->content != (zmq::msg_content_t*) ZMQ_DELIMITER &&          src_->content != (zmq::msg_content_t*) ZMQ_VSM) {        //  One reference is added to shared messages. Non-shared messages        //  are turned into shared messages and reference count is set to 2.        zmq::msg_content_t *content = (zmq::msg_content_t*) src_->content;        if (src_->flags & ZMQ_MSG_SHARED)            content->refcnt.add (1);        else {            src_->flags |= ZMQ_MSG_SHARED;            content->refcnt.set (2);        }    }    *dest_ = *src_;    return 0;}

zmq_msg_move(1)將source的訊息重設成初始化的空訊息。

而zmq_msg_copy(1)dst和src共用訊息content的引用,主要注意引用計數的變更。如果已經是SHARED模式則增加引用計數,否則則設定成SHARED模式,並將引用技術設定成2。

有了這些知識,下面我們來看一下基本的訊息作業的樣本:

//  Receive 0MQ string from socket and convert into C stringstatic char *s_recv (void *socket) {    zmq_msg_t message; // 建立訊息結構    zmq_msg_init (&message); // 初始化空訊息    zmq_recv (socket, &message, 0); // 接收訊息    int size = zmq_msg_size (&message); // 計算訊息的大小    char *string = malloc (size + 1); // 分配string為指向size + 1大小的heap空間,那個多出來的1位元組是'\0'的空間    memcpy (string, zmq_msg_data (&message), size); // 通過zmq_msg_data(1)獲得訊息的data地址,拷貝到字串中    zmq_msg_close (&message); // 釋放或銷毀訊息    string [size] = 0; // 設定'\0'    return (string);}//  Convert C string to 0MQ string and send to socketstatic ints_send (void *socket, char *string) {    int rc;    zmq_msg_t message; // 建立訊息結構    zmq_msg_init_size (&message, strlen (string)); // 以字串長度(不包括'\0')初始化成訊息    memcpy (zmq_msg_data (&message), string, strlen (string)); // 將字串的內容(不包括'\0')拷貝給訊息    rc = zmq_send (socket, &message, 0); // 發送訊息    assert (!rc);    zmq_msg_close (&message); // 釋放和銷毀訊息    return (rc);}

從上面的注釋可以看出收發訊息的流程。zeromq有相關的庫(czmq)封裝了這些操作,當然你也可以自己封裝。


ZMQ_DELIMITER:

剛才上文曾經提到過delimiter類型的訊息。這種類型的訊息類似於終結者的意思,主要在收發的管道中使用。因為zeromq會將訊息先發送到管道中,然後poller運行在另外一個線程,將管道中的資料讀出來發往socket的緩衝區,所以可以發送一個delimiter類型的訊息去終結管道,銷毀它。在以後我們分析管道的過程中,你能看到做這個工作的代碼。


總結:

本文簡單地介紹了zeromq中定義的訊息的資料結構以及相關的操作,基本上zeromq的訊息的資料結構就採用了可變長的資料結構來存放data,還會對小訊息進行記憶體配置上面的最佳化,直接使用stack分配,而不是使用heap動態分配,訊息content擁有引用計數可以共用之。

這邊我們看到訊息flags的時候我們發覺漏講了一個#define ZMQ_MSG_MORE 1的標誌,這個標誌是multipart message使用的。下一次我們就會分析這塊內容,並且會看看訊息發送到管道後,poller將其取出來發送給socket緩衝區的具體細節以及反過來接收訊息的過程,敬請期待。

希望有興趣的朋友可以和我聯絡,一起學習。 kaka11.chen@gmail.com








聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.