プロセス間通信をしてみた(3)
サーバ側をマルチスレッド化してみた

2021年2月7日

どもです。
前回前々回のエントリで、名前付きパイプを使用したプロセス間通信とそのフロー制御について書きました。
これらのエントリでは、サーバ側/クライアント側の両方とも、シングルスレッドで動作していました。
今回のエントリでは、サーバ側を「マルチスレッド化」してみたので、その内容について書きます。

1.開発環境

今回の開発環境を、下表に示します。

項目 内容
OS Windows10 Pro(1909)
CPU i7-8700
メモリ 16GB
IDE VisualStudioCommnuity 2019
version 16.5.1

※VisualStudioのプロジェクトは、前回のエントリのモノを引き継いで使用/追加しています。

2.サーバ側のマルチスレッド化

2.1.コード

いきなりですが、サーバ側のコードを示します。

using namespace std;

typedef struct _PIPE_HANDLES {
    HANDLE  hPipe;
    HANDLE  hEvent;
} PIPE_HANDLES, * PPIPE_HANDLES;

DWORD   WINAPI  ServerThreadProc(LPVOID threadParam);

int main()
{
    int nRetCode = 0;
    
    //
    //  ...(省略)...
    //
    int recvCount = 0;
    while (recvCount < 10) {
        pipeHandles.hPipe = INVALID_HANDLE_VALUE;
        pipeHandles.hPipe = CreateNamedPipe(_T("\\\\.\\pipe\\sample_pipe"),
            PIPE_ACCESS_INBOUND,
            PIPE_TYPE_BYTE | PIPE_WAIT,
            1,
            0,
            0,
            100,
            NULL);
        if (INVALID_HANDLE_VALUE == pipeHandles.hPipe) {
            _tprintf(_T("The pipe can not open.\r\n"));
        }
        else {
            _tprintf(_T("The pipe can open.\r\n"));
        }
        DWORD threadId = 0;
        pipeHandles.hEvent = CreateEvent(NULL, FALSE, FALSE, _T("IPC_SAMPLE"));
        HANDLE threadHandle = (HANDLE)_beginthreadex(
            NULL, 
            0, 
            (_beginthreadex_proc_type)ServerThreadProc, 
            (LPVOID)((PPIPE_HANDLES)(&pipeHandles)), 
            0,
            (unsigned int*)&threadId);
        if (NULL == threadId) {
            _tprintf(_T("Thread can not create.\r\n"));
            return (-1);
        }
        else {
            _tprintf(_T("Thread created.\r\n"));
        }
        WaitForSingleObject(threadHandle, INFINITE);
        if (CloseHandle(threadHandle)) {
            _tprintf(_T("Thread (and its handle) closed.\r\n"));
        }
        _tprintf(_T("Program end.\r\n"));
        Sleep(100);
        recvCount++;
    }
    //
    //  ...(省略)...
    //
    return nRetCode;
}

DWORD WINAPI    ServerThreadProc(LPVOID threadParam)
{
    _TCHAR  readBuffer[256];
    DWORD   readBufferSizeInByte = sizeof(readBuffer);
    ZeroMemory(readBuffer, readBufferSizeInByte);

    PPIPE_HANDLES   pipeHandles = (PPIPE_HANDLES)threadParam;
    int loopCount = 0;

    while (1)
    {
        WaitForSingleObject(pipeHandles->hEvent, INFINITE);
        DWORD   readDataSize = 0;
        BOOL readResult = ReadFile(pipeHandles->hPipe, readBuffer, readBufferSizeInByte, (LPDWORD)&readDataSize, NULL);
        ResetEvent(pipeHandles->hEvent);
        if (FALSE == readResult) {
            _tprintf(_T("Can not read data.\r\n"));
            break;
        }
        else {
            _tprintf(_T("Read data : %s\r\n"), readBuffer);
        }

        if (0 == readBuffer[0]) {
            break;
        }
    }

    _tprintf(_T("Exit loop\r\n"));
    SetEvent(pipeHandles->hEvent);

    if (CloseHandle(pipeHandles->hPipe)) {
        _tprintf(_T("Pipe close.\r\n"));
        _tprintf(_T("Exit loop thread.\r\n"));
    }
    return 0;
}

2.2.コードの解説

簡単ですが、上述のコードの解説を行います。

2.2.1.main関数()

main()関数の中では、_beginthreadex()関数を使用してスレッドを生成しています。
今回のサンプルでは、生成するスレッドに対してパイプとイベントのハンドルを渡します。
そのために、_beginthreadex()関数を使用します。
引数情報は、VOID型のポインタに変換して第4引数に渡します。
なお、スレッドを生成する関数の詳細は、Microsoftのサイトを参照してください。
スレッドを生成した後は、WaitForSingleObject()でスレッドの終了を待ちます。
生成したスレッドが終了するとイベントが発生し、WaitForSingleObject()から制御が返ります。
(Microsoftのサイトにも、WaitForSingleObject()を使用したサンプルが記載されています。)

2.2.2.ServerThreadProc関数

ServerThreadProc()が、生成されるスレッド/処理です。
引数はVOID型のポインタで渡されるので、PIPE_HANDLES構造体(独自の構造体)のポインタにキャストして使用しています。
この関数の処理は、前回のエントリで示したサーバー側のコードを、(ほぼ)そのまま使用しています。
(WaitForSingleObject()の実行箇所のみ、変更しています。)

3.まとめ

今回のエントリは、サーバ側の処理をマルチスレッドに変更する際の実装例を示しました。
それだけです!
次回は、今回紹介したコードをもう少し発展させて、名前付きパイプを使用した「エコーバック」に挑戦します。

ではっ!