WinSock

■ソケット通信

ソケットとはTCP/IPの通信プロトコルにおける出入り口の役割を担っているAPIのことです。
ソケットを使用することでプログラマはTCPやUDPなどの通信手段や手順を知らなくてもよく、
通信相手に合わせてソース内容を変更する必要もありません。
このソケットを使用した通信をソケット通信と呼びます。

●ソケット間通信で必要な情報
	ソケット間通信で必要な情報は主にIPアドレスポート番号の二つです。
	この二つの情報を元にしてネットワーク上で通信相手の特定を行い、
	PC間の通信を可能にしています。

	network_0061

	・IPアドレス
		IPアドレスはネットワーク上でのコンピュータを識別するための識別子です。
		IPアドレスにはLANのようなローカル環境で使用されるプライベートIPアドレスと
		インターネットで使用するグローバルIPアドレスがあります。

	・ポート番号
		ポート番号はIPアドレスで特定した通信データをどのアプリケーションで
		対処するのかを判断するために使用される識別子です。
		HTTP(WEB閲覧)やPOP3(メール受信)、IMAP(メール送信)などの
		頻繁に使われるアプリケーションには専用の番号が決めれています。
		このサービス側で決められている専用のポート番号をウェルノウンポートと呼びます。

■サーバーソケット、クライアントソケット

ソケットにはデータを受信側と送信側の二つのソケットが必要です。
このソケットの受信側をサーバーソケット、送信側をクライアントソケットと呼んでいます。

	network_0060
	
●サーバーソケット
	サーバーソケットはクライアントがいつ通信を行うか分からないので
	常に起動してクライアントから通信要求が来るのを待っています。
	この待機状態のことをリスン(listen)状態と呼びます。

●クライアントソケット
	クライアントソケットはサーバーソケットに要求を出すソケットで、
	サーバーソケットとは違い、サーバーに対して用がある時だけ使用します。
	クライアントソケットはサーバーソケットへの通信方法として
	ストリームソケットとデータグラムソケットの2種類があります。

	・ストリームソケット
		ストリームソケットはTCPプロトコルを使用して通信を行うソケットです。
		メリット:
			送受信データの信頼度が高い
		デメリット:
			データの信頼性を保つための手順が多いので時間がかかる

	・データグラムソケット
		データグラムソケットはUDPプロトコルを使用して通信を行うソケットです。
		メリット:
			データを高速に送受信する
		デメリット:
			速度を優先しているので、データの信頼性が低い

■WinSock

WinSock(Windows Sockets)は本来UNIXで使用されていたソケット通信を
Windows版に改良したAPIのことです。
「■ソケットとは」でも記述していますがプログラマはWinSockを使用することで
ネットワークの詳細を知らなくても通信データの送受信が可能となります。

●バージョン情報
	WinSockのバージョンは最新が2.2となります。
	バージョン1ではストリームソケットのみでしたが、
	バージョン2からデータグラムソケットが使用可能となりました。

●WinSockのプログラミングに必要なファイル
	・libファイル
		#pragma comment (lib, "Ws2_32.lib")

	・インクルードファイル
		#include <WinSock2.h>

		※#include <WinSock2.h>と#include <windows.h>の両方を
		 インクルードする場合は多重定義によるエラーが発生するので
		 インクルード前に「#define WIN32_LEAN_AND_MEAN」を宣言してください。

●手順
	①.バージョンの作成
		MAKEWORDマクロを使用し、WinSockをどのバージョンで作成するかを決めます。

	②.WSADATAの宣言
		③のWSAStartupの情報を格納するためにWSADATA構造体を宣言します。

	③.WinSockの初期化
		WSAStartup関数を使用し、WinSockを初期化します。

	④.WinSockの終了
		WSACleanup関数を使用し、WinSockを終了します。

	⑤.WinSockのエラー番号取得
		SOCKET_ERRORが返されるWinSock関数のエラーに対して
		WSAGetLastError関数を使用してエラー番号を取得します。

