Win32/64ネットワーク開発 004 winsockを使ったhttp通信

提供:yonewiki
2023年12月4日 (月) 22:04時点におけるYo-net (トーク | 投稿記録)による版 (→‎概要)

Win32/64 ネットワーク処理開発へ戻る。

前の記事:Win32/64ネットワーク開発 003 wininetを使ったftp通信

次の記事:[[Win32/64ネットワーク開発 005]]

概要

 WinSockという通信の窓口を使って、ネットワークを通信してみたいと思います。自分ではネットワークの基礎となる装置を作るのは大変なのでWindowsではコレを使うのが最も基礎的なネットワークのプログラムを始める原点になると思います。


 まずはhttp通信を処理するプログラムを作りますが、今はhttpsという通信が主要なので、あまり役に立たないですね。でも、https通信を行うとなると一気に難しくなります。httpsはSSL(Secure Socket Layer)やTLS(Transport Layer Security)という暗号方式で通信を行います。暗号通信おそるべし。そしてftpにもSSLがあります。ftpsですね。リモートにあるサーバを端末から操作する際、通信される一切の情報を暗号化するSSH(Secure SHell)を使ったSFTPもあるか。UNIX系のシェルが備えられている場合に使われるSCP(Secure Copy Protocol)もあるね。いろいろあります。ネットワーク通信楽しいけど、通信の道を出歩くときはちゃんと装備をして出歩かないと、街で強盗に合うような、通信の傍受をされることもあります。悪いことをする人がいなくなるといいんだけど、不平不満がなくならない世界に悪はなくならない。我慢が足りない人はたくさん存在するし、裕福な人の生活があり、そこをうらやむ人がいて、埋めようとする人がいる。ソフトウェアの勉強でもして人生を消費するのも楽しいのにね。Windows/Linux/Macパソコンがあるだけで裕福だと思うし、ましてや電力契約・ネットワーク通信契約が存在する環境があるなんて裕福過ぎる。スマホだと開発はあまりできないから、SNSに時間を消費したり暇になっちゃうかな。ここの管理人はSNSはやらないっすけどね。使い方だけちょろっと知って、あとはそっとしておくっす。


 なんにしても、複雑なことをする前に、風前の灯となりつつあるHTTP通信の習得からですね。


