EternalWindows
名前付きパイプ / マルチクライアントへの対応

これまで作成してきたサーバーは、1つのクライアントしか同時に接続できませんでしたが、 今回は複数のクライアントからの接続に対応したいと思います。 また、クライアントはボタンを通じて、任意のタイミングでメッセージを送信できるようにします。 サーバーをどうように実装したらよいかを理解するために、 まずはクライアントの例を示します。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{	
	switch (uMsg) {

	case WM_CREATE:
		// パイプの取得とスレッドの作成
		hPipe = CreateFile(TEXT("\\\\.\\pipe\\SamplePipe"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, hPipe, 0, NULL);
		return 0;

	case WM_COMMAND:
		// ボタンが押されたらデータを書き込む
		WriteFile(hPipe, szData, nLen, &dwResult, NULL);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(LPVOID lpParamater)
{
	HANDLE hPipe = (HANDLE)lpParamater;
	TCHAR  szData[256];
	DWORD  dwResult;
	
	for (;;) {
		// サーバーからのデータを取得
		ReadFile(hPipe, szData, sizeof(szData), &dwResult, NULL);
		MessageBox(NULL, szData, TEXT("OK"), MB_OK);
	}

	return 0;
}

このコードの要点は、データの書き込みはメインスレッドで行っているけれども、 データの取得は別スレッドで行っているという点です。 クライアントがデータを書き込むとサーバーがそれを取得し、その後クライアントにデータを返すようになっていますが、 このデータの取得を別スレッドで行うのです。 理由は、ReadFileによってコードの進行がブロックされるため、メインスレッドで呼び出してはUIの操作ができなくなるからです。 ただし、上記のコードのままでは、ReadFileは期待した動作を行わないはずです。 なぜなら、メインスレッドのWriteFileのデータを、 別スレッドのReadFileで取得してしまうような事態に陥るからです。 つまり、使用しているパイプのハンドルが同じであるため、 自分で書き込んだデータを自分で取得するようなことが発生するのです。 これを防ぐために、サーバーはPIPE_ACCESS_DUPLEXのパイプを作成するべきではありません。 PIPE_ACCESS_INBOUNDを指定したデータの取得用(クライアントからすれば書き込み用)のパイプと、 PIPE_ACCESS_OUTBOUNDを指定したデータの書き込み用(クライアントからすれば取得用)のパイプの2つを作成し、 それを適切に使い分けるようにします。

サーバーを実装するうえで考慮すべきもう1つの点は、スレッドに関する問題です。 サーバーはConnectNamedPipeでクライアントが接続してくるまで待機し、その後はクライアントからのデータを取得するためにReadFileを呼び出しますが、 このReadFileでスレッドが待機してしまっては、2つ目のクライアントのためにConnectNamedPipeを呼び出せません。 これでは、2つ目のクライアントがパイプに接続できませんから、 接続用のスレッドと読み書き用のスレッドを作成するようにします。 接続用のスレッドはConnectNamedPipeが制御を返すと、読み書き用のスレッドを作成し、 自身は再びConnectNamedPipeで待機します。 実際にクライアントからのデータを取得するのは読み書き用のスレッドであり、 データを取得した後はクライアントにデータを送信し、再びデータが送られるまで待機します。 読み書き用のスレッドは、接続してくるクライアントの数だけ作成されます。

それでは、サーバーのコードを実際に見ていきます。

#include <windows.h>

struct DATA {
	HANDLE hPipeIn;
	HANDLE hPipeOut;
	int    nIndex;
};
typedef struct DATA DATA;
typedef struct DATA *LPDATA;

HWND g_hwndListBox = NULL;
HANDLE g_hEventExit = NULL;
HANDLE g_hEventDisconnect = NULL;

DWORD WINAPI ThreadProcConnect(LPVOID lpParameter);
DWORD WINAPI ThreadProcReadWrite(LPVOID lpParameter);
BOOL WaitEvent(HANDLE hEvent);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample-server");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HANDLE hThread = NULL;

	switch (uMsg) {

	case WM_CREATE:
		g_hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		g_hEventExit = CreateEvent(NULL, TRUE, FALSE, NULL);
		g_hEventDisconnect = CreateEvent(NULL, TRUE, FALSE, NULL);
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProcConnect, NULL, 0, NULL);
		return 0;
	
	case WM_SIZE:
		MoveWindow(g_hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (hThread != NULL) {
			SetEvent(g_hEventExit);
			WaitForSingleObject(hThread, 1000);
			CloseHandle(hThread);
			CloseHandle(g_hEventExit);
			CloseHandle(g_hEventDisconnect);
		}

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProcConnect(LPVOID lpParameter)
{
	HANDLE     hPipeIn, hPipeOut;
	HANDLE     hEvent, hThread;
	OVERLAPPED ov;
	TCHAR      szData[256], szBuf[256];
	LPDATA     lpData;
	DWORD      dwClientProcessId;
	int        i = 0;
	
	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

	for (;;) {
		hPipeIn = CreateNamedPipe(TEXT("\\\\.\\pipe\\SamplePipeIn"), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 2, sizeof(szData), sizeof(szData), 1000, NULL);
		hPipeOut = CreateNamedPipe(TEXT("\\\\.\\pipe\\SamplePipeOut"), PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE, 2, sizeof(szData), sizeof(szData), 1000, NULL);
		if (hPipeIn == INVALID_HANDLE_VALUE || hPipeOut == INVALID_HANDLE_VALUE) {
			if (GetLastError() == ERROR_PIPE_BUSY) {
				SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("最大同時接続 発生"));
				if (!WaitEvent(g_hEventDisconnect))
					break;
				ResetEvent(g_hEventDisconnect);
				continue;
			}
			else {
				SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("名前付きパイプの作成に失敗"));
				break;
			}
		}

		ZeroMemory(&ov, sizeof(OVERLAPPED));
		ov.hEvent = hEvent;
		ConnectNamedPipe(hPipeIn, &ov);
		if (!WaitEvent(hEvent))
			break;
		
		ConnectNamedPipe(hPipeOut, NULL);
		
		GetNamedPipeClientProcessId(hPipeIn, &dwClientProcessId);
		wsprintf(szBuf, TEXT("No%d(PID %x) 接続"), i + 1, dwClientProcessId);
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
		
		lpData = (LPDATA)HeapAlloc(GetProcessHeap(), 0, sizeof(DATA));
		lpData->hPipeIn = hPipeIn;
		lpData->hPipeOut = hPipeOut;
		lpData->nIndex = ++i;

		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProcReadWrite, lpData, 0, NULL);
	}

	return 0;
}

DWORD WINAPI ThreadProcReadWrite(LPVOID lpParameter)
{
	LPDATA     lpData = (LPDATA)lpParameter;
	HANDLE     hEvent;
	OVERLAPPED ov;
	TCHAR      szData[256], szBuf[512];
	DWORD      dwResult, dwTransferred;

	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

	for (;;) {
		ZeroMemory(&ov, sizeof(OVERLAPPED));
		ov.hEvent = hEvent;
		ReadFile(lpData->hPipeIn, szData, sizeof(szData), &dwResult, &ov);
		if (!WaitEvent(hEvent))
			break;

		GetOverlappedResult(lpData->hPipeIn, &ov, &dwTransferred, TRUE);
		if (dwTransferred == 0) {
			wsprintf(szBuf, TEXT("No%d 切断"), lpData->nIndex);
			SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			SetEvent(g_hEventDisconnect);
			break;
		}

		ReadFile(lpData->hPipeIn, szData, sizeof(szData), &dwResult, &ov);
		wsprintf(szBuf, TEXT("No%d %s"), lpData->nIndex, szData);
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

		WriteFile(lpData->hPipeOut, szData, (lstrlen(szData) + 1) * sizeof(TCHAR), &dwResult, NULL);
	}
	
	DisconnectNamedPipe(lpData->hPipeIn);
	DisconnectNamedPipe(lpData->hPipeOut);
	CloseHandle(lpData->hPipeIn);
	CloseHandle(lpData->hPipeOut);
	CloseHandle(hEvent);
	HeapFree(GetProcessHeap(), 0, lpData);

	return 0;
}

BOOL WaitEvent(HANDLE hEvent)
{
	HANDLE hEventArray[2];
	DWORD  dwEventNo;

	hEventArray[0] = g_hEventExit;
	hEventArray[1] = hEvent;
	dwEventNo = WaitForMultipleObjects(2, hEventArray, FALSE, INFINITE) - WAIT_OBJECT_0;
	if (dwEventNo == 0)
		return FALSE;
	else if (dwEventNo == 1)
		return TRUE;
	else
		return FALSE;
}

WM_CREATEでリストボックスを作成しているのは、クライアントが接続してきたことや、送信されたデータを表示するためです。 WM_CREATEではスレッドも作成していますが、このスレッドが接続用のスレッドになります。 実行される関数はThreadProcConnectであり、まず次の部分に注目します。

hPipeIn = CreateNamedPipe(TEXT("\\\\.\\pipe\\SamplePipeIn"), PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE, 2, sizeof(szData), sizeof(szData), 1000, NULL);
hPipeOut = CreateNamedPipe(TEXT("\\\\.\\pipe\\SamplePipeOut"), PIPE_ACCESS_OUTBOUND, PIPE_TYPE_BYTE, 2, sizeof(szData), sizeof(szData), 1000, NULL);
if (hPipeIn == INVALID_HANDLE_VALUE || hPipeOut == INVALID_HANDLE_VALUE) {
	if (GetLastError() == ERROR_PIPE_BUSY) {
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("最大同時接続 発生"));
		if (!WaitEvent(g_hEventDisconnect))
			break;
		ResetEvent(g_hEventDisconnect);
		continue;
	}
	else {
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("名前付きパイプの作成に失敗"));
		break;
	}
}

