WinSock (Windows Sockets) API で IPv4/IPv6 デュアル スタック プログラミングに挑戦!!(TCPクライアント編)


Platform SDK (Windows SDK) サポートの小泉です。
みなさま、IPv4 アドレスが枯渇するといった話題が出てから何年も経ちましたが、IPv6 対応は既にお済でしょうか?

「まだ IPv4 が使えるし、IPv6 への改修をしたらIPv4 対応しかしていない古いネットワークでは自社アプリケーションが使えなくなってしまう。IPv6 対応はまだ早いかな。。。」と心配されている方!!IPv4 ネットワークにも IPv6 ネットワークにも対応できるWinSockのプログラミング方法があったとしたら如何でしょう?
今のうちに対応していれば、エンドユーザ様にも「自社のアプリケーションは、旧来の IPv4 ネットワークにも、最新の IPv6 ネットワークにも両方対応しております。将来、世の中が IPv6 ネットワークに完全移行してもアプリケーションに対する移行コストは 0 です。」と胸を張って言えますね。

という事で、今回は「WinSock (Windows Sockets) API で IPv4/IPv6 デュアル スタック プログラミングに挑戦!!」と銘打って、IPv4/IPv6 の両方に対応したプログラミング方法をご紹介いたします。

1.まずはIPv4対応WinSockプログラムのおさらい

まずは、一般的な IPv4 対応 WinSock プログラムのおさらいから始めましょう。
ほとんどの IPv4 対応WinSockプログラム(クライアント側)は以下のような流れになっているかと思います。以下の例を見てもわかるとおり、IPv4 アドレスを直接指定しており、これでは IPv4 通信しかできませんね。

 1)  WSAStartup API でソケットを初期化します
例:
WSAStartup(MAKEWORD(2, 2), &wsaData);

2)  Socket API で利用する IPv4 アドレスファミリとプロトコルを指定して、ソケットハンドルを作成します
例:
SOCKET ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

3)
  Connect API にて通信相手のアドレスとポート番号を指定し接続します
例:
sockaddr_in clientService;
clientService.sin_family = AF_INET;  //IPv4
を指定します
clientService.sin_addr.s_addr = inet_addr(“127.0.0.1”); //通信相手としてIPv4ループ バックアドレスを指定します
clientService.sin_port = htons(5010); //ポート番号を指定します
int iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService));

4) Send API にてデータを送信します
例:
int iResult = send(conn_socket, Buffer, lstrlen(Buffer)+1, 0);

5) Shutdown API にて送信終了を指示します
例:
int iResult = shutdown(ConnectSocket, SD_SEND);

6) Closesocket API にてソケットハンドルをクローズします
例:
int iResult = closesocket(ConnectSocket);

2.次に IPv6 対応にしてみましょう

さて、 IPv4/IPv6 デュアル スタック対応と、IPv6 対応の違いを明確にするために、IPv4対応をまずは IPv6 対応にしてみましょう。IPv4 対応との違いは、socket API とconnect API に渡すパラメータを IPv6 に対応させるだけです。非常に簡単な改修で済みますが、以下だと IPv6 アドレスを直接指定しているので、今度は IPv4 通信ができませんね。

1)  WSAStartup API にてソケットを初期化します
例:
WSAStartup(MAKEWORD(2, 2), &wsaData);

2)  Socket API にて利用する IPv6 アドレスファミリと TCP プロトコルを指定して、ソケットハンドルを作成します
例:
SOCKET ConnectSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);

3)
  Connect API にて通信相手のアドレスとポート番号を指定し接続します
例:
sockaddr_in6 clientService;
int intLen =sizeof(SockAddr);
WSAStringToAddress(“::1”,AF_INET6,NULL,(LPSOCKADDR)& clientService,&intLen); //
通信相手として IPv6 ループ バックアドレスを指定します
clientService. sin6_port = htons(5010); //ポート番号を指定します
int iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService));

4) Send API にてデータを送信します
例:
int iResult = send(ConnectSocket, Buffer, lstrlen(Buffer)+1, 0);

5) Shutdown API にて送信終了を指示します
例:
int iResult = shutdown(ConnectSocket, SD_SEND);

6)
 Closesocket API にてソケットハンドルをクローズします
例:
int iResult = closesocket(ConnectSocket);

3.それでは IPv4/IPv6 デュアル スタック対応にしてみましょう

お待たせしました。それでは IPv4/IPv6 デュアル スタック対応にしてみましょう。
ポイントは、getnameinfo API を利用した名前解決にあります。getnameinfo API は、ホスト名をpNodeNameパラメータに指定すると、ppResult にホスト名に対応する IPv4 および IPv6 アドレスのリストを返します。そのアドレスを利用して、ソケットハンドルを作成し、接続が成功するまで全てのアドレスを試します。
文末に IPv4/IPv6  デュアル スタック対応のサンプルプログラムの入手方法もご案内しますので、そちらも併せてご参照ください。

1)  WSAStartup API にてソケットを初期化します
例:
WSAStartup(MAKEWORD(2, 2), &wsaData);

2)  Getaddrinfo API にて通信相手がサポートしているアドレスのリストを取得します
ここではローカルホスト(自分自身)に対して、対応するアドレスのリストを取得します。
例:
struct addrinfo *results = NULL,*addrptr = NULL,hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
retval = getaddrinfo(
                        “localhost”,//ローカルホストを指定します
                        5010,
                        &hints,
                        &results
                        );

3)  アドレス リストの先頭アドレスを取得します
例:
addrinfo   addrptr = results;

