プログラミングWinSock

WinSockとは、プログラミングの際、Windowsでインターネット通信をするためのAPIなどである。webブラウザを作成したりしたい場合、このようなAPI(ソケット通信API)が必要である。もし単にホームページを作りたい場合には、Winsock は不要である(ホームページの作り方は、簡単なものなら HTML を使って作れる)。

Windowsに限らず、UnixやLinuxなどでも、OSが提供している、ネット通信をするためのAPI群を「ソケット」(socket)といい、ソケットを用いて通信を実現することを「ソケット通信」などという。

用語の注意

編集

この科目でいっている「クライアント」側のプログラムとは、たとえばwebブラウザそのもののようなプログラムである(HTMLファイルのことではないので、混同しないように)。

初心者むけのページ

編集
リンク先のページ内容
導入の方法 (Visual Studio 関連)
通信の全体像 (bind とか)
コードの基本骨格と概要 (#include <winsock2.h> とか)


とりあえずのコード

編集

とりあえず、動くコード(MSDN Creating a Socket for the Client などにあるコードを繋げただけ)

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

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


int main(int argc, char** argv)
{

    WSADATA wsaData;
    int iResult;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed: %d\n", iResult);
        return 1;
    }



    struct addrinfo* result = NULL,
        * ptr = NULL,
        hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;



    #define DEFAULT_PORT "27015"

    // Resolve the server address and port
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed: %d\n", iResult);
        WSACleanup();
        return 1;
    }


    SOCKET ConnectSocket = INVALID_SOCKET;

    // Attempt to connect to the first address returned by
// the call to getaddrinfo
    ptr = result;

    // Create a SOCKET for connecting to server
    ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
        ptr->ai_protocol);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Error at socket(): %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
        return 1;
    }


    // Connect to server.
    iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
    if (iResult == SOCKET_ERROR) {
        closesocket(ConnectSocket);
        ConnectSocket = INVALID_SOCKET;
    }

    // Should really try the next address returned by getaddrinfo
    // if the connect call failed
    // But for this simple example we just free the resources
    // returned by getaddrinfo and print an error message

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }



    return 0;
}


MSDN のページをもとに作成したコードだが、しかしMSDNのサイトでは「int main(int argc, char** argv)」の引数 「(int argc, char** argv) 」の部分の説明が抜けているのである。

