[BlueLeaf1336]> PROGRAM> WinSock勉強会>

TSocket -- その1

historyTOP

2002/12/04:作成

ソケットTOP

いよいよソケットを作る。で、ソケットとは何か?何だろう。 「こける」では、通信するための電話とある。媒介するもの。 音に対する空気にあたるものととらえてよいのだろうか。 光に対するエーテル(嘘)という感じだろうか。 とりあえず、問題が出てくるまでこの認識でいこう。

まず、ソケットを作る関数はこれ。

{$EXTERNALSYM socket}
function socket(af, Struct, protocol: Integer): TSocket; stdcall;
af: Integer
アドレスファミリー
Struct: Integer
ストリームタイプ(TCP)かユーザダイアグラム(UDP)と思う。
protocol: Integer
プロトコル
戻り値: TSocket
TSocket構造体

まず、アドレスファミリーとは何か。WinSock.pasを開けてみよう。

...どっさり出てきた。

{ Address families. }
  {$EXTERNALSYM AF_UNSPEC}
  AF_UNSPEC       = 0;         { unspecified }
  {$EXTERNALSYM AF_UNIX}
  AF_UNIX         = 1;         { local to host (pipes, portals) }
  {$EXTERNALSYM AF_INET}
  AF_INET         = 2;         { internetwork: UDP, TCP, etc. }
  {$EXTERNALSYM AF_IMPLINK}
  AF_IMPLINK      = 3;         { arpanet imp addresses }
  {$EXTERNALSYM AF_PUP}
  AF_PUP          = 4;         { pup protocols: e.g. BSP }
  {$EXTERNALSYM AF_CHAOS}
  AF_CHAOS        = 5;         { mit CHAOS protocols }
  {$EXTERNALSYM AF_IPX}
  AF_IPX          = 6;         { IPX and SPX }
  {$EXTERNALSYM AF_NS}
  AF_NS           = 6;         { XEROX NS protocols }
  {$EXTERNALSYM AF_ISO}
  AF_ISO          = 7;         { ISO protocols }
  {$EXTERNALSYM AF_OSI}
  AF_OSI          = AF_ISO;    { OSI is ISO }
  {$EXTERNALSYM AF_ECMA}
  AF_ECMA         = 8;         { european computer manufacturers }
  {$EXTERNALSYM AF_DATAKIT}
  AF_DATAKIT      = 9;         { datakit protocols }
  {$EXTERNALSYM AF_CCITT}
  AF_CCITT        = 10;        { CCITT protocols, X.25 etc }
  {$EXTERNALSYM AF_SNA}
  AF_SNA          = 11;        { IBM SNA }
  {$EXTERNALSYM AF_DECnet}
  AF_DECnet       = 12;        { DECnet }
  {$EXTERNALSYM AF_DLI}
  AF_DLI          = 13;        { Direct data link interface }
  {$EXTERNALSYM AF_LAT}
  AF_LAT          = 14;        { LAT }
  {$EXTERNALSYM AF_HYLINK}
  AF_HYLINK       = 15;        { NSC Hyperchannel }
  {$EXTERNALSYM AF_APPLETALK}
  AF_APPLETALK    = 16;        { AppleTalk }
  {$EXTERNALSYM AF_NETBIOS}
  AF_NETBIOS      = 17;        { NetBios-style addresses }
  {$EXTERNALSYM AF_VOICEVIEW}
  AF_VOICEVIEW    = 18;        { VoiceView }
  {$EXTERNALSYM AF_FIREFOX}
  AF_FIREFOX      = 19;        { FireFox }
  {$EXTERNALSYM AF_UNKNOWN1}
  AF_UNKNOWN1     = 20;        { Somebody is using this! }
  {$EXTERNALSYM AF_BAN}
  AF_BAN          = 21;        { Banyan }
  {$EXTERNALSYM AF_MAX}
  AF_MAX          = 22;

AF_INETは一回使ったことがある。 gethostbyaddr(@ip, 4, AF_INET); ただし、このときは、Structという引数名に代入される値だった。 しかし今回は、Structが別にある。それはそれとして、今回も、これを使うらしい。

