プロセス間通信をしてみた(4)
プロセス間通信でエコーバックしてみた

2021年2月7日

どもです。
これまでの記事で、名前付きパイプを使用したプロセス間通信について書いてきました。
基本的な部分は既に書いたので、今回のエントリでは、これまで書いてきた内容を使用してプロセス間でのエコーバックをしてみました。
なおこれまでの記事は、以下のリンクを参照してください。
プロセス間通信をしてみた(1)
Windows10の名前付きパイプでの通信

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

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

1.開発環境

まずは、毎度おなじみ/毎度同じの今回の開発環境を、下表に示します。

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

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

2.エコーバックの仕様

まず、今回実施したエコーバックの仕様について書きます。
なお、この仕様は、私が勝手に決めた仕様です。
他のトコロでは一切通用しませんので、ご了承ください。

2.1.シーケンス

基本的なシーケンスは、下図の通りです。
named_pipe_windows_004_001
クライアント側が送信したデータをサーバーが受信し、サーバ側は受信したデータを処理して返信データを作成、送信します。
返信データを受信したクライアント側は、受信したデータを処理して、次の送信データを作成して送信します。

2.2.送信するデータ

クライアント/サーバ間で送受信するデータのフォーマットを、以下に示します。
named_pipe_windows_004_002
(少しハンパですが)データのサイズは、6バイトとしています。
先頭2バイトで、送信回数を示しています。
残りの4バイトは、それぞれ0xFFで固定としています。

3.エコーバックの設計

次に、エコーバックの設計です。

3.1.シーケンス

エコーバックのシーケンス図を、下図に示します。
named_pipe_windows_004_003
基本的には、過去の記事で書いたシーケンスを発展させた内容です。
クライアント側/クライアント側の両方でイベント発行/イベント待ちを行い、お互いに動作を制御します。

3.2.実装

次に、この処理を実装してみます。

3.2.1.サーバ側

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_DUPLEX,
            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)
{
    BYTE    readBuffer[256];
    BYTE    sendBuffer[256];
    DWORD   readBufferSizeInByte = sizeof(readBuffer);
    ZeroMemory(readBuffer, readBufferSizeInByte);
    ZeroMemory(sendBuffer, sizeof(sendBuffer));

    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;
        }

        //Copy read data into a buffer to keep send(response) data.
        for (int index = 0; index < readDataSize; index++) {
            sendBuffer[index] = readBuffer[index];
        }

        //Update response data.
        LPWORD bufferToSend = (LPWORD)sendBuffer;
        (*bufferToSend)++;

        //Sleep(10);

        SetEvent(pipeHandles->hEvent);
        DWORD writtenDataSize = 0;
        BOOL writeResult = WriteFile(pipeHandles->hPipe, sendBuffer, readDataSize, (LPDWORD)&writtenDataSize, NULL);

        //Show received and sent data in buffers.
        _tprintf(_T("Rcv : "));
        for (int index = 0; index < readDataSize; index++) {
            _tprintf(_T("0x%02X "), readBuffer[index]);
        }
        _tprintf(_T("\r\n"));
        _tprintf(_T("Snd : "));
        for (int index = 0; index < writtenDataSize; index++) {
            _tprintf(_T("0x%02X "), sendBuffer[index]);
        }
        _tprintf(_T("\r\n"));

        WORD topData = (WORD)(*(LPBYTE)(&readBuffer[0]));
        if (0 == topData) {
            _tprintf(_T("End data received.(0x%04x)\r\n"), topData);
            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;
}

3.2.2.クライアント側

int main()
{
    int nRetCode = 0;

    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 | GENERIC_READ,
        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 = 0xFFFF;

        while (1) {
            BYTE sendBuffer[256];
            ZeroMemory(sendBuffer, sizeof(sendBuffer));

            BOOL isExitFlag = FALSE;

            //Set data to send.
            LPWORD bufferToSet = (LPWORD)sendBuffer;
            if (loopCount < maxLoopNum) {
                *bufferToSet = (WORD)((loopCount * 2) + 1);
                isExitFlag = FALSE;
            }
            else {
                *bufferToSet = (WORD)0;
                isExitFlag = true;
            }
            bufferToSet++;
            *bufferToSet = (WORD)0xFFFF;
            bufferToSet++;
            *bufferToSet = (WORD)0xFFFF;

            DWORD writtenByteSize = 0;
            DWORD numberOfByteToWrite = 6;
            SetEvent(hEvent);
            BOOL writeRes = WriteFile(hPipe, sendBuffer, numberOfByteToWrite, (LPDWORD)&writtenByteSize, NULL);
            if (!writeRes) {
                _tprintf(_T("Can not write data into file.\r\n"));
            }
            WaitForSingleObject(hEvent, INFINITE);
            BYTE recvBuffer[256] = { 0 };
            DWORD readDataSize = 0;
            BOOL readRes = ReadFile(hPipe, recvBuffer, sizeof(recvBuffer), (LPDWORD)&readDataSize, NULL);
            ResetEvent(hEvent);
            if (FALSE == readRes) {
                _tprintf(_T("Can not receive da ta.\r\n"));
                break;
            }
            else {
                //Show sent and received data buffers.
                _tprintf(_T("Snd : "));
                for (int index = 0; index < writtenByteSize; index++) {
                    _tprintf(_T("0x%02X "), sendBuffer[index]);
                }
                _tprintf(_T("\r\n"));
                _tprintf(_T("Rcv : "));
                for (int index = 0; index < readDataSize; index++) {
                    _tprintf(_T("0x%02X "), recvBuffer[index]);
                }
                _tprintf(_T("\r\n"));
            }

            if (FALSE != isExitFlag) {
                break;
            }

            Sleep(1);
            loopCount++;
        }

        CloseHandle(hPipe);
        _tprintf(_T("Wait for event.\r\n"));
        WaitForSingleObject(hEvent, INFINITE);

        _tprintf(_T("Event is activated.\r\n"));
    }
    CloseHandle(hEvent);

    return nRetCode;
}

3.3.プログラムについて

上述のコードは、これまで紹介してきた内容を一部改変したのみです。
そのため、詳細な説明は割愛します。

4.まとめ

今回は、いわゆる「オレオレ仕様」ですが、サーバ/クライアント間でのエコーバック処理による送受信処理を示しました。
お互いの送受信待ち制御は、今回紹介したコードで基本的な部分は示せているかと思います。
同じような処理を行う場合に、参考になればと思います。

ではっ!

ex.公開しています

今回のコードは、GitHubで公開しています。
今回紹介したコードの全ては、コチラを参考にしてください。