Ayumu's I/O

ねだるな勝ち取れ、さすれば与えられん

名前付きパイプによるプロセス間通信をやってみる

Windowsにおいてプロセス間通信(IPC)に使われる名前付きパイプについて調べました。

名前付きパイプ(Named Pipe)とは

名前付きパイプとは、プロセス間のデータ転送のためのプログラミングAPI。メールスロットと異なり、信頼される双方向通信を実現できる。ファイルのようにアクセスでき、Windows I/Oの標準関数であるCreateFile関数、ReadFile関数、WriteFile関数、CloseHandle関数を使って扱うことができる。生成時に作成するインスタンスの個数を指定できるため、1つのサーバが複数のクライアントと通信することもできる。また、サーバが自身のアカウント権限ではなくクライアント側のアカウント権限でスレッドの実行する、偽装(Impersonation)と呼ばれる機能も利用できる。名前付きパイプの関数は、kernel32.dllに実装されている。

f:id:aes256:20171204190757p:plain
図1.名前付きパイプ(PIPE_ACCESS_INBOUND)による通信のイメージ

CreateNamedPipe関数

名前付きパイプは、CreateNamedPipe関数を使ってインスタンスを作成できる。この関数の第一引数lpNameは、名前付きパイプ名へのポインタで、次の形式で指定する。

\\<サーバ>\Pipe\<パイプ名>
  • <サーバ>の部分には、名前付きパイプサーバを実行するコンピュータを指定する。Windows定義のエイリアス「\\.\」を指定した場合は、そのシステムのみで有効となる。 本記事では未検証だが、名前付きパイプはネットワークにまたがるプロセス間通信にも利用可能で、<サーバ>の部分にDNS名、IPアドレス、NetBIOS名なども指定できる。
  • <パイプ名>の部分には一意な名前を指定する。サブディレクトリを含むことができる。
例:\\.\Pipe\MyServerApp\ConnectionPipe

第二引数dwOpenModeでパイプの通信方向を指定できる。

説明
PIPE_ACCESS_DUPLEX 双方向。サーバとクライアントの両方が名前付きパイプの読み書きを行える。
PIPE_ACCESS_INBOUND 一方向。データの流れをクライアントからサーバへの方向に限定する。
PIPE_ACCESS_OUTBOUND 一方向。データの流れをサーバからクライアントへの方向に限定する。

パイプに関連する他の関数

関数名 説明
ConnectNamedPipe クライアントが名前付きパイプに接続してくるのを待機する。
CallNamedPipe 名前付きパイプがメッセージタイプの場合は当該関数で接続する。
PeekNamedPipe 名前付きパイプの状態を調べる。
WaitNamedPipe 接続可能になるかタイムアウト時間が経過まで待機する。

詳細は、下記Webページにまとめられている。
https://msdn.microsoft.com/ja-jp/library/cc429276.aspx

名前付きパイプのアクティビティの確認方法

Windows Sysinternals の PipeListツールを使う。
docs.microsoft.com

名前付きパイプの名前、それに対して作成されているインスタンス数、最大インスタンス数を表示できる。

C:\Users\Ayum>C:\Users\Ayum\Downloads\PipeList\pipelist.exe

PipeList v1.02 - Lists open named pipes
Copyright (C) 2005-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

Pipe Name                                    Instances       Max Instances
---------                                    ---------       -------------
InitShutdown                                      3               -1
lsass                                             4               -1
protected_storage                                 3               -1
ntsvcs                                            3               -1
scerpc                                            3               -1
plugplay                                          3               -1
Winsock2\CatalogChangeListener-484-0              1                1
epmapper                                          3               -1
(以下略)

プログラム

クライアント側で文字列を入力すると名前付きパイプを利用してプロセス間通信(一方向)を行い、サーバ側で文字列が表示されるというプログラム。実行する際は、名前付きパイプを作成してから読み込む必要があるのでサーバ側から先に実行する。終了する場合は、”EXIT”という文字列を入力する。

サーバ側のソースコード

サーバ側では、CreateNamedPipe関数を呼び出して名前付きパイプのインスタンスを作成し、ConnectNamedPipe関数でクライアントが接続するまで待機する。その後、名前付きパイプにクライアントからのメッセージがある場合は表示する。