で、Structだが、「こける」では、TCP/IPの場合は、SOCK_STREAM を入れると書いてある。

ということは、ここには、下の5つのうちのどれかが入るようになっているわけだ。

{ Types }
  {$EXTERNALSYM SOCK_STREAM}
  SOCK_STREAM     = 1;               { stream socket }
  {$EXTERNALSYM SOCK_DGRAM}
  SOCK_DGRAM      = 2;               { datagram socket }
  {$EXTERNALSYM SOCK_RAW}
  SOCK_RAW        = 3;               { raw-protocol interface }
  {$EXTERNALSYM SOCK_RDM}
  SOCK_RDM        = 4;               { reliably-delivered message }
  {$EXTERNALSYM SOCK_SEQPACKET}
  SOCK_SEQPACKET  = 5;               { sequenced packet stream }

さらに、protocolだが、おそらく、 getprotobynameのところで使った、IPPROTO_**が入ると思われる。ただし、0(=IPPROTO_IP)を指定することで、自動割り当てされるらしい。

そして、最も重要な、TSocketという型で作られたソケットが戻ってくる。 TSocketがどんな内容かを調べてみると、こんなの。

type
{ The new type to be used in all instances which refer to sockets. }
  {$EXTERNALSYM TSocket}
  TSocket = u_int;

早い話、Integerである。単なる整数値。 多分Windowsプログラミングで使うところのハンドルにあたるものだと思う。

ソケット作成に失敗した場合は、

{$EXTERNALSYM INVALID_SOCKET    =}
INVALID_SOCKET    = TSocket(NOT(0));
が返ってくる。

逆に、ソケットを廃棄する場合は、

{$EXTERNALSYM closesocket}
function closesocket(s: TSocket): Integer; stdcall;
s: TSocket
破棄したいソケットそのもの
戻り値: Integer
何が戻ってくるんだろう。

今日のところはこの2つ、作成・廃棄をやってみよう。

ソケットの作成・破棄TOP

こんな感じで。

implementation
var
    Sock: TSocket;

複数のボタンで、1つのソケット変数を操作するために、ユニット変数として ソケットを宣言しておく。

procedure TForm1.Button12Click(Sender: TObject);
begin
    Sock := socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    //if Sock = INVALID_SOCKET then
    //begin
    //    ShowMessage('INVALID_SOCKET');
    //end;
    ShowMessage(SysErrorMessage(WSAGetLastError));
end;

で、Button12Clickが、ソケットの作成。 で、予想通り、WSAStartupを呼び出したあとで、socketを呼び出さなければエラーになる。

WSAStartupより先に、上のコードで走らせると、 「アプリケーションが WSAStartup を呼び出していないか、または WSAStartup が失敗しました。」こんなエラーが表示される。つまり、WSAGetLastErrorが、エラーコードをつかんでくる。

ところが、上記コード中のコメント行を4行とも有効にすると、状況が変わってくる。

  1. INVALID_SOCKET
  2. この操作を正しく終了しました。

の順でメッセージ画表示される。 ここで疑問に思うのは、WSAGetLastErrorを呼び出していないのに、エラーがクリアされているということである。しているのは、変数と定数を比較してメッセージを表示しているだけである。

といいながら今試してわかったことがある。それは、

という点である。こんなことでいいんだろうか。 (※2002/12/05追記:ShowMessageが何らかのAPIを呼び 出しているなら、エラー状態が上書きされている可能性がある。 で、多分そうだろう。ウィンドウをひとつ作ってしかも成功しているわけだから。)

procedure TForm1.Button13Click(Sender: TObject);
begin
    closesocket(Sock);
    ShowMessage(SysErrorMessage(WSAGetLastError));
end;

それから、Button13Clickが、ソケットの破棄。

こっちは余計に何も考えることなく、作ったソケット変数を渡してやればそれでよい。表示されるエラーは、次の2つだけを確認できた。

1つ目のほうは、socket関数と同じ(つまりいっつもと同じ)エラーだが、2つ目のほうはちょっと違う。こんな感じ。

