一、ØMQ模式總覽
- ØMQ支持多種模式,具體可以參閱:https://blog.csdn.net/qq_41453285/article/details/106865539
- 本文介紹ØMQ的獨家對模式
二、獨家對模式
- 在前面的文章中我們介紹過如何編寫ØMQ多線程程序:https://blog.csdn.net/qq_41453285/article/details/106882216
- 獨家對模式(Exclusive pair)用於將一個對等點精確地連接到另一個對等點。此模式用於跨inproc傳輸的線程間通信
- 互斥對模式由http://rfc.zeromq.org/spec:31正式定義
- “獨家對模式”支持的套接字類型只有1種:
- ZMQ_PAIR
三、“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成爲線程對之間協調的最佳選擇
五、節點協調
- 當你想協調節點時,PAIR套接字就無法正常工作了,這是線程和節點的策略不同的少數地方之一。詳情參閱下一篇文章:https://blog.csdn.net/qq_41453285/article/details/106949903