EternalWindows
Winsock / データの送受信(VISTA編)
対応するサーバーはこちら

クライアントにとって、サーバーへの接続に時間が掛かることは何とか避けたい問題です。 サーバー情報として入力された文字列がIPアドレスではなくホスト名である場合、 getaddrinfoの呼び出し時にDNSを通じたIPアドレスへの変換処理が生じてしまい、 この次に呼び出すconnectを合わせて、2回のネットワークアクセスが生じることになります。 しかし、Windows Vistaから登場したWSAConnectByNameは、 IPアドレスへの変換を行うと同時にそのアドレスへ接続するため、 気持ちとしては1回のネットワークアクセスでサーバーへ接続することができます。 よって、getaddrinfoとconnectの組み合わせと比べて接続速度の向上が期待できます。

BOOL PASCAL WSAConnectByName(
  SOCKET s,
  LPSTR nodename,
  LPSTR servicename,
  LPDWORD LocalAddressLength,
  LPSOCKADDR LocalAddress,
  LPDWORD RemoteAddressLength,
  LPSOCKADDR RemoteAddress,
  const struct timeval *timeout,
  LPWSAOVERLAPPED Reserved
);

sは、ソケットの記述子を指定します。 nodenameは、接続するサーバーのIPアドレス、またはホスト名を指定します。 servicenameは、接続するサーバーのポート番号を指定します。 LocalAddressLengthは、LocalAddressのサイズを格納した変数のアドレスを指定します。 LocalAddressは、クライアントのアドレスを受け取るSOCKADDR構造体のアドレスを指定します。 LocalAddressにNULLを指定した場合は、LocalAddressLengthにNULLを指定することができます。 RemoteAddressLengthは、RemoteAddressのサイズを格納した変数のアドレスを指定します。 RemoteAddressは、サーバーのアドレスを受け取るSOCKADDR構造体のアドレスを指定します。 RemoteAddressにNULLを指定した場合は、RemoteAddressLengthにNULLを指定することができます。 timeoutは、接続を中止する時間を格納したtimeval構造体のアドレスを指定します。 0を指定した場合は、デフォルトのタイムアウト値が適応されます。 Reservedは、予約されているためNULLを指定します。 なお、WSAConnectByNameは、非ブロッキングに対応していません。

今回のクライアントプログラムは、対応するサーバープログラムにデータを送信します。 これを取得したサーバーはクライアントに応答データを送信し、 クライアントはこれを別スレッドで受信します。 事前に、対応するサーバープログラムを起動しておいてください。

#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

#define ID_SEND 100
#define ID_EDIT 200
#define WM_SOCKET WM_APP

SOCKET InitializeWinsock(LPTSTR lpszServerName, LPTSTR lpszPort);
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 SOCKET soc = INVALID_SOCKET;

	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);
		
		soc = InitializeWinsock(TEXT("localhost"), TEXT("7000"));
		if (soc == INVALID_SOCKET)
			return -1;
		
		WSAAsyncSelect(soc, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);

		return 0;

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

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

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

		if (send(soc, (char *)szData, nLen, 0) == SOCKET_ERROR)
			MessageBox(NULL, TEXT("データの送信に失敗しました。"), NULL, MB_ICONWARNING);

		return 0;
	}

	case WM_SOCKET:
		switch (WSAGETSELECTEVENT(lParam)) {

		case FD_READ: {
			int   nLen;
			int   nResult;
			TCHAR szBuf[256];
			TCHAR szData[256];
			
			nLen = sizeof(szData);
			nResult = recv(soc, (char *)szData, nLen, 0);
			
			wsprintf(szBuf, TEXT("%dバイト受信しました。\n%s"), nResult, szData);
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			break;
		}

		case FD_CLOSE:
			MessageBox(NULL, TEXT("サーバーとの接続が切断されました。"), TEXT("OK"), MB_OK);
			break;

		}
		return 0;


	case WM_DESTROY:
		if (soc != INVALID_SOCKET) {
			shutdown(soc, SD_BOTH);
			closesocket(soc);
			WSACleanup();
		}

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