#include <stdio.h>
#include <windows.h>

int main() {
    HANDLE hPipe = INVALID_HANDLE_VALUE;
    hPipe = CreateNamedPipe("\\\\.\\pipe\\mypipe", //lpName
                           PIPE_ACCESS_INBOUND,            // dwOpenMode
                           PIPE_TYPE_BYTE | PIPE_WAIT,     // dwPipeMode
                           1,                              // nMaxInstances
                           0,                              // nOutBufferSize
                           0,                              // nInBufferSize
                           100,                            // nDefaultTimeOut
                           NULL);                          // lpSecurityAttributes

    if (hPipe == INVALID_HANDLE_VALUE) {
        fprintf(stderr, "Couldn't create NamedPipe.");
        return 1;
    }
    if (!ConnectNamedPipe(hPipe, NULL)) {
        fprintf(stderr, "Couldn't connect to NamedPipe.");
        CloseHandle(hPipe);
        return 1;
    }

    while (1) {
        char szBuff[256];
        DWORD dwBytesRead;
        if (!ReadFile(hPipe, szBuff, sizeof(szBuff), &dwBytesRead, NULL)) {
                fprintf(stderr, "Couldn't read NamedPipe.");
                break;
        }
        if (!strncmp("EXIT", szBuff, 4)) { // Exit Check
                break;
        }
        szBuff[dwBytesRead] = '\0';
        printf("Server : %s", szBuff);
        Sleep(1);
    }
    FlushFileBuffers(hPipe);
    DisconnectNamedPipe(hPipe);
    CloseHandle(hPipe);
    return 0;
}

クライアント側のソースコード

クライアント側では、サーバ側で作成した名前付きパイプのハンドルを取得し、ユーザによって入力された文字列をWriteFile関数で書き込む。

#include <stdio.h>
#include <windows.h>

int main() {
	HANDLE hPipe = INVALID_HANDLE_VALUE;
	hPipe = CreateFile("\\\\.\\pipe\\mypipe",
                          GENERIC_WRITE,
                          0,
                          NULL,
                          OPEN_EXISTING, 
                          FILE_ATTRIBUTE_NORMAL,
                          NULL);
	
	if (hPipe == INVALID_HANDLE_VALUE) {
		fprintf(stderr, "Couldn't create NamedPipe.");
		return 1;
	}

	while (1) {
		char szBuff[255];
		DWORD dwBytesWritten;
		fgets(szBuff, sizeof(szBuff), stdin);
		if (!WriteFile(hPipe, szBuff, strlen(szBuff), &dwBytesWritten, NULL)) {
			fprintf(stderr, "Couldn't write NamedPipe.");
			break;
		}
		if (!strncmp("EXIT", szBuff, 4)) { // Exit Check
			break;
		}
	}
	CloseHandle(hPipe);
	return 0;
}

実行結果

クライアントに文字列を入力してEnterを押下すると、サーバにも同一の文字列が表示される。

f:id:aes256:20171204175406p:plain
図2.プログラムの実行イメージ
このときの名前付きパイプのアクティビティは次の通りとなった。「mypipe」が表示されていることがわかる。

C:\Users\Ayum>C:\Users\Ayum\Downloads\PipeList\pipelist.exe

PipeList v1.02 - Lists open named pipes
Copyright (C) 2005-2016 Mark Russinovich
Sysinternals - www.sysinternals.com

Pipe Name                                    Instances       Max Instances
---------                                    ---------       -------------
InitShutdown                                      3               -1
lsass                                             4               -1
protected_storage                                 3               -1
ntsvcs                                            3               -1
scerpc                                            3               -1
plugplay                                          3               -1
(中略)
mypipe                                            1                1

Process Hackerなどのツールを使ってクライアントのハンドル一覧を見れば、パイプのハンドルを確認できる。

f:id:aes256:20171205174120p:plain
図3.クライアントのハンドル一覧


上記コードを32bit/64bitプログラムにそれぞれコンパイルし、各プロセス間で名前付きパイプによる通信が可能か検証した。いずれも成功した。

サーバ    クライアント プロセス間通信
32bit 32bit 成功
32bit 64bit 成功
64bit 32bit 成功
64bit 64bit 成功