[BlueLeaf1336]> PROGRAM> WinSock勉強会>
history | TOP |
2002/11/29:作成
2002/11/30:更新
2002/12/01:更新
2004/02/09:整理
IPアドレス | TOP |
ネットワークにあるコンピュータは、通常IPアドレスを持っている。 たとえば、192.168.0.1とか、127.0.0.1とか。 両方とも、実は、インターネットでは使われていないはずのアドレスだが、 とりあえず、XXX.XXX.XXX.XXXという形であらわされる、コンピュータの住所を持つ。
じゃあ、コンピュータは、どんな形で扱っているのかどうかが気になる。 まさか、このままじゃあないだろうと思った人は正解。 実際は、TInAddrという型で扱う。
type PInAddr = ^TInAddr; {$EXTERNALSYM in_addr} in_addr = record case integer of 0: (S_un_b: SunB); 1: (S_un_w: SunW); 2: (S_addr: u_long); end; TInAddr = in_addr;SunBとかSunW って何だ。と思ったらちゃんとあった。
type {$EXTERNALSYM SunB} SunB = packed record s_b1, s_b2, s_b3, s_b4: u_char; end; {$EXTERNALSYM SunW} SunW = packed record s_w1, s_w2: u_short; end;
それに、u_longとかu_charとかu_shortはと思ったらこれもある。ついでに、u_intも持ってきた。
type {$EXTERNALSYM u_char} u_char = Char; {$EXTERNALSYM u_short} u_short = Word; {$EXTERNALSYM u_int} u_int = Integer; {$EXTERNALSYM u_long} u_long = Longint;
全部見たことのある型になった。
つまり、TInAddrは、共用体で、SunBとかSunWとかu_longとかなっているが、SunB = u_char * 4、SunW = u_short * 2で、さらに言い換えると、SunB = Char * 4、SunW = Word * 2、u_long = Longintといえる。よって、4バイトを3種類の方法で表してるだけで、結局4バイトの整数ということだ。
実は上位下位が逆に表現されていることに注意する必要がある。 「インターネットではビッグエンディアン」というのに関係あるが、よくは知らない。 そのときに考えよう。今は関係ないらしいし。
inet_addr | TOP |
でも、普通見ているIPアドレスは、XXX.XXX.XXX.XXXなわけで、どうにかしてコンピュータで扱える4バイトの整数に直す必要がある。とはいえ、関数がある。
{$EXTERNALSYM inet_addr} function inet_addr(cp: PChar): u_long; stdcall; {PInAddr;} { TInAddr }
inet_addrテスト | TOP |
procedure TForm1.Button3Click(Sender: TObject); begin ShowMessage(IntToStr(inet_addr('127.0.0.1'))); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage(IntToStr(inet_addr('192.168.0.1'))); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage(IntToStr(inet_addr('dum.168.0.1'))); ShowMessage(SysErrorMessage(WSAGetLastError)); end;
IPアドレス | inet_addr(IP) | SysErrorMessage(WSAGetLastError) |
---|---|---|
127.0.0.1 | 16777343 | この操作を正しく終了しました。 |
192.168.0.1 | 16820416 | この操作を正しく終了しました。 |
dum.168.0.1 | -1 | この操作を正しく終了しました。 |
結果は上のようになった。しかも、ソケットオープンしてない状態で。でもあたりまえか。 単なる計算だ。逆につながってた状態で、誰に何を聞くのか考えるほうが難しい。
それから興味深いのは、最後の嘘IPアドレスに対して、-1が返り、エラーなしの点だ。 これは、次の定数宣言に関係しているということだ。
const {$EXTERNALSYM INADDR_ANY} INADDR_ANY = $00000000; //誰でもOK {$EXTERNALSYM INADDR_LOOPBACK} INADDR_LOOPBACK = $7F000001; //ループバック(自分自身) {$EXTERNALSYM INADDR_BROADCAST} INADDR_BROADCAST = -1; //ブロードキャスト {$EXTERNALSYM INADDR_NONE} INADDR_NONE = -1; //ありえないアドレス2つの-1があるが、間違いなくINADDR_NONEだ。 でも、-1 = 255.255.255.255をあらわすらしいので、確かにエラーではない。奥深い。
INADDR_LOOPBACK = $7F000001というのがあるが、これって、127.0.0.1を あらわしているはず。ちょっと、電卓で計算してみよう。 16進モードにして、貼り付けて、10進モードに変換したら、ぬ。2130706433になった。 テストでは、16777343だったのに。なんかあるんだろうけど不明なので保留する。
inet_ntoa | TOP |
当然、inet_addrの逆、つまり、整数表示のIPアドレスを、文字列表示に変換する関数もある。
{$EXTERNALSYM inet_ntoa} function inet_ntoa(inaddr: TInAddr): PChar; stdcall;TInAddrについては、さっき勉強したので速攻で、テストしてみよう。こんな感じで。 と思ったら、コンパイル通らん。
ShowMessage(inet_ntoa(inet_addr('127.0.0.1')));よく見てみると、inet_addr()は、u_long を返す。inet_ntoa()は、TInAddrを求める。 確かに違う。ううむ。キャストしてみよう。
ShowMessage(inet_ntoa(TInAddr(inet_addr('127.0.0.1'))));あ、うまくいった。でも、こんなキャストしていいのか。 TInAddr.S_addr: u_longだけども、TInAddr≠u_longじゃないのか。 構造体のメンバが、u_long 型1個(ホントは共用体)とはいえ。
ま。いっか。 ということで、
inet_ntoaテスト | TOP |
procedure TForm1.Button4Click(Sender: TObject); begin ShowMessage(inet_ntoa(TInAddr(inet_addr('127.0.0.1')))); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage(inet_ntoa(TInAddr(inet_addr('192.168.0.1')))); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage(inet_ntoa(TInAddr(inet_addr('dum.168.0.1')))); ShowMessage(SysErrorMessage(WSAGetLastError)); end;
inet_ntoa(TInAddr(inet_addr('127.0.0.1'))) | 127.0.0.1 |
inet_ntoa(TInAddr(inet_addr('192.168.0.1'))) | 192.168.0.1 |
inet_ntoa(TInAddr(inet_addr('dum.168.0.1'))) | 255.255.255.255 |
ちゃんといけてる。ソケットオープンは相変わらず不要。 それに、嘘っぱちIPアドレスも確かに、255.255.255.255と解釈されている。 賢いのー。
PHostEnt | TOP |
いうたら、ここまでは単なる計算だ。もうちょっとよい感じの関数を使おう。
さて、IPアドレスからそのコンピュータの情報を取得する関数がある。 それが、gethostbyaddrで、例によって、WinSock.pasでは次のように。
{$EXTERNALSYM gethostbyaddr} function gethostbyaddr(addr: Pointer; len, Struct: Integer): PHostEnt; stdcall;
それから、似たような感じだが、コンピュータ名(ホスト名)から、そのコンピュータの情報を取得する関数もある。 こっちは、gethostbynameという。
{$EXTERNALSYM gethostbyname} function gethostbyname(name: PChar): PHostEnt; stdcall;
とりあえず、この2つの関数を使うことを目的にする。
で、気づくのが、両方とも、PHostEntという型のデータを戻り値としている。 このPHostEntは、こんなもの。
type PHostEnt = ^THostEnt; {$EXTERNALSYM hostent} hostent = record h_name: PChar; h_aliases: ^PChar; h_addrtype: Smallint; h_length: Smallint; case Byte of 0: (h_addr_list: ^PChar); 1: (h_addr: ^PChar) end; THostEnt = hostent;
説明のために、var HostEntry:PHostEntを宣言したことにする。
わかるというかパクれるところを説明する。
function HostAliases(HostEntry: PHostEnt): string; type PPChar=^PChar; var ppc: PPChar; begin Result := ''; ppc := PPChar(HostEntry^.h_aliases); while assigned(ppc^) do begin // ppc は、PPChar = ^PChar なので、ppc^ = PChar になる。 Result := Result + ',' + ppc^; inc(ppc); end; //1文字目が、絶対に','になってるのでごみ掃除 Result := Copy(Result, 2, MaxInt); end;
PInAddr(HostEntry^.h_addr_list^)^こういう解釈になるんだろうか。
gethostbyaddr | TOP |
やっと準備が終わった。いよいよ使ってみよう。
{$EXTERNALSYM gethostbyaddr} function gethostbyaddr(addr: Pointer; len, Struct: Integer): PHostEnt; stdcall;
を入れるそうだが、最初のaddrはあれとして、残りの2つは何だろう。
とりあえず、AF_INETは定数値なので、WinSock.pasに見つかった。
{$EXTERNALSYM AF_INET} AF_INET = 2; { internetwork: UDP, TCP, etc. }まあ、そういうもんだということにした。
gethostbyaddrテスト | TOP |
2002/11/30
こんな感じで。
procedure TForm1.Button5Click(Sender: TObject); var HostEntry: PHostEnt; ip: u_long; begin ip := inet_addr('127.0.0.1'); HostEntry := gethostbyaddr(@ip, 4, AF_INET); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_name:' + HostEntry^.h_name); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_aliases:' + HostAliases(HostEntry)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_addrtype:' + IntToStr(HostEntry^.h_addrtype)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_length:' + IntToStr(HostEntry^.h_length)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_addr_list:' + inet_ntoa(PInAddr(HostEntry^.h_addr_list^)^)); ShowMessage(SysErrorMessage(WSAGetLastError)); end;
結果はこんな感じ。
127.0.0.1 | ||
h_name | sotec-m360 | このコンピュータの名前 |
h_aliases | ''(空文字列) | 別名なんてつけ方知らないので |
h_addrtype | 2 | おそらくAF-INET |
h_length | 4 | アドレスの長さ(関数に渡した) |
h_addr_list | 127.0.0.1 | 関数に渡した |
大体予想通り。他のIPアドレスも試してみよう。
IP | h_name | h_aliases | h_addrtype | h_length | h_addr_list |
---|---|---|---|---|---|
0.0.0.0 | sotec-m360 | '' | 2 | 4 | AAA.AAA.AAA.AAA |
BBB.BBB.BBB.BBB | sotec-m360 | '' | 2 | 4 | AAA.AAA.AAA.AAA |
127.0.0.0 | localnet | '' | 2 | 4 | 127.0.0.0 |
192.168.0.0 | エラー1 | - | - | - | - |
255.255.255.255 | エラー1 | - | - | - | - |
CCC.CCC.CCC.CCC | CCC.dion.ne.jp | '' | 2 | 4 | CCC.CCC.CCC.CCC |
2002/12/01
ふと気づく。 h_addr_listと名乗っておるくせに、たった1つしか IPアドレスを返さないわけがない。 けども、参考文献(こけるWired)にはそれっぽいことが書いてないのでそんなもんかと 思っていたが。 たまたまダウンロードした、WinSock系のOCXの説明書の中の親切説明に、 h_aliases、h_addr_listの構造がともに、似たようなもんだと書いてあるのを発見。 上の方に書いた、HostAliasesを参考にして、HostAddressListを作成 してみよう。
function HostAddressList(HostEntry: PHostEnt): string; type PPChar=^PChar; var ppc: PPChar; begin Result := ''; ppc := PPChar(HostEntry^.h_addr_list); while assigned(ppc^) do begin // ppc は、PPChar = ^PChar なので、ppc^ = PChar になる。 Result := Result + ',' + inet_ntoa(PInAddr(ppc^)^); inc(ppc); end; //1文字目が、絶対に','になってるのでごみ掃除 Result := Copy(Result, 2, MaxInt); end;
で、これを使って、昨日のButton5Clickを書き換えてテストしてみる。
procedure TForm1.Button5Click(Sender: TObject); var HostEntry: PHostEnt; ip: u_long; begin ip := inet_addr('0.0.0.0'); HostEntry := gethostbyaddr(@ip, 4, AF_INET); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_name:' + HostEntry^.h_name); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_aliases:' + HostAliases(HostEntry)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_addrtype:' + IntToStr(HostEntry^.h_addrtype)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_length:' + IntToStr(HostEntry^.h_length)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_addr_list:' + HostAddressList(HostEntry)); ShowMessage(SysErrorMessage(WSAGetLastError)); end;
結果はこんな感じ。
0.0.0.0 | ||
h_name | sotec-m360 | このコンピュータの名前 |
h_aliases | ''(空文字列) | 別名なんてつけ方知らないので |
h_addrtype | 2 | おそらくAF-INET |
h_length | 4 | アドレスの長さ(関数に渡した) |
h_addr_list | PPPoE用,LANカード用 | ちゃんと2種類!! |
よしよし。こんな感じでよさそう。エラーをあまり試せなかったけど、 思いつかんし、次にいこう。
gethostbyname | TOP |
今度は、IPアドレスではなく、ホスト名からコンピュータの情報を取得するための関数だ。
{$EXTERNALSYM gethostbyname} function gethostbyname(name: PChar): PHostEnt; stdcall;今度は引数がとっても簡単。
gethostbynameテスト | TOP |
こんな感じで、beginの次行のPChar()をいろいろ変化させてみよう。
procedure TForm1.Button6Click(Sender: TObject); var HostEntry: PHostEnt; begin HostEntry := gethostbyname(PChar('localhost')); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_name:' + HostEntry^.h_name); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_aliases:' + HostAliases(HostEntry)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_addrtype:' + IntToStr(HostEntry^.h_addrtype)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_length:' + IntToStr(HostEntry^.h_length)); ShowMessage(SysErrorMessage(WSAGetLastError)); ShowMessage('h_addr_list:' + HostAddressList(HostEntry)); ShowMessage(SysErrorMessage(WSAGetLastError)); end;
結果はこんな感じに。
ホスト名 | h_name | h_aliases | h_addrtype | h_length | h_addr_list |
---|---|---|---|---|---|
localhost | sotec-m360 | '' | 2 | 4 | 127.0.0.1 |
sotec-m360 | sotec-m360 | '' | 2 | 4 | PPPoE用,LANカード用 |
www.microsoft.co.jp | reroute.microsoft.com | www.microsoft.co.jp | 2 | 4 | 207.46.248.234 |
www.google.co.jp | www.google.com | www.google.co.jp | 2 | 4 | 216.239.33.101 |
www.samurai.com ※注1 | home.samurai.com | www.samurai.com | 2 | 4 | 205.207.28.66 |
www.okusama.com ※注1 | www.okusama.com | - | 2 | 4 | 207.82.103.197 |
www.harapeko.com ※注2 | そのようなホストは不明です。 |
こんなところで、このページを終わることにする。 ただし、タイトルのWinSockの基本関数が何かという定義づけもしていないし、 基本関数が2個だけということもありえないが、そこそこ長くなったので、別のページに切り替える ことにする。ただ、しばらくは、WinSockの基本関数をタイトルにすることになる。
ここまでで、勉強した大体の関数や構造体など。
エラーコード | TOP |
また、gethostbynameとgethostbyaddrが返すエラーコードとして、 こんな感じで宣言されている。(それなりに訳してみた。辞書なしで。)
{ resolverを使っているときに、gethostbyname() と gethostbyaddr() から返される エラーコード。これらのエラーは、WSAGetLastError() を通して、retrievedされる ことに注意すること。なので、特殊な実装や language run-time systems からのエラーコードによるクラッシュを避けるためのルールに従わなければならない ことに注意すること。 この理由により、エラーコードは、WSABASEERR+1001 を基準にしている。 また、[WSA]NO_ADDRESSが、互換性の目的のためだけに定義されていることも注意する こと。}
{ Authoritativeなエラーコード: Host not found } {$EXTERNALSYM WSAHOST_NOT_FOUND} WSAHOST_NOT_FOUND = (WSABASEERR+1001); {$EXTERNALSYM HOST_NOT_FOUND} HOST_NOT_FOUND = WSAHOST_NOT_FOUND; { Authoritativeでないエラーコード: Host not found, or SERVERFAIL } {$EXTERNALSYM WSATRY_AGAIN} WSATRY_AGAIN = (WSABASEERR+1002); {$EXTERNALSYM TRY_AGAIN} TRY_AGAIN = WSATRY_AGAIN; { どうしようもないエラー, FORMERR, REFUSED, NOTIMP } {$EXTERNALSYM WSANO_RECOVERY} WSANO_RECOVERY = (WSABASEERR+1003); {$EXTERNALSYM NO_RECOVERY} NO_RECOVERY = WSANO_RECOVERY; { Valid name, no data record of requested type } {$EXTERNALSYM WSANO_DATA} WSANO_DATA = (WSABASEERR+1004); {$EXTERNALSYM NO_DATA} NO_DATA = WSANO_DATA; { no address, look for MX record } {$EXTERNALSYM WSANO_ADDRESS} WSANO_ADDRESS = WSANO_DATA; {$EXTERNALSYM NO_ADDRESS} NO_ADDRESS = WSANO_ADDRESS;それと、基準にしているというWSABASEERRについては、これ。
{ すべてのWindowsのソケットエラーコード定数は、"normal"から、WSABASEERR によって バイアスされている。} {$EXTERNALSYM WSABASEERR} WSABASEERR = 10000;
EOF | TOP |