「(int argc, char** argv) 」とは、C言語界隈で慣用的によく用いられる引数であり、「プログラム仮引数」という。(詳しくは『C言語/中級者向けの話題#プログラム仮引数を持つ関数』を参照。)


さて、構造体の部分は、

struct addrinfo* result = NULL,

(以下略) は、Windows仕様やあるいは規格か何かでこう書くと決まっているらしく、 MSDNにもこう構造体が書いてあるので、

読者はそのまま受け入れてもらうしかない。

MSDNコード例の解説

編集

MSDNの完成形プログラムの概要

編集

MSDNソースコードの予備

編集

上記のMSDNのコードの予備を、諸般の事情のため、wikibooksでも保管する。

文法の解説

編集

WSAStartup

編集

WSAStartup が成功した場合、0を返す仕様である。

つまり、もし返り値が 0 でなければ、エラーである。

なので、続くif文で、返り値が 0 かどうかを見ている。


getaddrinfo()

編集

IPv4 のころは gethostbyname が使われていたが現在ではあまりサポートされておらず、 2010年以降の現代では代わりに getaddrinfo() を使うのが推奨されています。 (上述MSDNの完成形コード例でも gethostbyname は使われておらず、getaddrinfo() だけが使われています。)

getaddrinfo()を使うには winsock2 が必要ですのでインクルードする必要があります(つまりバージョン1ではダメ)。


残念ながら日本のネットにあるwinsockについての古い解説サイトでは gethostbyname でいまだに解説しているサイトもありますが、単にそのサイトの作者が更新をサボっているだけなので無視しましょう。

getaddrinfo() は成功したら返り値として 0 を返却します。


なお、 getaddrinfo() で確保されたメモリは freeaddrinfo() で解放されます。


Linuxのソケットプログラミングにも同様の関数がありますが、しかし仕様が微妙に異なるので、読者はwinsockプログラミングではwindows用のサイトを参考にしましょう。

IPv4版

編集
コード
編集

とりあえず、下記のようなコードで、サイトのドメイン名から、IPv4対応のIPアドレスを知ることが出来ます。(※いきなりIPv6対応のコードを書くのは難しいので、まずIPv4対応のコードから書こう。)

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

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


int main(int argc, char** argv) {

    // WinSockの初期化など
    WSADATA data;
    WSAStartup(MAKEWORD(2, 2), &data);


    char hostname[20] = "www.yaahole.com"; // 架空の企業名
    struct addrinfo hints, * res;

    int err;

    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_family = AF_INET;

    hints.ai_protocol = IPPROTO_TCP;


    err = getaddrinfo(hostname, NULL, &hints, &res);

    if (err != 0) {
        return -1;
    }

    struct sockaddr_in* temp = (struct sockaddr_in*)(res->ai_addr);

    struct in_addr nameaddr;
    nameaddr.s_addr = (temp)->sin_addr.s_addr;


    char destbuf_text[500];

    inet_ntop(AF_INET, &(nameaddr.s_addr), destbuf_text, 100);
    printf("IPアドレス %s \n", destbuf_text);


    freeaddrinfo(res);

    // WinSockの終了
    WSACleanup();

}
実行結果
編集

※ 架空の企業のIPアドレスですので、数字はデタラメです。また、一部、伏字にしています。

IPアドレス 123.△〇△.987.〇〇×

のような結果になる。

※ 上記コードは2023年のwindows11でも問題なく動作する。

※よくあるエラーの場合の対処法
  • 架空の企業名のアドレスを、実際の企業のアドレスに置き換えること。
  • 上記のアドレス書き換えのさい、まちがえて企業名にhttpまたはhttpsを間違えてつけていないか確認すること。httpもhttpsもつかないのが正しい。


解説
編集
ソケット指定時のバージョン指定など
   WSAStartup(MAKEWORD(2, 2), &data);

のMAKEWORDというのは、この場合、WinSockのバージョンが2.2であることを表している。 あまり仕様の詳細のハッキリしない関数であるが、MSDNでこの関数(正確にはマクロ)が使われているので、それに従おう。

なお、生成されるWinSockのバージョンは

   WSAStartup(MAKEWORD(メジャーバージョン, マイナーバージョン), &data);

の書式である。

また、もし

   WSAStartup(MAKEWORD(2, 0), &data);

のようにバージョン2.0を指定しても、実行時に生成されるWinSockのバージョンは自動的に2.2のような実行時の最新安定版のバージョンになる。

あまり重要ではないので、詳しくは後述する。


廃止された関数

さて、かつて inet_ntoa という関数があったが、これは2020年代のVisual Studio では使えないので、 inet_ntop に置き換える必要がある。


ソケット構造体

さて、それとは別件だが、

nameaddr.s_addr = ((struct sockaddr_in*)(res->ai_addr))->sin_addr.s_addr;

は、何をやっているかというと、大まかには、まず、getaddrinfo()の結果が構造体 res (またはそのポインタ)に入り、

そしてgetaddrinfo(hostname, NULL, &hints, &res); の第4引数でアドレスを指定した構造体(&res)は、addrinfo構造体というものに格納される。 そして、Windowsの場合、addrinfo構造体は、要素として

struct sockaddr *ai_addr;

と言う要素を持つ仕様になっている。(一方、BSDでは、OPアドレス情報は sockaddr に含まないらしい。)

この sockaddr *ai_addr は構造体のポインタなので、利用の際にはアロー演算子->で指定しなければいけない(なぜ構造体そのものではなく構造体のポインタなのか気になるかもしれないが、規格などでそう決まってしまっているようなので、従うしかない。外国のサイトを見ても、同様のコードである)。

本来のBSDなどのsockaddr と sockaddr_in は、違いとして、あて先IPアドレスの情報を含んでいるかどうかの違いがある。sockaddr は、その構造体変数の内部情報には、IPアドレスの情報を含まない。一方、 sockaddr_in はIPアドレス情報を含む。なので、上記コードでは、最終的にIPアドレス情報を含む sockaddr_in に変換しているわけである。

BSD本来の sockaddr はIPアドレス情報を含まないが、Windowsでは sockaddr がなんらかの方法でIPアドレス情報を含んでおり、ともかくWindowsでは sockaddr_in に変換できるように実装をされている。


IPアドレス情報をテキスト文字化できる関数 inet_ntop が、sockaddr_in だけに対応しているので、これに変換しなけれんばならない。

 inet_ntop(AF_INET6,  &nameaddr.s_addr, destbuf_text, 100 );

とあるが、

 inet_ntop(アドレスファミリ,  & sockaddr_in 構造体変数, 単なる入れ物用の変数, 書き込みの幅 );

のような書式である。


なお、むりやり sockaddr 構造体の情報を引数に入れてもコンパイルできてしまうが、まったく違うIPアドレスが表示されてしまう。

 inet_ntop(AF_INET6,  (res->ai_addr), destbuf_text, 100 );

および

 inet_ntop(AF_INET6,  (res->ai_addr), destbuf_text, 100 );

は、まったく見当違いのアドレスを表示するだけである。


なので、まず、 nameaddr.s_addr = ((struct sockaddr_in*)(res->ai_addr))->sin_addr.s_addr; は、addrinfo構造体の中から、IPアドレス関連の要素を抜き出すために、

(res->ai_addr)

という項目がある。

そして、

(struct sockaddr_in*)(res->ai_addr)

によって、addrinfo構造体のメンバとしての sockaddr 構造体の情報を、独立した sockaddr_in 構造体のポインタとなるように転写している。

さて、 sockaddr_in 構造体は、メンバ変数として「sin_addr.s_addr」というのを持つが、

(struct sockaddr_in*)(res->ai_addr)は、 sockaddr_in 構造体ではなく、 sockaddr_in 構造体のポインタ なので、なので、「sin_addr.s_addr」にアクセスしたい場合には、アロー演算子でアクセスしなければならない。


備考
編集
MAKEWORDについて

なお、もし、冒頭の WSAStartup のコードを変えてしまって、

   WSAStartup(0x0002, &data);

と変えてもコンパイルできてしまい、実行時に表示されるIPアドレスも相手先のアドレスであるが(※ 2020年にVisual Studio 2019で確認)、しかしMSDNでは推奨していない書式なので、MAKEWORDで書いたほうが安全だろう。

なお、「0x」とは「16進数で」という意味。

WORDは、「16進数で4ケタ」の値という意味。

なお、16進数の1ケタは、4ビットである。(2×2×2×2=16なので。)

なので、1 WORD は、4×4=16より、1 WORD は16ビット長である。


そして、1バイトは8ビットなので、つまりWORDとは2バイトである。

8ビットで0から256の数値を扱えるので、2バイトだと0から65536の数値を扱える。つまり、WORDは0から65536の数値を扱える。


MAKEWORDマクロ で作られるビット列も、1 WORDである。なので、16進数で4ケタを指定してもいいのである。


なお、DWORD とは「16進数で8ケタ」である。つまりDWORDなら合計4バイトである。DWORDは4バイトなので 0から 4,294 967,296 の数値を扱える。


ちなみに、0xでWSAStartupを直接指定した場合、

   WSAStartup(0x 上2ケタがマイナーバージョン 下2ケタがメジャーバージョン, &data);

の書式である。MAKEWORDで生成した場合とは、マイナーバージョンとメジャーバージョンの順序が逆なので、気をつけよう。

なので、

   WSAStartup(0x0202, &data);

でもコンパイルが通るし、表示されるIPアドレスも正常である。

一方、

   WSAStartup(0x0200, &data);

では実行しても、IPアドレスが表示されない。


ちなみに、

    printf("%s\n", data.szDescription); // WinSock 2.0

    printf("バージョン : %d.%d \n" ,
        (BYTE)data.wHighVersion, (BYTE)(data.wHighVersion >> 8)
    );    // バージョン : 2.2

というコードをWinSockの生成以降からクローズ前の任意の場所に付け足すことで、バージョンを表示すことができる。 >> というのはシフト演算。上記コード例の場合なら、右に8ビットのシフトをするという意味。

上記コード中の「data」とは、

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

の第2引数の&dataに由来している。なので、もし引数名が別の名前(たとえば wsadata)で、ソケット生成時のコードも

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

なら、

   printf("%s\n", wsadata.szDescription); // WinSock 2.0

のようにコードが変わる。

追加コードの表示結果は、

WinSock 2.0
バージョン : 2.2

のように表示される。バージョン2.2であっても、「WinSock 2.0」と表示される。

IPv6対応のgetaddrinfo()

編集

下記コードでgetaddrinfoでIPv6でDNSサーバと通信できる。

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

#include <windows.h>
#include <stdlib.h>

#include <locale.h>

#define _WINSOCK_DEPRECATED_NO_WARNINGS // 古い関数をいくつか使っているので。古いのを置き換えできるなら不要。

#define _CRT_SECURE_NO_WARNINGS


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


// Winsock用パラメータ // ないとコンパイル時にエラーになる。
// なぜだか、早めに宣言する必要がある。
int status;
int numsnt;


int main(int argc, char** argv) {

    // WinSockの初期化など
    WSADATA data;
    WSAStartup(MAKEWORD(2, 0), &data);

    char hostname[20] = "www.yaahule.com"; // 架空の企業名
    struct addrinfo hints, * res;
    int err;

    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_family = AF_INET6;

    hints.ai_protocol = IPPROTO_TCP;


    err = getaddrinfo(hostname, NULL, &hints, &res);

    if (err != 0) {
        return -1;
    }

    // いっぺんに↓こう書いても、コンパイルできない。いろいろアレンジしてもダメ。
    // &nameaddr->sin6_addr = ((struct sockaddr_in6*)(res->ai_addr))->sin6_addr.s6_addr;

    struct sockaddr_in6* nameaddr; // in6_addr 型ではなく sockaddr_in6 に変えること

    nameaddr = (struct sockaddr_in6*)res->ai_addr;

    char destbuf_text[500];

    inet_ntop(AF_INET6, &(nameaddr->sin6_addr), destbuf_text, 200);
    printf("IP v6 アドレス - %s\n", destbuf_text);


    freeaddrinfo(res);

    // WinSockの終了
    WSACleanup();
}

socket() 関数

編集

socket() 関数の返却値は、intではなく、SOCKET型という専用の型である必要があります。

そのため、事前に SOCKET型の変数 ConnectSocket を宣言しています。


またsocket() 関数は失敗すると INVALID_SOCKET という返却値を返します。

socket()関数は3つの引数を取ります。


これら3つの引数の組み合わせで、通信方法を指定しています。


第1引数はアドレスファミリというものの指定です。


ソケット構造体

編集

基本

編集

MSDNのコード中に

struct addrinfo *result

とありますが、

これは既にwindowsによって定義されている addrinfo 構造体についてのポインタ変数 result を作成する宣言です(Unixでも、構造体の名前は違いますが、ソケット通信の専用の構造体が最初から用意されており、その構造体の変数を書き換えることで通信する方法です)。

さて、Windowsの上記の宣言では、 addrinfo は作成されません(なぜなら、すでにaddrinfo構造体は作成されているので)。


ptr->ai_family について

ソケット作成のコードで

   ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
       ptr->ai_protocol);