SOCKET InitializeWinsock(LPTSTR lpszServerName, LPTSTR lpszPort)
{
	WSADATA wsaData;
	SOCKET  soc;
	BOOL    bResult;

	WSAStartup(MAKEWORD(2, 2), &wsaData);

	soc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	bResult = WSAConnectByName(soc, lpszServerName, lpszPort, NULL, NULL, NULL, NULL, NULL, NULL);
	if (!bResult) {
		int nError = WSAGetLastError();
		if (nError == WSAECONNREFUSED)
			MessageBox(NULL, TEXT("サーバーがリッスンしていません。"), NULL, MB_ICONWARNING);
		else if (nError == WSAHOST_NOT_FOUND)
			MessageBox(NULL, TEXT("サーバーが存在しません。"), NULL, MB_ICONWARNING);
		else
			MessageBox(NULL, TEXT("サーバーへの接続が失敗しました。"), NULL, MB_ICONWARNING);
		closesocket(soc);
		WSACleanup();
		return INVALID_SOCKET;
	}

	return soc;
}

今回のInitializeWinsockでは、getaddrinfoとconnectの呼び出しの代わりに、 WSAConnectByNameを呼び出しています。 クライアントのアドレスを取得するつもりがないため、第4引数と第5引数にNULLを指定し、 サーバーのアドレスも取得するつもりがないため、第6引数と第7引数にNULLを指定しています。

WSAConnectByListについて

Windows Vistaから追加されたWSAConnectByListという関数は、 複数個指定されたIPアドレスの中から、いずれかのサーバーに接続する機能を提供します。 次に、コード例を示します。

#include <winsock2.h>
#include <ws2tcpip.h>

#pragma comment(lib, "ws2_32.lib")

#define ID_SEND 100
#define ID_EDIT 200
#define WM_SOCKET WM_APP

SOCKET InitializeWinsock(LPSTR *lpszIpAddress, int nAddressCount, int nPort);
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 SOCKET soc = INVALID_SOCKET;

	switch (uMsg) {

	case WM_CREATE: {
		LPSTR lpszIpAddress[] = {"127.0.0.1", "169.254.86.243"};

		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);
		
		soc = InitializeWinsock(lpszIpAddress, 2, 7000);
		if (soc == INVALID_SOCKET)
			return -1;
		
		WSAAsyncSelect(soc, hwnd, WM_SOCKET, FD_READ | FD_CLOSE);

		return 0;
	}

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

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

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

		if (send(soc, (char *)szData, nLen, 0) == SOCKET_ERROR)
			MessageBox(NULL, TEXT("データの送信に失敗しました。"), NULL, MB_ICONWARNING);

		return 0;
	}

	case WM_SOCKET:
		switch (WSAGETSELECTEVENT(lParam)) {

		case FD_READ: {
			int   nLen;
			int   nResult;
			TCHAR szBuf[256];
			TCHAR szData[256];
			
			nLen = sizeof(szData);
			nResult = recv(soc, (char *)szData, nLen, 0);
			
			wsprintf(szBuf, TEXT("%dバイト受信しました。\n%s"), nResult, szData);
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			break;
		}

		case FD_CLOSE:
			MessageBox(NULL, TEXT("サーバーとの接続が切断されました。"), TEXT("OK"), MB_OK);
			break;

		}
		return 0;


	case WM_DESTROY:
		if (soc != INVALID_SOCKET) {
			shutdown(soc, SD_BOTH);
			closesocket(soc);
			WSACleanup();
		}

		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

