Web/DB プログラミング徹底解説

ホーム > Windows プログラミング入門 > はじめての WinSock 2 プログラム ~ WinSock 2 による名前解決

はじめての WinSock 2 プログラム ~ WinSock 2 による名前解決

今回から数回に分けて、ネットワークプログラミングについて勉強しましょう。

ネットワークプログラミング、と一口に言ってもいろいろあります。例えば、今回ご紹介するソケット。 その他、WinInet API、 WinHTTP API、 HTTP API、あるいは .NET Framework 上にもネットワーク用の使いやすいライブラリはたくさんあります。 特に HTTP 用の API はたくさんあるため、機能的にはそれぞれ重なっている部分もたくさんあります。

それぞれいろんな特色がありますが、私の考えとしては、「一番地味なところから学び、徐々に便利な API を学ぶほうが良い」 と思っています。その意味で、ソケットプログラミングをはじめに取り上げます。

WinSock とは何か?

「とは何か?」 なんてタイトルを書いておいて言うのもなんですが、ソケットとは何か?というところは適当にご自身で Wikipedia でも読んでいただきたいと思います(笑)

ここでは、徹底解説流 ではないとお思いになるかもしれませんが、動くサンプルを提示することによって、 WinSock を肌で理解していただきたい と思います。

なぜならば、WinSock の最新の仕様、バージョン 2.2.1 でさえ 1997 年に策定済みであり、あちこちでその歴史的背景やら 意味づけなどが説明されています。ですから、私がここで WinSock そのものについて説明を繰り返す意味はないと思うからです。これが理由のひとつ。 もうひとつの理由は、Windows 上でネットワークプログラミングを本気でやるなら WinSock 2 の基本的な関数セットをそのまま使うことはないと思うからです。 Windows でソケットレベルのコード書くなら、「標準的な WinSock 2」 ではなくWinSock のマイクロソフトの拡張 API 群 を使うべきと思います。標準的な WinSock 2 を最初にちょっと使ってみてから、さらに上位の API を理解するという手順を踏むあたりが、 徹底解説流 の理解の仕方だと思っていただけると嬉しいです。

という、言い訳 前置きをしてから、ざっくり WinSock 「入門コース」 らしくひとことで頭に入れるなら、
「最も低レベルで伝統的なネットワーク API。ちょっと面倒くさいけどほぼ何でもできる」
という認識でいいと思います。

はじめての WinSock プログラム ~ 名前解決方法

それではさっそく、はじめての WinSock プログラムを作ってみましょう。出来上がるプログラムは、名前解決をするプログラムです。 つまり、www.microsoft.com などの名前を IP アドレスに変換するプログラムです。

以下のコードを mylookup.cpp という名前で保存してください。

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

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

     INT rc;
     WSADATA wsaData;
     addrinfo hints, *pAddrInfo = NULL;
     ZeroMemory(&hints, sizeof(addrinfo));
     SOCKADDR_IN *pSockAddr_in;

     if ( argc != 2 ) {
          printf ("ex.\n mylookup www.yahoo.com\n");
          return 1;
     }

     // WinSock の初期化
     rc = WSAStartup(MAKEWORD(2, 2), &wsaData);

     if( rc ) {
          printf("WSAStartup Failed.\n");
          return 1;
     }

     // アドレス情報の取得
     hints.ai_flags    = AI_CANONNAME;
     hints.ai_family   = AF_UNSPEC;
     hints.ai_socktype = SOCK_STREAM;
     hints.ai_protocol = IPPROTO_TCP;

     rc= getaddrinfo( argv[1], "http", &hints, &pAddrInfo);

     if( rc ) {
          printf("getaddrinfo Failed. rc = %d \n", rc);
          return 1;
     }

     while (1) {

          printf ("------------------------------\n");
          printf ("Canon Name: %s\n", pAddrInfo->ai_canonname ? 
               pAddrInfo->ai_canonname : "");
          
          pSockAddr_in = (SOCKADDR_IN*) pAddrInfo->ai_addr;
          printf ("Port:       %d\n", ntohs(pSockAddr_in->sin_port));
          printf ("Addr:       %s\n", inet_ntoa(pSockAddr_in->sin_addr));
          
          if( !pAddrInfo->ai_next ) {
               break;
          }

          pAddrInfo = pAddrInfo->ai_next;

     }

     freeaddrinfo(pAddrInfo);

     // WinSock のクリーンアップ
     WSACleanup();

     return 0;
}

makefile は次の通りです。

TARGETNAME=mylookup
OUTDIR=.\chk

ALL : "$(OUTDIR)\$(TARGETNAME).exe"


"$(OUTDIR)" :
    @if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

CPP_PROJ=\
	/MT\
	/W3\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/DWIN32
		
LINK32=link.exe

LINK32_FLAGS=\
	ws2_32.lib\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG\
	/RELEASE
	
LINK32_OBJS= \
	"$(OUTDIR)\mylookup.obj"


"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

.cpp{$(OUTDIR)}.obj::
   $(CPP) $(CPP_PROJ) $<

WinSock 2 を使うために、ws2_32.lib とリンクしています。

nmake でビルドすると、chk サブディレクト内に mylookup.exe が出来上がります。 それを実行すると次のようになります。

> mylookup www.google.com
------------------------------
Canon Name: www.l.google.com
Port:       80
Addr:       74.125.19.104
------------------------------
Canon Name:
Port:       80
Addr:       74.125.19.99
------------------------------
Canon Name:
Port:       80
Addr:       74.125.19.103
------------------------------
Canon Name:
Port:       80
Addr:       74.125.19.147

>

この実行結果は、www.google.com というひとつの名前に対して、複数の IP アドレスが解決されていることを示しています。

ここで利用している関数は getaddrinfo です。以前は名前解決には gethostbyname を使うとされていましたが、 RFC 2553 以降 (現在は RFC 3493) に getaddrinfo が組み入れられ、getaddrinfo を使うことが推奨されています。

さて、いかがでしたでしょうか? ちゃんと動きましたでしょうか? このコードは、次回説明したいと思います。