既に述べたようにサーバーはパイプが2つ必要になるため、CreateNamedPipeを2回呼び出しています。 このうち、PIPE_ACCESS_INBOUNDを指定した作成したパイプはReadFileに指定することになるため、 ブロッキングが発生しないようにFILE_FLAG_OVERLAPPEDを指定します。 パイプの作成に失敗してGetLastErrorがERROR_PIPE_BUSYを返した場合は、 パイプに接続できる最大数に到達していることを意味します。 今回の場合、同時に接続できるクライアントの数は2であるため、現在2つのクライアントが接続しているということになり、 これ以上パイプを作成するわけにはいきません。 このようなときは、クライアントが接続を切断するまで待機し、それを検出した後にCreateNamedPipeを呼び出せばよいでしょう。 読み書き用のスレッドは、クライアントの切断を検出した場合にg_hEventDisconnectをシグナル状態にするため、 これをWaitEventに指定すれば、関数の成功時には接続に空きが生じしていると判断できます。 g_hEventDisconnectを再び使用する可能性があるので、ResetEventで非シグナル状態にしておきます。

ZeroMemory(&ov, sizeof(OVERLAPPED));
ov.hEvent = hEvent;
ConnectNamedPipe(hPipeIn, &ov);
if (!WaitEvent(hEvent))
	break;