4) Socket APIにて、getaddrinfo API で取得したアドレスファミリとプロトコルを指定して、ソケットハンドルを作成します
例:
SOCKET conn_socket = socket(addrptr->ai_family, addrptr->ai_socktype, addrptr->ai_protocol);

5) connect API にて通信相手のアドレスとポート番号を指定し接続します
例:
int retval = connect(conn_socket, addrptr->ai_addr, (int)addrptr->ai_addrlen);

6)
アドレス リスト内の次のアドレスを取得し、Connect  API  が成功するまで、4) 5) の処理を繰り返します
例:
addrptr = addrptr->ai_next;

7)
 Connect  API が成功したソケットハンドルを利用し、Send API にてデータを送信します
例:
int iResult = send(conn_socket, Buffer, lstrlen(Buffer)+1, 0);

8)
Shutdown API にて送信終了を指示します
例:
int iResult = shutdown(conn_socket, SD_SEND);

9)
Closesocket API にてソケットハンドルをクローズします
例:
int iResult = closesocket(conn_socket);

4.サンプル プログラムを入手してみよう

 実は、上記でご紹介したIPv4/IPv6  デュアル スタックに対応したサンプル プログラムがWindows® Software Development Kit に付属しております。すぐにビルドして動作確認ができるようにソリューションファイルと共に公開しておりますので、是非この機会にWindows® Software Development Kit をダウンロードしてみてください。

入手方法
以下のサイトよりWindows® Software Development Kitをインストールしてください
URL
http://msdn.microsoft.com/en-us/windowsserver/bb980924.aspx

C:\Program Files にインストールした場合、下記の場所にサンプルコードが展開されます。
アドレス:

C:\Program Files\Microsoft SDKs\Windows\v7.1\Samples\netds\winsock\simple
    client ——
クライアント側のサンプル プログラム(simplec.exe)です。
    server —– サーバー側のサンプル プログラム(simples.exe)です。

使い方
Visual Studio 2005 以降にてソリューションファイルを開きクライアント側およびサーバー側の双方のサンプル プログラムをビルドすると、クライアント側は  simplec.exe 、サーバー側は simples.exe が生成されます。

本サンプル プログラムは、クライアント側が送信した文字列「This is a small test message [number 0]」を、受信したサーバー側はそのまま返すという単純なコマンドライン プログラムとなります。今回は、 TCP クライアント側の動作を確認するため、Windows 7  にてクライアント側は IPv4/IPv6 デュアル スタック対応の TCP クライアントとして、サーバー側は IPv4 専用 TCP サーバーとして動作させます。以下にサンプルプログラムの使い方をご案内しますので、お試し頂けましたら幸いです。

1)
  コマンドプロンプトを起動し、サーバー側 (simples.exe)  IPv4 のみに対応したTCP サーバーとして実行します。リッスンポートは 5010 番を利用します。
コマンド例:
C:\temp\simple\server\Debug>simples.exe  -4 -p TCP -e 5010
socket 0x498 bound to address 0.0.0.0 and port 5010

2)
  初めて サーバー側 (simples.exe)  を起動する場合、以下のファィアウォールのブロックの許可を促すダイアログが表示されますので、「アクセスを許可する」を選択します。


3)
 コマンドプロンプトを起動し、クライアント側 (simplec.exe) IPv4/IPv6  デュアル スタック対応の TCP クライアントとして実行します。通信相手は、1) サーバーでリッスン中のローカルホストのポート5010番を指定します。
コマンド例:
C:\temp\simple\client\Debug>simplec -p TCP -e 5010 -n localhost

4)
 通信が成功すると、サーバー側 (simples.exe) 、クライアント側 (simplec.exe) 共に以下のように表示されます。
クライアント側の表示を見ると、まずは  IPv6  で接続を試し、次に  IPV4  で接続を試していることがわかりますね。

サーバー側(simples.exe)
C:\temp\simple\server\Debug>simples.exe  -4 -p TCP -e 5010
socket 0x498 bound to address 0.0.0.0 and port 5010
Accepted connection from host 127.0.0.1 and port 64340
read 40 bytes
wrote 40 bytes

クライアント側 (simplec.exe)
C:\temp\simple\client\Debug>simplec -p TCP -e 5010 -n localhost
Client attempting connection to: ::1 port: 5010
Client attempting connection to: 127.0.0.1 port: 5010
Connection established…
wrote 40 bytes
read 40 bytes, data [This is a small test message [number 0]] from server

補足

サーバー側 (simples.exe)  IPv6  専用 TCP サーバーとして実行した場合は以下のようになります。クライアント側 (simplec.exe) の表示を見ると、IPv6 で接続をしていることがわかりますね。つまり、クライアント側 (simplec.exe) IPv4 および IPv6 の双方に接続が可能ということがわかっていただけるかと思います。

サーバー側(simples.exe)
C:\temp\simple\server\Debug>simples.exe  -6 -p TCP -e 5010
socket 0x50 bound to address :: and port 5010
Accepted connection from host ::1 and port 2184
read 40 bytes
wrote 40 bytes

クライアント側 (simplec.exe)
C:\temp\simple\client\Debug>simplec -p TCP -e 5010 -n localhost
Client attempting connection to: ::1 port: 5010
Connection established…
wrote 40 bytes
read 40 bytes, data [This is a small test message [number 0]] from server

あとがき

IPv4/IPv6 デュアル スタック対応といっても、中身を見るとそんなに難しい実装ではないことがお分かり頂けたのではないでしょうか。
次回は TCP サーバー側の実装方法にも触れていきたいと思いますのでお楽しみに!!

Skip to main content