とあります。 「->」はアロー演算子というもので、構造体のポインタ の メンバ にアクセスするときに使います。

ポインタ ptr が出てきて厄介ですが、

読みとくと、ptr は単なる構造体 addrinf のポインタ変数です。

そしてWindowsの既に用意している addrinfo 構造体には、すでに ai_family メンバがありますので 『ADDRINFOA (ws2def.h) - Win32 apps | Microsoft Docs』『ADDRINFOA structure』

よって、 ptr->ai_family により、addrinfo構造体の ai_family メンバにアクセスできるのです。

hints 関連の項目にある AF_INET というのはIPv4のアドレスファミリですが、なぜかIPv6時代の現代でも流用されており、これで色々と通用します。

さて WindowsにかぎらずLinuxなどでも一般に hints では、通信方法を指定する情報が扱われている仕様である。

しかしWinSockのこのコードの hints は単にaddrinfo から流用した構造体変数である。 実際、MSDN の ADDRINFOA structure を見ると、ページ中ほどにある ai_family の項目一覧の中に「 AF_INET 」という項目があります。

※ hintsは結局は addrinfo 構造体の流用なので、極端な話、もしかしたら hints変数を作成しなくてもソケット通信できる可能性もあるが、しかし互換性などのため、hints 変数を残すほうが望ましいだろう。

