【原創】modb 功能設計之「跨線程通訊」


      在《 modb 功能設計之「支持多消費者單生產者」 》中曾提到,須要解決「rabbitmq 線程與 sql 執行線程之間的跨線程通訊手段」。本文針對這個問題進行一些說明。

【第一個版本】
      使用 pipe 做爲跨線程通訊方式,使用以下代碼來支持 pipe:
# 使用 _pipe 來模擬 pipe 
#if defined(WIN32) || defined(_WIN32) 
#include <io.h> 
#include <fcntl.h> 
#define pipe(fds) _pipe(fds, 4096, _O_BINARY) 
#endif

      此時運行實現了 sql 線程的 demo,發現 sql 線程老是沒法正常運行,在調用 libevent 接口 event_base_dispatch 時會有錯誤 「10038」 報出。最終查出的結論是: linux

WSAENOTSOCK                    (10038)             Socket operation on non-socket.

操做試圖不是在套接字上進行。它多是套接字句柄參數沒有引用到一個合法套接字,或者是調用 select() 函數時,一個 fd_set 中的成員不合法。 sql

      查來查去,結果發現,就是 windows 下不能將 pipe(實際上是 _pipe)和 select 一塊兒使用的緣由。由於 windows 平臺上會認爲 pipe 產生的 fd 不是一個合法的 socket 句柄,在將讀端 fds[0] 添加進 libevent 進行可讀監聽後(在 windows 平臺下 libevent 使用 select 進行 fd 的檢測),就會報出上面的 「10038」 錯誤;與此對應的,linux 下認爲「一切皆文件」,因此是沒有這個問題的。這讓我再一次鄙視了 MS Windows。

_pipe() 的實現以下:
int __cdecl _pipe ( 
        int phandles[2], 
        unsigned psize, 
        int textmode 
        ) 
{ 
    int handle0, handle1; 
    HANDLE ReadHandle, WriteHandle; 
    ... 
    if (!CreatePipe(&ReadHandle, &WriteHandle, &SecurityAttributes, psize)) {  // 調用 win api 建立 pipe 
        ... 
        return -1; 
    } 
    /* now we must allocate C Runtime handles for Read and Write handles */ 
    if ((handle0 = _alloc_osfhnd()) != -1) {   // 獲取可用的 C 運行時句柄用做 pipe 的讀端 
        ... 
        if ((handle1 = _alloc_osfhnd()) != -1) {  // 獲取可用的 C 運行時句柄用做 pipe 的寫端 
        ... 
        _set_osfhnd(handle0, (intptr_t)ReadHandle);  // 將 CRT 文件句柄和 Win32 HANDLE 進行關聯 
        _set_osfhnd(handle1, (intptr_t)WriteHandle); 
        } 
    } 
    ... 
    phandles[0] = handle0; 
    phandles[1] = handle1; 
     
    return 0; 
}

      沒辦法,由於有 windows 下調試的須要,既然在 windows 下沒法對非 socket fd 進行 select,那麼可選方案只有經過 socket 來實現 pipe 了,備選方案以下: shell

  1. 經過 socket 模擬 pipe 的實現
  2. 使用 socketpair 實現(一樣是經過 socket 來模擬 unix 下的 socketpair)
PS:另一個出現的錯誤狀況是,windows 下再調用 select() 前,須要先調用 WSAStartup(),不然會報出 「10093」 錯誤。 
WSANOTINITIALISED           (10093)             Successful WSAStartup() not yet performed.

應用程序沒有調用 WSAStartup() 函數,或函數 WSAStartup() 調用失敗了。應用程序可能訪問了不屬於當前活動任務的套接字(例如試圖在任務間共享套接字),或調用了過多的 WSACleanup() 函數。  windows


【第二個版本】

      翻閱大量開源軟件的代碼後,通過 對比發現後發現,二者的實現代碼基本相同,只是名字不一樣而已。換句話說,經過 socket 模擬出來的這個東東,既能夠替代 pipe(一般用於具備父子關係的進程間通訊)的功能,也能夠替代 socketpair(一般用於 AF_UNIX 域)的功能。

      雖然上面說過,使用 socket 模擬 pipe 和模擬 socketpair 在代碼實現上是沒有差異的,但並非說 linux 下的 pipe 和 socketpair 沒有差異,因此下面的討論以實現 pipe 爲例。 具體代碼能夠參考我以前寫過的一篇博客:《 MySQL Proxy中socketpair的使用 》。

      在採用 socket 實現的 pipe 後,首先遇到的問題是,調用 write( sql_event_thread->notify_send_fd, "", 1 ) 會報以下斷言錯誤:
vctools\crt_bld\self_x86\crt\src\write.c 
Line: 67 
Expression: (fh >= 0 && (unsigned)fh < (unsigned)_nhandle)

其中 fh 爲程序建立的 socket 句柄的值(例如 12116),_nhandle 是系統定義值(發現其值爲 32)。 _nhandle 爲何是 32 ?這個值表明什麼意思?(讀者思考)

api

      總之,結論代表在 windows 平臺下,對 socket fd 直接進行 read/write 會有錯誤產生。 沒辦法仍是換成標準的 socket 函數 send/recv 吧。

結果一切都 ok 了。


=== 未完待續 ===
相關文章
相關標籤/搜索