ConnectNamedPipe(hPipeOut, NULL);

GetNamedPipeClientProcessId(hPipeIn, &dwClientProcessId);
wsprintf(szBuf, TEXT("No%d(PID %x) 接続"), i + 1, dwClientProcessId);
SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

lpData = (LPDATA)HeapAlloc(GetProcessHeap(), 0, sizeof(DATA));
lpData->hPipeIn = hPipeIn;
lpData->hPipeOut = hPipeOut;
lpData->nIndex = ++i;

hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProcReadWrite, lpData, 0, NULL);

パイプを作成したら、ConnectNamedPipeでクライアントが接続してくるまで待機します。 しかし、今回も前節と同様にOVERLAPPED構造体を指定しているため、実際にはConnectNamedPipeは直ちに制御を返し、 WaitEventで待機することになります。 クライアントはCreateFileを連続して呼び出す設計になっているため、WaitEventから制御が返った際には、 2つ目のConnectNamedPipeも成功して、直ちに制御を返すことになるでしょう。 クライアントが接続してきたことをリストボックスに表示するためにSendMessageを呼び出していますが、 この際にはクライアントのプロセスIDも表示したら便利であるため、GetNamedPipeClientProcessIdを呼び出しています。 この関数は、Windows Vistaから使用可能であるため注意してください。 CreateThreadに指定しているThreadProcReadWriteが、読み書き用のスレッドの開始アドレスです。 このスレッドにはパイプなどを渡したいため、DATA構造体のメモリを確保し、それをCreateThreadの第4引数に指定しています。