#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <Windows.h>
#include <cstdio>
#include <winsock2.h>
#include <iostream>
#include <string>
#pragma comment( lib, "ws2_32.lib" )
int main()
{
	WSADATA WSADATAwsaData;
	LPHOSTENT LPHOSTENTlpHostnet;
	SOCKET SOCKETs;
	int iReturn;
	SOCKADDR_IN SOCKADDR_INsockadd;
	char szServer[256], szURL[256], szStrRcv[1024], szPort[8], szYesNo[4];
	wchar_t wchStr[256];
	u_short u_shortPort;
	unsigned int uiAddr;

	while (1) {
		if (WSAStartup(MAKEWORD(1, 1), &WSADATAwsaData) != 0) {
			perror("WSAStartupエラー\n");
			return -1;
		}

		printf("Webサーバー名(ex.www.yo-net.jp):");
		std::cin >> szServer;
		printf("ポート番号(ex.80):");
		std::cin >> szPort;
		if (strcmp(szPort, "") == 0) {
			strcpy_s(szPort, strlen("80"), "80");
		}

		u_shortPort = (u_short)atoi(szPort);
		printf("URL(ex.http://www.yo-net.jp/index.html):");
		std::cin >> szURL;
		if (strcmp(szURL, "") == 0){
			strcpy_s(szURL, strlen("/"), "/");
		}

		//ソケット処理
		SOCKETs = socket(PF_INET, SOCK_STREAM, 0);
		if (SOCKETs == INVALID_SOCKET) {
			perror("ソケットをオープンできない。\n");
			WSACleanup();
			return -2;
		}

		//ホスト情報取得処理
		LPHOSTENTlpHostnet = gethostbyname(szServer);
		if (LPHOSTENTlpHostnet == NULL) {
			uiAddr = inet_addr(szServer);
			LPHOSTENTlpHostnet = gethostbyaddr((char*)&uiAddr, 4, AF_INET);
			if (LPHOSTENTlpHostnet == NULL) {
				int iUTF16Len = MultiByteToWideChar(932, 0, szServer, -1, NULL, 0);
				std::wstring wstringCP932Str(iUTF16Len, 0);
				if (iUTF16Len > 0) {
					MultiByteToWideChar(932, 0, szServer, -1, &wstringCP932Str[0], iUTF16Len);
				}

				wsprintf(wchStr, L"%sが見つからない\n", wstringCP932Str.c_str());
				int iCP932Len = WideCharToMultiByte(932, 0, wchStr, -1, NULL, 0, NULL, NULL);
				std::string stringCP932Str(iCP932Len, 0);
				if (iCP932Len > 0) {
					WideCharToMultiByte(932, 0, wchStr, -1, &stringCP932Str[0], iCP932Len, NULL, NULL);
				}
				perror(stringCP932Str.c_str());
				WSACleanup();
				return -3;
			}
		}

		//SOCKADDR型構造体への値設定処理
		memset(&SOCKADDR_INsockadd, 0, sizeof(SOCKADDR_INsockadd));
		SOCKADDR_INsockadd.sin_family = AF_INET;
		SOCKADDR_INsockadd.sin_port = htons(u_shortPort);
		SOCKADDR_INsockadd.sin_addr = *((LPIN_ADDR)*LPHOSTENTlpHostnet->h_addr_list);
		
		//接続処理
		if (connect(SOCKETs, (PSOCKADDR)&SOCKADDR_INsockadd, sizeof(SOCKADDR_INsockadd)) != 0) {
			perror("サーバーソケットに接続失敗\n");
			closesocket(SOCKETs);
			WSACleanup();
			return -4;
		}

		//送信メッセージ形成処理
		int iUTF16Len = MultiByteToWideChar(932, 0, szURL, -1, NULL, 0);
		std::wstring wstringCP932Str(iUTF16Len, 0);
		if (iUTF16Len > 0) {
			MultiByteToWideChar(932, 0, szURL, -1, &wstringCP932Str[0], iUTF16Len);
		}

		wsprintf(wchStr, L"GET %s HTTP/1.0\r\n\r\n", wstringCP932Str.c_str());
		int iCP932Len = WideCharToMultiByte(932, 0, wchStr, -1, NULL, 0, NULL, NULL);
		std::string stringCP932Str(iCP932Len, 0);
		if (iCP932Len > 0) {
			WideCharToMultiByte(932, 0, wchStr, -1, &stringCP932Str[0], iCP932Len, NULL, NULL);
		}

		//送信処理
		iReturn = send(SOCKETs, stringCP932Str.c_str(), (int)strlen(stringCP932Str.c_str()), 0);

		//受信処理 受信最終文字までループ継続
		while (1) {
			memset(szStrRcv, '\0', sizeof(szStrRcv));
			iReturn = recv(SOCKETs, szStrRcv, (int)sizeof(szStrRcv) - 1, 0);
			printf("%s", szStrRcv);
			if (iReturn == 0){
				break;
			}
			if (iReturn == SOCKET_ERROR) {
				perror("recvエラー\n");
				break;
			}
		}

		//終了処理 シャットダウン→クローズ→WSAクリア
		if (shutdown(SOCKETs, SD_BOTH) != 0) {
			perror("ソケットシャットダウン失敗\n");
		}

		closesocket(SOCKETs);
		WSACleanup();
		
		//プログラム継続判定
		printf("\n再実行(Y/N):");
		std::cin >> szYesNo;
		if (strcmp(szYesNo, "n") == 0 || strcmp(szYesNo, "N") == 0){
			break;
		}
	}
	return 0;
}


 上記のコードはコマンドプロンプトで動くコンソールアプリケーションです。プロジェクトのプロパティでリンカーの入力の追加の依存ファイルに;WSock32.lib;ws2_32.libを追加しなければなりません。


 では少しづつ、動きを確認してみましょう。


 1行目の#define WIN32_LEAN_AND_MEANがある場合Windows.hから以下のインクルードが除外されます。

  • #include <cderr.h>
  • #include <dde.h>
  • #include <ddeml.h>
  • #include <dlgs.h>
  • #include <lzexpand.h>
  • #include <mmsystem.h>
  • #include <nb30.h>
  • #include <rpc.h>
  • #include <shellapi.h>
  • #include <winperf.h>
  • #include <winsock.h>
  • #include <wincrypt.h>
  • #include <winefs.h>
  • #include <winscard.h>
  • #include <winspool.h>
  • #include <ole2.h>
  • #include <commdlg.h>


 次に#define _WINSOCK_DEPRECATED_NO_WARNINGSで、Winsockの関連関数を使うときにエラーとするようになっている部分を解除します。


 WinSockを使う場合はwinsock2.hをインクルードすると関連関数が導入され使えるようになります。


 WinSockとはいったい何なのかという疑問もありますが、考察をすると長くなります。でも知っておいた方がいいような気もする。歴史的な背景とかね。バージョンとかね。winsock2だもんね。知識を吸収する部分は本家ウィキペディアでも難しいことはあまりないのでそちらに説明を譲ります。https://ja.wikipedia.org/wiki/Winsockですね。深追いするとなかなか難しいことになってきます。ほどよい距離感で知るとよいでしょう。


 main関数の最初の部分ではこれから使う変数などを定義しています。そして、プログラム全体を繰り返して使うための永久ループwhileを使っています。whileから出るときは最後の部分で入力をうけつけて、それがある一定の入力値なら、break;して抜け出して処理を終了します。


 最初に実行すべきは、winsock関数利用時の初期化処理です。23行目がそれにあたります。


 全体の流れとしては、


