先日、ブログ「Geekなページ」の記事 に触発されて、ツイッター上でインターネットが IPv4からIPv6 への移行する際の話をしてたら、ブログの筆者の、あきみち(@geekpage)さんにいろいろ親切に教えていただきました。
そのとき あきみち さんからサンプルでいただいたソースコードを中3の息子に読ませてみた、というメモです。
(注:親バカねたです。)
やりとりの中で、IPv4からIPv6に移行(または混在)した場合に、ルーター等が機能したりパケット等がちゃんとやりとりされるレベルだけではなく、
本当にブラウザとかdropboxとかevernoteとかメールとか、全部ちゃんと動くのかなあ
という私の質問に対して、
凄く単純化すると、アプリの中でやってることは、このLinux Cサンプルみたいな感じになると思います→ http://www.geekpage.jp/programming/linux-network/getaddrinfo-4.php
ご紹介いただいたページに掲載されているコードは以下の通り。
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> int main() { char *hostname = "localhost"; char *service = "http"; struct addrinfo hints, *res0, *res; int err; int sock; memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = PF_UNSPEC; if ((err = getaddrinfo(hostname, service, &hints, &res0)) != 0) { printf("error %d\n", err); return 1; } for (res=res0; res!=NULL; res=res->ai_next) { sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) { continue; } if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) { close(sock); continue; } break; } if (res == NULL) { /* 有効な接続が出来なかった */ printf("failed\n"); return 1; } freeaddrinfo(res0); /* ここ以降にsockを使った通信を行うプログラムを書いてください */ return 0; }
十分短いとは思うのですが、FORTRAN、COBOL、N88-BASIC時代のおっさんに、Cのソースコードがすらすら読めるわけもございません。
ということで、現在中学3年生で「将来の夢は年収250万円のプログラマー」という我が家の息子に、
差出人: Tetsuya Isozaki
日時: 2011年2月24日 22:10:42JST
宛先: *** Isozaki
件名: このソース読んでーhttp://www.geekpage.jp/programming/linux-network/getaddrinfo-4.php
とメールを送っておいたところ、返事が来ましたよ。
差出人: *** Isozaki
日時: 2011年2月25日 19:55:32JST
宛先: Tetsuya Isozaki
件名: Re: このソース読んでー
読みました。俺のコメントつき↓タブの幅を4文字分で、等幅フォントを使うと
読みやすいかも#include <stdio.h> /* 標準入出力ライブラリ */
#include <sys/types.h> /* addrinfo型の定義されているヘッダファイル */
#include <sys/socket.h> /* ソケット通信のライブラリ */
#include <netdb.h> /* ネットワークデータベースを弄るときに使う定数、型の定義のヘッダファイル */int main(){ /* ここからたぶんC++で書かれていることが考えられる。Cでは「int main(void)」と書くべき */
/* 変数の定義の仕方は「型名 変数名」となる。変数名の頭にアスタリスクがついているのはポインタの印
文字型のポインタ≒文字列の変数みたいな感じ。しかしこの考えが初学者を苦しめるw */char *hostname = “localhost”; /* つまり「127.0.0.1」のこと、IPv6では「::1」って書くらしいけどv6はよく知らんw */
char *service = “http”; /* ポート番号の指定をする、つまりはプロトコルをhttpとすることでポートが23に決まる。
文字列→ポート番号の処理はgetaddrinfo()が行う */struct addrinfo hints, *res0, *res; /* 構造体の変数の定義addrinfo構造体はgetaddrinfo()関数に渡すために用いる */
int err;
int sock;memset(&hints, 0, sizeof(hints)); /* 構造体のゼロクリア、WinAPIなどではZeroMemory()マクロなどでも代用できる、
構造体にその領域を使ってた前のアプリケーションの残りかすが残っていると結果が不定となるバグの元*/hints.ai_socktype = SOCK_STREAM; /*ソケット型の指定、SOCK_STREAM(ストリームタイプ)とSOCK_DGRAM(データグラムタイプ)を指定できる*/
hints.ai_family = PF_UNSPEC; /*アドレスファミリの指定、AF_INET(IPv4)かAF_INET6(IPv6)を指定できるのだが、今回はPF_UNSPECなので指定なし*/if ((err = getaddrinfo(hostname, service, &hints, &res0)) != 0) {
/*C/C++では良く使うけれどほかでは見ない形分かりやすく書き直すと
err = getaddrinfo(hostname,service, &hints,&res0)
if(err!=0){ … }
となる。0はNULLをあらわし、getaddrinfo()関数は失敗すると0以外の値を返す。
getaddrinfo()関数はインターネットのホストとサービスを識別する役割を果たし、渡されたaddrinfo構造体で
ネットワークを弄ることができる。
呼び出しに使われたアンバサントつきの実引数(具体的には「&hints」、「&res0」のこと)については
「&変数名」で、変数のアドレスを取り出すことができる。*/printf(“error %d\n”, err);
/* errorと表示。\nは改行コードのエスケープシーケンスprintf()関数は文字列の最後に改行コードを付け足してくれないので
自ら足さないとたぶん画面に
「error 2C:\>」
みたいな意味不明文字列が表示されてしまう。ちなみにputs()関数は自動的にくっつけてくれる。
printf()関数のいいところはフォーマット指定子が使えることで、errの内容を文字列に埋め込んで表示することができる。
表示例
error 941
C:\>
みたいな感じ。「C:\>」は勝手に考えたプロンプトだからお気になさらず。*/return 1;
}for (res=res0; res!=NULL; res=res->ai_next) {
/*ご存知for文for(初期化;条件;インクリメント)
まあtoとかはでてこんわけです。basicと違って。
初期化はいいとして、「条件」のブロックが成り立たなくなったときループを抜けるわけです
「インクリメント」のブロックはこれは実際にインクリメントしているわけではなくて、
整数を増減させる以外のこともできる。ループのたびに毎回実行するということです。
ここでは、リスト構造になっているaddrinfo構造体の次の要素にポインタを進めているというわけです。
リスト構造のわかりやすい説明はこちら http://ja.wikipedia.org/wiki/%E9%80%A3%E7%B5%90%E3%83%AA%E3%82%B9%E3%83%88
ほんとにざっくり言えば配列の従兄弟みたいな感じw */
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
/* ソケットのハンドルを得る。本来socket()の戻り値はSOCKET型なのだが、おそらくtypedefで定義されていて実質int型だと思われる
たぶんエラーは出ないけど警告くらいなら出ると思うw 失敗したときはINVALID_SOCKETを返す。
intにしたけりゃ明示的にキャストしたほうがいい
sock = (int) socket(res->ai_family, res->ai_socktype, res->ai_protocol);みたいな感じで。 */if (sock < 0) { /* INVALID_SOCKETのMSBがたぶん1なんだろうね、たぶんあんまり良くない書き方w */
continue; /* 失敗したらがりがり条件を変えて再試行しろということです */
}if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) { /* 接続要求を出す。成功の場合は0を返すので、非ゼロの場合がエラー */
close(sock); /* お約束、ソケットの終了処理。fopen()に対してfclose()みたいな感じかな。今までのが閉じられてなかったら、
どんどんと二度と開放できないメモリがたまっていく。(うひー) */continue; /* 失敗し(ry */
}break; /* breakはループを一段階抜けるための制御文。考えてほしいのは、
ここまでたどり着けるのは接続に成功した場合ということ。
つまり成功したらループを抜けてくれと言っているのだ。 */
}if (res == NULL) {
/* 有効な接続が出来なかった */
/* ↑おっしゃるとおりで上のループは、失敗している間は条件が潰えるまで実行されている。
抜けるときは、どうなっているかと言うと、すべて失敗したとき、もしくは、成功があった瞬間である。
ここで、リスト構造のおわりのノードの次ノードにリンクするためのポインタに普通NULL(0)をいれる。(←悪文ですまん)
要は最後までたどり着くとresの値はNULLになる。
つまり、試しつくした状態のときにres==NULLが成り立ち、上のループではすべて失敗になるときは、
すべての条件が試されている…脳内で自分がコンピュータだと思ってシュミレーションしてみるとわかりやすいかも。 */printf(“failed\n”);
/* failedと表示\nは改行コードのエスケープシーケンスprintf()関数は文字列の最後に改行コードを付け足してくれないので
自ら足さないとたぶん画面に
「failedC:\>」
みたいな意味不明文字列が表示されてしまう。ちなみにputs()関数は自動的にくっつけてくれる。*/return 1;
/* 関数の戻り値を返し、サブルーチンを抜ける。これはmain()関数なのでここでreturn文を使うとプログラム自体を終了する働きを持つ。
exit()関数を使えば、main()関数以外でも同じことができる。「return 1;」の1はつまるところ終了コードとなる訳だが、
ご存知の通り、0以外だったら普通は何らかのエラーが発生したことを表す。 */
}freeaddrinfo(res0);
/*res0はgetaddrinfo()で、得た値を表している。openしたらcloseする。
get,allocしたらfreeするのがC/C++言語のお約束。メモリ管理は大事だよw*//* ここ以降にsockを使った通信を行うプログラムを書いてください */
return 0; /*正常終了*/
}
・・・うん、説明ありがとう。
お父さんの理解の助けにはなるけど、息子の理解がそもそも正しいのか、どれくらい実用になりそうなレベルなのかを確かめるすべが、すでにお父さんには無いことを知った次第でありました。orz
(追記)
あきみちさんからツイッターでいただいたコメント:
ブログ拝見しました。凄いですね!一部WIndows環境をベースに考えていてLinuxコードの解釈がしきれていない部分や、細かいミスもありますが、凄いと思いました!
ということで、あきみちさん著のこの本:
ソフトバンククリエイティブ
売り上げランキング: 46115
を購入。
ソースコードの一括ダウンロードと書籍の正誤表は
http://www.geekpage.jp/programming/linux-network/book/
にあるそうです。
(ではまた。)
[PR]
メールマガジン週刊isologue(毎週月曜日発行840円/月):
「note」でのお申し込みはこちらから。