DWORD WINAPI ThreadProcReadWrite(LPVOID lpParameter)
{
	LPDATA     lpData = (LPDATA)lpParameter;
	HANDLE     hEvent;
	OVERLAPPED ov;
	TCHAR      szData[256], szBuf[512];
	DWORD      dwResult, dwTransferred;

	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

	for (;;) {
		ZeroMemory(&ov, sizeof(OVERLAPPED));
		ov.hEvent = hEvent;
		ReadFile(lpData->hPipeIn, szData, sizeof(szData), &dwResult, &ov);
		if (!WaitEvent(hEvent))
			break;

		GetOverlappedResult(lpData->hPipeIn, &ov, &dwTransferred, TRUE);
		if (dwTransferred == 0) {
			wsprintf(szBuf, TEXT("No%d 切断"), lpData->nIndex);
			SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			SetEvent(g_hEventDisconnect);
			break;
		}

		ReadFile(lpData->hPipeIn, szData, sizeof(szData), &dwResult, &ov);
		wsprintf(szBuf, TEXT("No%d %s"), lpData->nIndex, szData);
		SendMessage(g_hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);

		WriteFile(lpData->hPipeOut, szData, (lstrlen(szData) + 1) * sizeof(TCHAR), &dwResult, NULL);
	}
	
	DisconnectNamedPipe(lpData->hPipeIn);
	DisconnectNamedPipe(lpData->hPipeOut);
	CloseHandle(lpData->hPipeIn);
	CloseHandle(lpData->hPipeOut);
	CloseHandle(hEvent);
	HeapFree(GetProcessHeap(), 0, lpData);

	return 0;
}


データを最初に送信するのはクライアントであるため、サーバーはReadFileから呼び出します。 このReadFileでブロッキングしてしまっては、サーバーのウインドウが閉じられたタイミングを検出できないため、 第5引数にOVERLAPPED構造体を指定して、WaitEventで待機するようにします。 WaitEventから制御が返ると、オーバーラップ(非同期)操作が成功したかどうかを確認するために、GetOverlappedResultを呼び出します。 第3引数には読み取ったバイト数が返されますが、これが0であるということはクライアントがWriteFileを呼び出さず、 パイプを閉じたことによって操作が完了したと判断できます。 よって、このときはクライアントが切断したことをリストボックスに表示し、 接続に空きができたことをSetEventで通知します。 dwTransferredが0でない場合はReadFileを呼び出してデータを取得し、それをリストボックスに表示します。 その後、WriteFileでデータを返信し、再びデータが送られるのをWaitEventで待機します。

続いて、クライアントのコードを示します。 実行時には、事前にサーバーを起動しておいてください。

#include <windows.h>

#define ID_SEND 100
#define ID_EDIT 200

HANDLE g_hEventExit = NULL;