ZeroMemory() というのは指定した変数のメモリにゼロ 0 を書き込む関数型APIマクロ[1]

ZeroMemory() の第1引数は、0を書き込みたいインスタンスへのポインタ。

第2引数は 0 を書き込む文字の長さ。

外部通信の方法

編集

外部通信の原理

編集

HTTP通信

編集

telnet(テルネット) でのHTTP通信についても、上記リンク先にある。

WinInet

編集

Windowsでは、実はWinSockを使わずとも、HTTPやFTPなど、既存の有名な通信プロトコルに対応した、簡略的に通信できる関数を持った関数グループがWindowsには既に用意されており、たとえばWinInetと言われる関数グループがそのような目的のために用意されている。


※ 分類上は、WinInet技術は WinSock とは別の分類ではあるが、本wikiでは説明のレベルの都合上、WinSockの科目内でWinInet技術についても説明することにする。

下記に、WinInetを用いたHTTP通信のコード例を示す。

下記コードを実験するには、事前に XAMPP や Apache などの webサーバ を立ち上げること。なおwindowsの場合、apacheを直接インストールするのは難しいので、xamppをインストールするのが入門的である(なお『PHP/確実に動作させるまで』にXAMPPなどの設定の解説あり)。

実験用のHTMLファイルには、detarame.html というHTMLファイルに、「seikou」(成功の意味)という文字列が書いてあるとする。