●WinSock基本プログラム
	#define WIN32_LEAN_AND_MEAN
	#include <stdio.h>
	#include <Windows.h>
	#include <WinSock2.h>

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

	void main(void)
	{
		// ①WinSockのバージョン作成
		WORD version_request = MAKEWORD(1, 1);

		// ②WSADATA:Windows Sockets情報を格納する構造体
		WSADATA sadata;

		// ③WinSockを初期化する
		int error = WSAStartup(version_request, &sadata);
		if (error != 0)
		{
			printf("WinSock初期化失敗\n");
			return;
		}

		printf("使用バージョン = %d.%d\n", 
				LOBYTE(sadata.wVersion), HIBYTE(sadata.wVersion));
		printf("最新バージョン = %d.%d\n", 
				LOBYTE(sadata.wHighVersion), HIBYTE(sadata.wHighVersion));
		printf("%s\n", sadata.szDescription);
		printf("状態 = %s\n", sadata.szSystemStatus);
		printf("ソケット最大数 = %d\n", sadata.iMaxSockets);
		printf("UDP送受信最大バイト数 = %d\n", sadata.iMaxUdpDg);

		// ④WinSockのリソースを解放する
		error = WSACleanup();
		if (error == SOCKET_ERROR)
		{
			// ⑤エラー番号を取得する
			error = WSAGetLastError();
			printf ("WinSockエラー番号 = %d\n", error);
		}

		while(true);
	}

■WinSockによるHTTP通信

上のソースはWinSockを初期化してすぐに解放するだけの処理でしたので、
次はWinSockを使用したHTTP通信を行いたいと思います。
	
●HTTP通信の手順
	①.WinSockを初期化する
		WSAStartup関数を使用してWinSockを初期化します。

	②.ポート番号とホスト名の入力
		ポート番号とホスト名を入力します。

		ホスト名:
			WinSockが指すホスト名はFQDN(完全修飾ドメイン名)、ドメイン名、
			IPアドレス、コンピュータ名のいずれか

			ホスト(ホストコンピュータ):
				ホストはネットワークを通じて様々なサービスを
				提供するコンピュータのことです。
				サーバーはホストのシステムを指しており
				WinSockではIPアドレス等を使用して
				ネットワーク上からホストコンピュータを探し出して接続します。
		ポート番号:
			HTTP通信なので80で固定
			※ゲーム等ではエフェメラルポートのいずれかを使用する

	③.ファイル階層の設定
		ファイル階層はドメイン(yahoo.co.jp)やIP(182.22.59.229)の後に続く
		「/」以降の文字列になります。
		例えばyahooの海外サッカーのURLは「soccer.yahoo.co.jp/ws/」となっています。
		これは「soccer.yahoo.co.jp」までがドメインでその後の「/ws/」が
		そのドメインをファイル階層の頂点としたファイル階層です。

	④.ソケットを開く
		IPアドレスの種類、通信の種類を決定して
		socket関数でソケットを開きます。

	⑤.ホスト名からホスト情報を取得する
		ホスト名からホスト情報を取得する方法は2種類あります。
			・gethostbyname関数の使用
				ドメイン名(FQDN含む)、コンピュータ名の場合は
				gethostbyname関数を使用することで情報を取得できます。
				
			・gethostbyaddr関数の使用
				IPアドレスの場合はgethostbyaddrを使用しますが
				この関数はアドレスが文字列のままでは使用できないので、
				inet_addr関数を使用して文字表現を数値表現に変換して
				情報の取得をします。

	⑥.ホストへ接続する
		ホストへの接続はconnetct関数を使用します。
		接続ための情報をSOCKADDR_IN構造体を使用して設定します。

		・SOCKADDRとSOCKADDR_IN構造体の関係
			WinSockの関数ではソケットアドレスの情報を引数で渡す場面が多々あり、
			関数の引数の内容はSOCKADDRのポインタとなっています。
			ですが、SOCKADDR_INの構造体を設定したとしても問題なく動作します。
			理由として、SOCKADDRは他のソケット構造体に切り替えても
			動作するようにメモリ配置を考えて作られています。

	⑦.閲覧したいページの情報を送信する
		HTTP通信を行う際は確認したいURLのGETリクエストを
		send関数を使用して送信する

		GETリクエスト基本書式:
			GET 閲覧したいURL HTTP/1.1

	⑧.受信データを読み込む
		sendで送信したHTTPデータをrecv関数で受信する

	⑨.ソケットの送受信を停止する
		通信を終了する前にshutdown関数を使用して
		ソケットの受信と送信を停止する

	⑩.ソケットを閉じる
		使用が終わったソケットはclosesocket関数で閉じる

	⑪.WinSockを終了する
		WSACleanup関数を使用してWinSockを終了する