BOOL WaitEvent(HANDLE hEvent);
DWORD WINAPI ThreadProc(LPVOID lpParamater);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample-client");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND   hwndButton = NULL;
	static HWND   hwndEdit = NULL;
	static HANDLE hThread = NULL;
	static HANDLE hPipeRead = NULL;
	static HANDLE hPipeWrite = NULL;
	
	switch (uMsg) {

	case WM_CREATE:
		hwndButton = CreateWindowEx(0, TEXT("BUTTON"), TEXT("送信"), WS_CHILD | WS_VISIBLE, 10, 10, 60, 30, hwnd, (HMENU)ID_SEND, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndEdit = CreateWindowEx(0, TEXT("EDIT"), TEXT("メッセージを入力してください。"), WS_CHILD | WS_VISIBLE | WS_BORDER, 90, 10, 300, 35, hwnd, (HMENU)ID_EDIT, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		hPipeWrite = CreateFile(TEXT("\\\\.\\pipe\\SamplePipeIn"), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
		hPipeRead = CreateFile(TEXT("\\\\.\\pipe\\SamplePipeOut"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
		if (hPipeWrite == INVALID_HANDLE_VALUE || hPipeRead == INVALID_HANDLE_VALUE) {
			if (GetLastError() == ERROR_PIPE_BUSY)
				MessageBox(NULL, TEXT("パイプへこれ以上接続できません。"), NULL, MB_ICONWARNING);
			else
				MessageBox(NULL, TEXT("パイプへの接続に失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}

		g_hEventExit = CreateEvent(NULL, FALSE, FALSE, NULL);

		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, hPipeRead, 0, NULL);

		return 0;

	case WM_COMMAND: {
		int   nLen;
		TCHAR szData[256];
		DWORD dwResult;

		if (LOWORD(wParam) != ID_SEND)
			return 0;

		nLen = GetWindowText(hwndEdit, szData, sizeof(szData));
		nLen = (nLen + 1) * sizeof(TCHAR);

		if (!WriteFile(hPipeWrite, szData, nLen, &dwResult, NULL))
			MessageBox(NULL, TEXT("データの送信に失敗しました。"), NULL, MB_ICONWARNING);

		return 0;
	}

	case WM_DESTROY:
		if (hThread != NULL) {
			SetEvent(g_hEventExit);
			WaitForSingleObject(hThread, 1000);
			CloseHandle(hThread);
			CloseHandle(g_hEventExit);
		}

		if (hPipeRead != NULL)
			CloseHandle(hPipeRead);
		
		if (hPipeWrite != NULL)
			CloseHandle(hPipeWrite);

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProc(LPVOID lpParamater)
{
	HANDLE     hPipe = (HANDLE)lpParamater;
	HANDLE     hEvent;
	OVERLAPPED ov;
	TCHAR      szData[256], szBuf[512];
	DWORD      dwResult, dwTransferred;

	hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	
	for (;;) {
		ZeroMemory(&ov, sizeof(OVERLAPPED));
		ov.hEvent = hEvent;
		ReadFile(hPipe, szData, sizeof(szData), &dwResult, &ov);
		if (!WaitEvent(ov.hEvent))
			break;
		
		GetOverlappedResult(hPipe, &ov, &dwTransferred, TRUE);
		if (dwTransferred == 0) {
			MessageBox(NULL, TEXT("サーバーとの接続が切断されました。"), TEXT("クライアント"), MB_OK);
			break;
		}

		ReadFile(hPipe, szData, sizeof(szData), &dwResult, &ov);
		wsprintf(szBuf, TEXT("%dバイト受信しました。\n%s"), dwTransferred, szData);
		MessageBox(NULL, szBuf, TEXT("クライアント"), MB_OK);	
	}
	
	CloseHandle(hEvent);

	return 0;
}

BOOL WaitEvent(HANDLE hEvent)
{
	HANDLE hEventArray[2];
	DWORD  dwEventNo;

	hEventArray[0] = g_hEventExit;
	hEventArray[1] = hEvent;
	dwEventNo = WaitForMultipleObjects(2, hEventArray, FALSE, INFINITE) - WAIT_OBJECT_0;
	if (dwEventNo == 0)
		return FALSE;
	else if (dwEventNo == 1)
		return TRUE;
	else
		return FALSE;
}

サーバーはパイプを2つ作成していたため、クライアントはCreateFileを2回呼び出してそれぞれのパイプに接続します。 hPipeReadはReadFileに指定することになるため、その際にオーバーラップが可能になるようにFILE_FLAG_OVERLAPPEDを指定します。 GetLastErrorがERROR_PIPE_BUSYを返した場合は、CreateNamedPipeの第4引数に指定した数だけのクライアントが既に接続していることを意味します。 この場合は、別のクライアントを終了させてからもう一度起動することになるでしょう。 WM_COMMANDでは、エディットコントロールに入力された文字列をWriteFileで書き込みます。 このときに使用するパイプは、書き込み用のhPipeWriteでなければなりません。 ThreadProcでは、サーバーからのデータを取得するためにReadFileを呼び出します。 WaitEventやGetOverlappedResultを呼び出すのは、サーバーと同じ要領です。

WaitNamedPipeについて

今回のクライアントでは、パイプへ接続できなかった場合はプロセスを終了するようにしていますが、 接続の空きがないことが原因で失敗した場合は、接続に空きができるまで待つようにしてもよいでしょう。 WaitNamedPipeを呼び出すようにすればこのような事は可能です。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND   hwndButton = NULL;
	static HWND   hwndEdit = NULL;
	static HANDLE hThread = NULL;
	static HANDLE hPipeRead = NULL;
	static HANDLE hPipeWrite = NULL;
	
	switch (uMsg) {

	case WM_CREATE:
		hwndButton = CreateWindowEx(0, TEXT("BUTTON"), TEXT("送信"), WS_CHILD | WS_VISIBLE, 10, 10, 60, 30, hwnd, (HMENU)ID_SEND, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndEdit = CreateWindowEx(0, TEXT("EDIT"), TEXT("メッセージを入力してください。"), WS_CHILD | WS_VISIBLE | WS_BORDER, 90, 10, 300, 35, hwnd, (HMENU)ID_EDIT, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		EnableWindow(hwndButton, FALSE);

		g_hEventExit = CreateEvent(NULL, FALSE, FALSE, NULL);

		SendMessage(hwnd, WM_CONNECT, 0, 0);

		return 0;

	case WM_CONNECT:
		hPipeWrite = CreateFile(TEXT("\\\\.\\pipe\\SamplePipeIn"), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
		hPipeRead = CreateFile(TEXT("\\\\.\\pipe\\SamplePipeOut"), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
		if (hPipeWrite == INVALID_HANDLE_VALUE || hPipeRead == INVALID_HANDLE_VALUE) {
			if (GetLastError() == ERROR_PIPE_BUSY) {
				CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProcWait, hwnd, 0, NULL);
				return 0;
			}
			else {
				MessageBox(NULL, TEXT("パイプへの接続に失敗しました。"), NULL, MB_ICONWARNING);
				DestroyWindow(hwnd);
			}
		}
		EnableWindow(hwndButton, TRUE);
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, hPipeRead, 0, NULL);
		return 0;

	case WM_COMMAND: {
		int   nLen;
		TCHAR szData[256];
		DWORD dwResult;

		if (LOWORD(wParam) != ID_SEND)
			return 0;

		nLen = GetWindowText(hwndEdit, szData, sizeof(szData));
		nLen = (nLen + 1) * sizeof(TCHAR);

		if (!WriteFile(hPipeWrite, szData, nLen, &dwResult, NULL))
			MessageBox(NULL, TEXT("データの送信に失敗しました。"), NULL, MB_ICONWARNING);

		return 0;
	}

	case WM_DESTROY:
		if (hThread != NULL) {
			SetEvent(g_hEventExit);
			WaitForSingleObject(hThread, 1000);
			CloseHandle(hThread);
			CloseHandle(g_hEventExit);
		}

		if (hPipeRead != NULL)
			CloseHandle(hPipeRead);
		
		if (hPipeWrite != NULL)
			CloseHandle(hPipeWrite);

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

DWORD WINAPI ThreadProcWait(LPVOID lpParamater)
{
	HWND hwnd = (HWND)lpParamater;

	if (WaitNamedPipe(TEXT("\\\\.\\pipe\\SamplePipeIn"), NMPWAIT_WAIT_FOREVER))
		PostMessage(hwnd, WM_CONNECT, 0, 0);
	
	return 0;
}

CreateFileでERROR_PIPE_BUSYを返った場合に、接続の空きを確認するためのスレッドを作成します。 このスレッドはWaitNamedPipeを呼び出しており、第2引数がNMPWAIT_WAIT_FOREVERである場合は、 第1引数の名前のパイプに空きができるまで待機します。 関数が成功した場合はパイプに接続できるようになったということで、 WM_CONNECTという独自のメッセージを送ります。 そして、WM_CONNECTではCreateFileを呼び出してパイプに接続します。



戻る