「ソケット以外のものに対して操作を実行しようとしました。」

ためしにこんなコードで試してみた。

closesocket(1);

なんとコンパイルは通る。だが、実行時には、エラー(「ソケット以外のものに対して操作を実行しようとしました。」)が表示される。

今日はここまで。

getsocknameTOP

2002/12/05

昨日は、TSocketを作って捨てたので、今回は、TSocketを使う関数を少しずつ見ていこう。とりあえず、getsocknameで。どっかのサイトで拾った説明によると、 「ソケットから自分のマシンのアドレスとポート番号を取得する」ための関数らしい。

{$EXTERNALSYM getsockname}
function getsockname(s: TSocket; var name: TSockAddr;
                     var namelen: Integer): Integer; stdcall;

TSocket以外は、すべてvarなので、ここに値がセットされて戻ってくるような気がする。

s: TSocket;
別のところで作ったソケット。すぐ作れる。
var name: TSockAddr;
まだ使ったことの無い構造体。
var namelen: Integer
多分、TSockAddr構造体のサイズ。
戻り値: Integer
エラーコード?

とりあえず、準備として、TSockAddrについて見る必要がある。

type
  { Structure used by kernel to store most addresses. }
  {$EXTERNALSYM PSOCKADDR}
  PSOCKADDR = ^TSockAddr;
  {$EXTERNALSYM TSockAddr}
  TSockAddr = sockaddr_in;

これによると、TSockAddrは、sockaddr_inだとわかる。 じゃあ、sockaddr_inは?というわけで、これが本体らしい。

type
  PSockAddrIn = ^TSockAddrIn;
  {$EXTERNALSYM sockaddr_in}
  sockaddr_in = record
    case Integer of
      0: (sin_family: u_short;
          sin_port: u_short;
          sin_addr: TInAddr;
          sin_zero: array[0..7] of Char);
      1: (sa_family: u_short;
          sa_data: array[0..13] of Char)
  end;
  TSockAddrIn = sockaddr_in;

「こける」には、0のほうを見ればよいとある。大きさぐらい見ておくと、 sin_portがu_shortなので、1バイト。sin_addrがTInAddrで4バイト。 sin_zeroがarray[0..7] of Charで8バイト。合計13バイト。 それから、sa_dataが、0..13の14バイト。あれ?一緒になるのかと思ったが。

sin_family: u_short;
アドレスファミリー。AF_INET。
sin_port: u_short;
ポート番号。ただし、ネットワークバイトオーダーで。
sin_addr: TInAddr;
IPアドレス。inet_addr('127.0.0.1')かなんかで指定する。
sin_zero: array[0..7] of Char

というわけで、とりあえず、この関数(getsockname)が、ソケットだけを 指定すれば、残りはセットして戻してくるということにして、こんな感じで使ってみる。

procedure TForm1.Button14Click(Sender: TObject);
var
    name: TSockAddr;
    namelen: integer;
begin
    getsockname(Sock, name, namelen);
    ShowMessage(SysErrorMessage(WSAGetLastError));
end;

ん?「無効な引数が提供されました。」ときた。

どれが無効かわからん。 とりあえず、namelenに、SizeOf(TSockAddr)を渡すように変更。 大きさは16らしい。だめ。 TSockAddrをそれなりに埋めて渡すように変更。だめ。 結局こんな感じになったが、まったくおんなじエラーが出つづける。 もちろん、オープン処理・ソケット作成はやってある。 どこが何なんだろう。

var
    name: TSockAddr;
    namelen: integer;
    retvalue: integer;
begin
    FillChar(name, SizeOf(TSockAddr), #0);
    namelen := SizeOf(TSockAddr);
    name.sin_family      := AF_INET;
    name.sin_port        := htons(IPPORT_FTP);
    name.sin_addr.S_addr := inet_addr('127.0.0.1');
    retvalue := getsockname(Sock, name, namelen);
    ShowMessage(SysErrorMessage(WSAGetLastError));
    ShowMessage(IntToStr(retvalue));
end;

時間が無いのでここまで。

(未完)

EOFTOP