コード例
#include <stdio.h>
#include <windows.h>
#include <wininet.h> // 当然だが、wininet.h をインクルードしないと、wininet技術を使えない

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

int main() {
	// ハンドルの作成
	HINTERNET hInet;
	hInet = InternetOpen(TEXT("sample"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);

	// URLのオープン
	HINTERNET hFile;
	hFile = InternetOpenUrl(hInet,
		TEXT("http://localhost/detarame.html"), // ここにアクセスしたいページ名のURLなど
		NULL, 0, INTERNET_FLAG_RELOAD, 0);

	// ファイルの読み込みの事前処理
	DWORD readSizeMax = 1000; // 読み取りの上限サイズ。仕様により、DWORDと決まっている。
	char* outBuf; // 受信物を格納。仕様により、ポインタでなければならない

	outBuf = (char*)GlobalAlloc(GPTR, 1000); // メモリの動的確保。これしないと文字化け「seikouフフフフフフ」以下略が発生

	DWORD* realRead; // 実際に読み込まれるサイズの格納用

	// 読み込みのループ
	while (1) {
		bool rVal = InternetReadFile(hFile, outBuf, readSizeMax, (DWORD*)&realRead);

		if (rVal == true) {
			break;
		}
	}

	printf("%s\n", outBuf);
	GlobalFree(outBuf); // さきほど動的確保したメモリの解放


	// 終了処理
	InternetCloseHandle(hFile);
	InternetCloseHandle(hInet);

	return 0;
}


実行結果

http://localhost/detarame.html のページ内容と同じ内容が、そのまま表示される。

たとえば、webブラウザで上記ページにアクセスした場合に「seikou」と表示されるなら、上記C++コードを実行した際には、コマンドプロンプトで「seikou」と表示される。

seikou

のように表示される。

※ 2023年のwindows11でも問題なく上記コードは動作する。
技術解説

WinInetのHTTP通信の場合、ポート番号などの指定の必要がない。

指定するのは、ほぼURLのみで、関数 InternetOpenURL でアクセス先 URL を指定する。「URLのどこからどこがドメイン名か?」とか、ユーザーは意識の必要がない。


その他、InternetOpen などが用意されており、割とC言語のファイル入出力の雰囲気に近いようなユーザーフレンドリーな感じで操作できるように、マイクロソフトが仕様を作っている。

先にInternetOpen してからでないと、関数 InternetOpenURL を使えないハズである。


さて、C言語のファイル入出力では、ファイルポインタというものを介して、ファイルのOpenやcloseなどの処理をする。

Win32APIではファイルハンドルというものが用意されており、ファイルポインタと似たような使いかたをする。


InternetOpenの内容を、ファイルハンドルに保管する。

通信が終わったら、使用したファイルハンドルを片付けるため、関数 InternetCloseHandleでクローズする。


GlobalAllocは、Windows特有の、メモリの動的確保の関数。標準C言語でいうmallocのようなメモリ確保の関数である。

本WinInet通信では実装の都合などで、もしコレを使わずに直接にchar型の配列に受信した文字を格納しようとしても、文字化けで

seikouフフフフフフフフフフフフフフフ

以下略のように表示されてしまう。


メモリ確保したら、本来なら、使用が終わったらGlobalFree()でメモリ解放しないといけない。なので、上記コードでは、メモリ解放してある。

なお、mallocも、free()関数でメモリ解放するのが、行儀よい、とされる。なぜなら、mallocなどのメモリ確保の関数を使う場合、使用後にはメモリ解放をしないと、バグとして「メモリリーク」というメモリ圧迫のバグの原因になりやすいからである。


メモリ確保したら、本来なら、使用が終わったらGlobalFree()でメモリ解放しないといけない。なので、上記コードでは、メモリ解放してある。 実は、この程度の長さのコードの場合、Windowsが気を利かして、コード終了時にメモリを解放してくれるのでGlobalFree()の省略も可能である。だが今後、別の長いコードにコピーする場合など将来的な学習も考え、上記コードではメモリを行儀よく解放してある。


なお、もし単に GlobalAlloc(GPTR, 1000); の部分をそのまま malloc(1000); に置き換えても、Windowsの実装の都合なのか、文字化け「seikouヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘ」が出てしまう。

malloc だと、GlobalAlloc引数の「GPRT」などのフラグ設定を出来ない。


その他

マイクロソフトは、WinInetについてはサンプルコードのひとつも公式MSDNに紹介していない(2020年7月に確認)。せっかくユーザフレンドリーなWinInetがあるのに、サービス心が中途半端である。



その他のコード例

編集

InternetReadFile の仕様として、ファイルを読み終わると、 InternetReadFile(hFile, outBuf, readSizeMax, (DWORD*)&realRead); の最後の引数 realRead に 0 が代入される。

なので、この仕様を使うことで、HTTP通信における受信のwhileループを下記のように書き換えることもできる。


コード例
#include <stdio.h>
#include <windows.h>
#include <wininet.h> // 当然だが、wininet.h をインクルードしないと、wininet技術を使えない

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

int main() {
	// ハンドルの作成
	HINTERNET hInet;
	hInet = InternetOpen(TEXT("sample"), INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);

	// URLのオープン
	HINTERNET hFile;
	hFile = InternetOpenUrl(hInet,
		TEXT("http://localhost/detarame.html"), // ここにアクセスしたいページ名のURLなど
		NULL, 0, INTERNET_FLAG_RELOAD, 0);

	// ファイルの読み込みの事前処理
	DWORD readSizeMax = 1000; // 読み取りの上限サイズ。仕様により、DWORDと決まっている。
	char* outBuf; // 受信物を格納。仕様により、ポインタでなければならない

	outBuf = (char*)GlobalAlloc(GPTR, 1000); // メモリの動的確保。これしないと文字化け「seikouフフフフフフ」以下略が発生

	DWORD* realRead; // 実際に読み込まれるサイズの格納用

	// 読み込みのループ
	while (1) {
		bool rVal = InternetReadFile(hFile, outBuf, readSizeMax, (DWORD*)&realRead);

		if ( (int)realRead <= 0) {
			break;
		}
	}
    
	printf("%s\n", outBuf);
	GlobalFree(outBuf); // さきほど動的確保したメモリの解放

	// 終了処理
	InternetCloseHandle(hFile);
	InternetCloseHandle(hInet);

	return 0;
}

※ windows11で試したところ、realRead を (int) で変換しないといけなかった。変換しないとビルドエラーになった。

関連ページ

編集

外部リンク集

編集

ソケットプログラミングについての出版状況は、まるで Visual C++ の Windows APIプログラミングの書店での不足ぶりと類似の状況であり、あまり市販の入門書ではソケットプログラミングが解説されていない状態である。

そのため当面はネットの解説サイトを参考にするしかない。

下記のサイトに、充実したWinSockの解説がある。

参考文献

編集
  1. ^ 2021年8月現在、ZeroMemoryはObsolateで、SecureZeroMemory の使用が推奨されています(最適化で揮発しません)。 ⇒ https://docs.microsoft.com/ja-jp/previous-versions/windows/desktop/legacy/aa366877(v=vs.85)