はじめての WinSock 2 プログラムの説明
それでは今回は、前回ビルドした はじめての WinSock2 プログラム の内容を説明します。 まだ試していない人はそちらもチラ見しておいてください。
では、コードを上からいきます。
ヘッダーファイル ~ WinSock2 用は winsock2.h と ws2tcpip.h
ヘッダーファイルは次の3つを取り込んでいます。
#include <stdio.h> #include <winsock2.h> #include <ws2tcpip.h>
WinSock 2 を利用するときの基本的なヘッダファイルは winsock2.h です。 ws2tcpip.h には WinSock2 PSA (WinSock 2 Protocol-Specific Annex) で新しく定義されたデータ構造や関数が定義されています。
stdio.h は説明するまでもありませんね。 printf 等を使うために取り込んでいます。
WinSock の初期化
最初にやらないといけないことは、WinSock ライブラリの初期化です。初期化は WSAStartup 関数を使います。
int main(int argc, char* argv[]) { INT rc; WSADATA wsaData; addrinfo hints, *pAddrInfo = NULL; ZeroMemory(&hints, sizeof(addrinfo)); SOCKADDR_IN *pSockAddr_in; ... // WinSock の初期化 rc = WSAStartup(MAKEWORD(2, 2), &wsaData); if( rc ) { printf("WSAStartup Failed.\n"); return 1; }
WSAStartup を呼び出す時に、第一引数にこれから使う WinSock のバージョンを指定します。 通常ためらわず最新バージョンの 2.2 を使うはずですから、ここでは 2.2 を指定しています。
ちなみに winsock2.h でも WINSOCK_VERSION という便利マクロが定義されてます。
#ifndef WINSOCK_VERSION #define WINSOCK_VERSION MAKEWORD(2,2) #endif
第二引数の WSADATA 構造体は次のような形をしています。
typedef struct WSAData { WORD wVersion; WORD wHighVersion; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char FAR *lpVendorInfo; } WSADATA, *LPWSADATA;
一見ややこしいですがバージョン 2 以降ではあまり役に立つ情報を返してこないので、 気にしなくて構いません。
WSAStartup の確認ツール ~ StartupTest
「いや、気になる!」 という方は、私が以前使ったツールがあるので、それで試してみてください。
WSAStartup を呼び出し WSADATA の中身を表示する
このツールは WSAStartup の第一引数のバージョンを変えて呼び出すと、 WSADATA にどのような値が返ってくるか見るだけのツールです。(私も暇ですね。こんなもの作って...(苦笑))
では次に行きましょう。
アドレス情報の取得
さていよいよ getaddrinfo 関数を使って、"www.microsoft.com" 等の名前を IP アドレスに解決します。
addrinfo hints, *pAddrInfo = NULL; ZeroMemory(&hints, sizeof(addrinfo)); SOCKADDR_IN *pSockAddr_in; ... // アドレス情報の取得 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; }
getaddrinfo は次の形をしています。
int WSAAPI getaddrinfo( __in const char *nodename, __in const char *servname, __in const struct addrinfo *hints, __out struct addrinfo **res );
第 1 引数に名前を渡し、第 2 引数にサービス名 (プロトコル名) またはポート番号を文字列で渡し、 第 3 引数に解決のヒントを渡して呼び出すと、第 4 引数にその結果が返ってくる、ということになります。
ぶっちゃけ通常、サービス名及びヒントの部分は両方 NULL でも問題ありません。ちゃんと名前解決されて返ってきます。 ここでわざわざヒントとして設定してみたパラメータの意味を詳しく知りたい方は MSDN をご覧ください。 またサービス名まで渡してあげると、結果のなかにポート番号まで入ってきて便利です。
結果の表示
結果は getaddrinfo の第 4 引数に返ってきます。型は addrinfo へのポインタです。 addrinfo は次のように定義されています。次のノードへのポインタを *ai_addr で保持する、単方向リストです。つまり言い換えると、第 4 引数に返ってくるのは addrinfo リストのリストヘッドが返ってきています。
typedef struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; char *ai_canonname; struct sockaddr *ai_addr; struct addrinfo *ai_next; } ADDRINFOA, *PADDRINFOA;
リストですから、ループで結果を表示しています。次のノードのポインタが NULL になったところでループを抜けます。
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);
アドレス情報は ai_addr に返ってきます。これは IPv4 の環境では SOCKADDR_IN* にキャスト可能です。 SOCKADDR_IN が取れれば、アドレス情報はその中のメンバーである sin_addr を inet_ntoa に渡すことによって、IP アドレスの文字列表現を取得することが出来ます。
SOCKADDR_IN については別ページ WinSock での IP アドレスとポートの扱い方 で説明していますのでそちらも見てください。
結果が不要になったら freeaddrinfo に渡してメモリを解放します。
WinSock のクリーンアップ
WinSock2 を使ったら最後に WSACleanup を呼び出してクリーンアップします。
// WinSock のクリーンアップ WSACleanup(); return 0; }
以上、はじめての WinSock2 の説明でした。慣れないデータタイプが出てきて、 最初は 「なんだこれ?」 と思うかもしれませんが、頑張ってみてください!