SOCKET InitializeWinsock(LPSTR *lplpszIpAddress, int nAddressCount, int nPort)
{
	int                   i;
	WSADATA               wsaData;
	SOCKET                soc;
	BOOL                  bResult;
	DWORD                 dwAddressListSize;
	DWORD                 dwSockAddrSize;
	LPSOCKET_ADDRESS_LIST lpAddressList;
	LPSOCKADDR_IN         lpSockAddrIn;
	
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	soc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	
	dwSockAddrSize = sizeof(SOCKADDR) * nAddressCount;
	dwAddressListSize = sizeof(int) + sizeof(SOCKET_ADDRESS) * nAddressCount + dwSockAddrSize;
	lpAddressList = (LPSOCKET_ADDRESS_LIST)HeapAlloc(GetProcessHeap(), 0, dwAddressListSize);
	lpSockAddrIn = (LPSOCKADDR_IN)((LPBYTE)lpAddressList + dwSockAddrSize);

	lpAddressList->iAddressCount = nAddressCount; 

	for (i = 0; i < nAddressCount; i++) {
		lpSockAddrIn[i].sin_family      = AF_INET;
		lpSockAddrIn[i].sin_port        = htons((USHORT)nPort);
		lpSockAddrIn[i].sin_addr.s_addr = inet_addr(lplpszIpAddress[i]);

		lpAddressList->Address[i].lpSockaddr      = (LPSOCKADDR)&lpSockAddrIn[i];
		lpAddressList->Address[i].iSockaddrLength = sizeof(SOCKADDR);
	}
	
	bResult = WSAConnectByList(soc, lpAddressList, NULL, NULL, NULL, NULL, NULL, NULL);
	if (!bResult) {
		int nError = WSAGetLastError();
		if (nError == WSAECONNREFUSED)
			MessageBox(NULL, TEXT("サーバーがリッスンしていません。"), NULL, MB_ICONWARNING);
		else if (nError == WSAHOST_NOT_FOUND)
			MessageBox(NULL, TEXT("サーバーが存在しません。"), NULL, MB_ICONWARNING);
		else
			MessageBox(NULL, TEXT("サーバーへの接続が失敗しました。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpAddressList);
		closesocket(soc);
		WSACleanup();
		return INVALID_SOCKET;
	}

	HeapFree(GetProcessHeap(), 0, lpAddressList);

	return soc;
}

WSAConnectByListを呼び出すには、SOCKET_ADDRESS_LIST構造体で表現できるバッファを第2引数に指定する必要があります。 バッファのフォーマットは、先頭がアドレス数を格納するメンバとなり、 その次が1つ以上のSOCKET_ADDRESS構造体、そしてこれ以降がアドレス情報を格納する1つ以上のSOCKADDR構造体になります。 バッファの先頭を指すlpAddressListは、次のように初期化されています。

dwSockAddrSize = sizeof(SOCKADDR) * nAddressCount;
dwAddressListSize = sizeof(int) + sizeof(SOCKET_ADDRESS) * nAddressCount + dwSockAddrSize;
lpAddressList = (LPSOCKET_ADDRESS_LIST)HeapAlloc(GetProcessHeap(), 0, dwAddressListSize);
lpSockAddrIn = (LPSOCKADDR_IN)((LPBYTE)lpAddressList + dwSockAddrSize);

まず、一連のSOCKADDR構造体のサイズを計算し、その後にバッファのサイズを計算します。 このバッファのサイズは、アドレス数を格納するメンバのサイズと、 一連のSOCKET_ADDRESS構造体のサイズ、 そして先に求めた一連のSOCKADDR構造体のサイズの合計になります。 バッファの数だけメモリを確保したら、 一連のSOCKADDR構造体の先頭アドレスをlpSockAddrに保存しておきます。 これによって、初期化が容易になります。

for (i = 0; i < nAddressCount; i++) {
	lpSockAddrIn[i].sin_family      = AF_INET;
	lpSockAddrIn[i].sin_port        = htons((USHORT)nPort);
	lpSockAddrIn[i].sin_addr.s_addr = inet_addr(lplpszIpAddress[i]);

	lpAddressList->Address[i].lpSockaddr      = (LPSOCKADDR)&lpSockAddrIn[i];
	lpAddressList->Address[i].iSockaddrLength = sizeof(SOCKADDR);
}

SOCKADDR構造体にアドレスファミリとポート番号、そして数値化したIPアドレスを指定します。 そして、このアドレスをSOCKET_ADDRESS構造体のlpSockaddrに指定します。 重要なのは、このlpSockaddrが指すアドレスが、 lpAddressListが指しているバッファの中に存在するという点です。



戻る