WSAStartup(winsockバージョン番号指定, 受け取る変数)で初期化

socket(ソケット方式指定)関数でsocket値を取得

gethostbyneame(ホスト名)でLPHOSTENT型値にアドレス情報を取得構築する

SOCKADDR_IN型にアドレス情報と方式とポート番号情報を指定して、接続情報を構築

connect関数で接続。socket値とSOCKADDR_IN型値を使います。

通信メッセージをsend関数で送信。直前までに送信するメッセージを構築しておかないと駄目ですね。

送信した応答をrecv関数で受け取り、受け取り時は一定の大きさの文字列変数で受け取り終わるまで繰り返すような呼び出しをする仕組みがあります。

通信が終わったら、ソケット番号のシャットダウン

ソケットのクローズ

WSACleanupで処理を終了


 というような、処理の流れになります。


 上記の流れを細かく追いつつ、主要な関数の使い方の説明をしていきます。まずはwinsockを使うときの初期化にあたる、WSAStartup関数から見ていきましょう。WSAとつく関数はwinsock関連の関数であることを表しています。Windows Sockets APIを意味していると思います。


■WSAStartup(WORD, LPWSADATA)


第1引数:WORD wVersionRequested

 MAKEWORD関数が二つのByte型(8bit)引数をとり、WORD型(16bit)に変換してくれますので、WinSockのバージョン1.1を使うときは、MAKEWORD(1, 1)のように指定すると、0x0101(10進数で257)という値を渡すことができます。WORD値の上位バイトがマイナーバージョンで、下位バイトがマイナーバージョンなのですが、MAKEWORDマクロは最初の引数が下位バイトで、次が上位バイトを受け取ることになっているので、バージョン2.0のときはMAKEWORD(2, 0)と指定します。


第2引数:LPWSADATA lpWSAData

 WSADATA型の変数の参照を渡すと、引数値に初期化の結果返される値が格納されます。今回はWSADATAwsaData.wVersionに257。WSADATAwsaData.wHighVersionに514(0x0202で2.2が最新版)。WSADATAwsaData.iMaxSocketsに32767(0x7fff)。WSADATAwsaData.iMaxSocketsに32767。WSADATAwsaData.UdpDgに65467(0xffbb)。WSADATAwsaData.szDescriptionに"WinSock 2.0"。WSADATAwsaData.szSystemStatusに"Running"。というような内容が格納されます。WSADATAwsaData.lpVendorInfo char*型変数は初期化されず不定値です。


 

前の記事:Win32/64ネットワーク開発 003 wininetを使ったftp通信

次の記事:[[Win32/64ネットワーク開発 005]]

Win32/64 ネットワーク処理開発へ戻る。