[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 |