プロセス間通信をしてみた(2)
Windows10の名前付きパイプでの通信の続き

2021年2月7日

どもです。

前回のエントリで、名前付きパイプを使用したプロセス間通信の基本について書きました。
このエントリの内容は、本当に「基本」の部分でした。
そこで今回は、サーバ/クライアントのフロー制御処理を加えた処理について書きます。

1.開発環境

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

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

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

2.フロー制御

名前付きパイプのフロー制御は、簡単ですが図表にしたものが下図になります。

この図では、フロー制御のために「イベント」を使用しています。
フロー制御の内容を、簡単に説明します。

  1. サーバ側で「名前付きパイプ」と「イベント」を生成します。
  2. サーバ側で、生成した「イベント」に対して「イベント発行待ち」とします。
  3. クライアント側は、サーバが生成したイベントに対して、「イベント発行」を行います。
  4. すると、サーバ側の「発行待ち」が解除され、制御が返ります。
  5. このとき、サーバ側は発行されたイベントを「リセット」します。
  6. 次にサーバ側は、データの読み出しを行います。
    この読み出しでは、実際にデータが書き込まれるまで制御が返りません。
  7. クライアント側がデータを送信(名前付きパイプに対して書き込み)を実施します。
  8. サーバ側では、制御とともにクライアントが書き込んだデータが返ります。
  9. データの書き込みが完了したクライアント側は、「イベント発行待ち」とします。
  10. サーバ側は、読み出したデータ処理完了後、「イベント発行」を行います。
  11. クライアント側に制御が返り、処理が再開されます。

3.コード

前述の処理を行うサーバ側/クライアント側のコードを、それぞれ(一部抜粋ですが)記載します。

3.1.サーバ側

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

DWORD   WINAPI  ServerThreadProc(LPVOID threadParam);

int main()
{
    //
    //...(省略)...
    //
    int recvCount = 0;
    do {
        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"));
        }

        pipeHandles.hEvent = CreateEvent(NULL, FALSE, FALSE, _T("IPC_SAMPLE"));
        WaitForSingleObject(pipeHandles.hEvent, INFINITE);

        while (1) {
            _TCHAR  readBuffer[256];
            DWORD   readBufferSizeInByte = sizeof(readBuffer);
            DWORD   readDataSize = 0;
            ZeroMemory(readBuffer, readBufferSizeInByte);

            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;
            }
        }
    } while (0);
    SetEvent(pipeHandles.hEvent);
    CloseHandle(pipeHandles.hPipe);
    
    //
    //...(省略)...
    //
    return nRetCode;
}

3.2.クライアント側

int main()
{
    //
    //...(省略)...
    //
    HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, _T("IPC_SAMPLE"));
    if (NULL == hEvent) {
        _tprintf(_T("Error event open.\r\n"));
        return (-1);
    }
    HANDLE hPipe = CreateFile(_T("\\\\.\\pipe\\sample_pipe"),
        GENERIC_WRITE,
        0, 
        NULL, 
        OPEN_EXISTING, 
        FILE_ATTRIBUTE_NORMAL, 
        NULL);
    if (INVALID_HANDLE_VALUE == hPipe) {
            _tprintf(_T("Can not create file, as PIPE\r\n"));
    }
    else {
        _tprintf(_T("Can create file, as PIPE\r\n"));
        int loopCount = 0;
        int maxLoopNum = 13;

        while (1) {
            _TCHAR buffer[256];
            ZeroMemory(buffer, sizeof(buffer));
            DWORD writtenWordSize = 0;
            DWORD numberOfByteToWrite = 0;
            BOOL isExitFlag = FALSE;
            if (loopCount < maxLoopNum) {
                _stprintf_s(buffer, _T("CLIENT SEND DATA = %d"), loopCount);
                numberOfByteToWrite = _tcsclen(buffer) * sizeof(TCHAR);
                isExitFlag = FALSE;
            }
            else {
                buffer[0] = 0;
                numberOfByteToWrite = sizeof(_TCHAR);
                isExitFlag = true;
            }
            SetEvent(hEvent);
            BOOL writeRes = WriteFile(hPipe, buffer, numberOfByteToWrite, (LPDWORD)&writtenWordSize, NULL);
            if (!writeRes) {
                _tprintf(_T("Can not write data into file.\r\n"));
            }
            else {
                _tprintf(_T("Can write data into file.(data size = %d)\r\n"), writtenWordSize);
            }

            if (FALSE != isExitFlag) {
                break;
            }
            loopCount++;
        }

        CloseHandle(hPipe);
        WaitForSingleObject(hEvent, INFINITE);
    }
    CloseHandle(hEvent);
    //
    //...(省略)...
    //
    return nRetCode;
}

4.関数の説明

シーケンス図と対応させながら、プログラムの説明を行います。

4.1.イベントの生成

「イベントの生成」は、CreateEvent()関数で行います。
第1~第3引数の詳細は、Microsoftのサイトを参照してください。
重要なのは第4引数であり、イベントの「名前」をこれで指定します。
このイベントの名前は、クライアント側でイベントのハンドルを取得する(OpenEvent()関数)際にも使用しています。

4.2.イベントの発行

イベントの発行は、SetEvent()関数で行います。
このとき、CreateEvent()/OpenEvent()で取得したハンドルを、引数に指定します。

4.3.イベント発行待ち

イベント発行待ちは、WaitForSingleObject()関数で行います。
シーケンス図でも書いていますが、この関数を実行すると、第1引数に対応するイベントが発行されるまで、処理が停止します。
この停止する機関は、第2引数で指定します。
上述のプログラムでは「INFINITE」を指定しています。
これは、「イベントが発行されるまでずっと待ち続ける」よう指定しています。

4.4.イベント初期化

イベントの初期化は、ResetEvent()関数で行います。
この関数を実行することで、次にSetEvent()関数が実行されるまで、WaitForSingleObject()関数でサーバまたはクライアント側の処理を一時的に停止させることができるようになります。

5.まとめ

今回のエントリでは、名前付きパイプを使用したプロセス間通信でのフロー制御について書きました。
諸々の内容を詳細に説明するのは困難なので、簡単な説明とサンプルコードのみとなってしまいました…。
それでも、「イベント」を使用したフロー制御の基本的な部分の説明はできているかと思います。

ではっ!