●ソース
	#define _WINSOCK_DEPRECATED_NO_WARNINGS
	#include <stdio.h>
	#include <WinSock2.h>

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

	void main(void)
	{
		WSADATA wsa_data;
		LPHOSTENT lp_host;
		SOCKET sock;
		int result;
		SOCKADDR_IN sock_add;
		char server[256];
		char url[256];
		char rcv[256];
		char port[8];
		char str[256];
		unsigned short port_no;
		unsigned int addr;

		while (true)
		{
			// ①.WinSockを初期化
			result = WSAStartup(MAKEWORD(1, 1), &wsa_data);

			if (result != 0)
			{
				printf("WinSockの初期化エラー\n");
				getchar();
				return;
			}

			// ②.ホスト、ポート号の入力
			// ホスト名入力(yahoo.co.jp)
			printf("-------ホスト名-------\n");
			gets_s(server);
				
			// ポート番号入力
			printf("-------ポート番号-------\n");
			gets_s(port);

			// ポート番号80以外はエラー
			if (strcmp(port, "80") != 0)
			{
				printf("ポート番号は80のみ使用可能です。\n");
				getchar();
				return;
			}

			// ③.ファイル階層入力
			// ファイル階層入力
			port_no = (unsigned short)atoi(port);
			printf("-------ファイル名-------\n");
			gets_s(url);

			// ファイル階層が入力されていなかったらエラー
			if (strcmp(url, "") == 0)
			{
				url[0] = '/';
			}

			// ④.ソケットを開く
			sock = socket(PF_INET, SOCK_STREAM, 0);
			if (sock == INVALID_SOCKET)
			{
				printf("ソケットオープンエラー\n");
				WSACleanup();
				getchar();
				return;
			}

			// ⑤.サーバー名からホスト情報を取得
			lp_host = gethostbyname(server);
			if (lp_host == NULL)
			{
				// アドレスを表す文字列を数値表現に変換
				addr = inet_addr(server);
				// ネットワークアドレスからホスト情報を取得
				lp_host = gethostbyaddr((char*)&addr, 4, AF_INET);

				if (lp_host == NULL)
				{
					printf("%sが見つかりません\n", server);
					WSACleanup();
					getchar();
					return;
				}
			}

			// SOCKADDR_IN構造体に必要事項をセット
			memset(&sock_add, 0, sizeof(sock_add));
			sock_add.sin_family = AF_INET;
			sock_add.sin_port = htons(port_no);
			sock_add.sin_addr = *((LPIN_ADDR)*lp_host->h_addr_list);

			// ⑥.ソケット接続
			if (connect(sock, (PSOCKADDR)&sock_add, sizeof(sock_add)) != 0)
			{
				printf("ソケットに接続失敗\n");
				closesocket(sock);
				WSACleanup();
				getchar();
				return;
			}

			// ⑦.GETリクエスト送信
			sprintf_s(str, "GET http://%s%s HTTP/1.0\r\n\r\n”", server, url, 256);
			result = send(sock, str, (int)strlen(str), 0);
			printf("RequestURL = %s\n", str);
				
			while (true)
			{
				// 受信用バッファをクリア
				memset(rcv, 0, sizeof(rcv));

				// ⑧.データを受信
				result = recv(sock, rcv, (int)sizeof(rcv) - 1, 0);

				// 受信したデータを標準出力に書き出す
				printf("%s\n", rcv);

				if (result == 0)
				{
					// 何も受け取らなかったら終了
					break;
				} else if (result == SOCKET_ERROR) {
					printf("recvエラー\n");
					getchar();
					break;
				}
			}

			// ⑨.シャットダウン
			if (shutdown(sock, SD_BOTH) != 0)
			{
				printf("シャットダウンに失敗しました\n");
				getchar();
			}
			// ⑩.ソケットを閉じる
			closesocket(sock);

			// ⑪.WinSock解放 
			WSACleanup();
		}
	}

●サンプル
	以下のリンクは上記コードが書かれているサンプルです。
	
	サンプル