EternalWindows
Winsock / ソケットとネットワーク

Winsockでネットワーク通信を行うためには、何よりもまずソケットを作成しなければなりません。 ソケットは通信の端点であり、この端点を通じてデータを送信したり受信したりします。 ソケットは、socketで作成することになります。

SOCKET WSAAPI socket(
  int af,
  int type,
  int protocol
);

afは、アドレスファミリを指定します。 typeは、ソケットの種類を指定します。 これは、afに指定した値によって異なります。 protocolは、使用するプロトコルを指定します。 これは、afに指定した値によって異なります。 戻り値は、ソケットを識別する記述子が返ります。 関数が失敗した場合は、INVALID_SOCKETが返ります。

不要になったソケットは、closesocketで開放することになります。

int closesocket(
  SOCKET s
);

sは、ソケットの記述子を指定します。

Winsockの1つの特徴は、プロトコルに依存しない通信を提供しているところです。 この点は非常に重要であるため、具体的な話を交えて説明します。 まず、一般的にいわれる通信というのは、私達が普段から行っているインターネットを通じた通信です。 これは、IPアドレスによって通信相手を識別し、 様々なルータやコンピュータを経由することによって、 目的の相手にデータ(パケット)を届ける方法です。 当然ながら、このようなIPレベルの通信はWinsockでサポートされていますが、 Winsockではこのようなインターネットを利用しない通信もサポートしています。 たとえば、赤外線通信について規格するIrDAや、無線通信について規格するBluetoothなどがあります。 つまり、Winsockはこのような通信を行う場合でも利用されることになります。 そして、このような規格を識別する値がアドレスファミリです。 たとえば、インターネットを使用した通信を行う場合はsocketの第1引数にAF_INET(またはAF_INET6)を指定しますが、 IrDAによる通信を行う場合はAF_IRDA、Bluetoothによる通信を行う場合はAF_BTHを指定します。 本章では、AF_INET(またはAF_INET6)を使用した通信について取り上げていますが、 一部の関数についてはIrDAやBluetoothによる通信の際にも呼び出すことになります。

インターネットを使用した通信には、HTTPやTCP、IPなど、非常に様々なプロトコルが利用されています。 これらプロトコルの詳細を理解することは非常に大変ですが、 ネットワーク上にデータが送信されるまでの流れを把握しておけば、 各種プロトコルの位置づけというものが見えてきます。 次に、データを送信する例を示します。

send(soc, "sample", ...);

このsendというWinsock関数は、第2引数に指定されたデータを第1引数のソケットと接続されたソケットに送信します。 つまり、上記コードを実行すると、通信相手にsapmleというデータが届くことになります。 ただし、実際にネットワーク上に送信されるデータは、このsampleというデータ単一ではありません。 単純に考えて、これだけではどのホストへデータを送信すればよいか分からないからです。 それではなぜ、上記コードではsampleというデータを送信することができるのでしょうか。 それは、渡されたデータに対して、システムが内部で通信に必要なデータを追加しているからです。 この追加するデータのフォーマットが独自のものであっては、 受信相手がこれを理解することができなくなりますから、 既存のプロトコル(仕様)に基づいてフォーマットされることになっています。 たとえば、AF_INET(またはAF_INET6)による通信の場合は、必ずデータにIPヘッダが含まれることになっています。 IPヘッダとは、IPプロトコルによって定義されたフォーマットに基づいたデータであり、 IPアドレスなどの情報を含んでいます。 システムが通信に必要なデータを追加してくれるおかげで、 アプリケーションは自身が送信したいデータのみを関数に指定できるようになり、 IPプロトコルの詳細が隠蔽されることになります。

データの送信に必要なのは、IPアドレスだけではありません。 確かにIPアドレスが分かっていれば目的のホストまでデータは届きますが、 そのデータをどのアプリケーション(正確にはソケット)が受信すればよいかまでは分かりません。 よって、アプリケーションを識別するためのポート番号と呼ばれるデータが必要になります。 ポート番号はTCPヘッダまたはUDPヘッダという形でデータに追加され、 どちらのヘッダが追加されるかどうかは、socketの第3引数によって変化します。

socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

socketの第3引数にIPPROTO_TCPを指定した場合、TCPプロトコルを使用することになります。 つまり、このプロトコルのフォーマットに従ったTCPヘッダが、データの送信時に追加されます。 IPPROTO_TCPを指定する場合は、第2引数がSOCK_STREAMになります。 上記コードで作成したソケットによる通信では、 データにTCPヘッダとIPヘッダが追加されて送信されるため、 TCP/IP通信ということになります。

socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

この場合は、第3引数にIPPROTO_UDPを指定しているので、UDPプロトコルを使用することになります。 つまり、このプロトコルのフォーマットに従ったUDPヘッダが、データの送信時に追加されます。 IPPROTO_UDPを指定する場合は、第2引数がSOCK_DGRAMになります。 上記コードで作成したソケットによる通信では、 データにUDPヘッダとIPヘッダが追加されて送信されるため、 UDP/IP通信ということになります。

TCPとUDPの違いを理解するためには、各ヘッダにどのような情報が格納されているかに注目します。 まず、UDPヘッダに格納される情報は、先に述べたポート番号とデータの長さぐらいです。 既に述べたように、IPアドレスとポート番号があればデータを送信できるようになりますから、 このヘッダとIPヘッダを組み合わせれば、データは通信相手に届くことになります。 ただし、ネットワーク上のデータは、ネットワークトラフィックの交雑などよって破棄されることもあり、 実際に受信側に届いた際にはデータの一部が失われていることが起こりえます。 そのため、UDPを利用した通信では、データの信頼性に欠けるという問題があります。 しかし、TCPヘッダにはUDPヘッダの情報に加えて、データをチェックする情報が含まれているため、 たとえデータが途中で失われたとしても、再送処理を行うなどしてデータの信頼性を維持することができます。 よって、TCPとUDPの最大の違いは、データ及び通信の信頼性ということになります。 よくTCPとUDPは比較対象として取り上げられますが、あまり両者を別物として捉えないほうがよいでしょう。 TCPはUDPよりヘッダの情報量が多く、それ故にデータの信頼性を維持できるという解釈で十分と思われます。

これまでの話から分かるように、ネットワーク上へのデータの送信は、いくつかの段階を踏むことになります。 たとえば、アプリケーションがデータを用意し、それを受け取ったシステムがTCPまたはUDPヘッダを追加、 そして最後にIPヘッダを追加するという具合です。 こうしたプロトコル別の処理を階層として表現した場合、 誰がどのような処理を行えばよいかが、より明確になります。

プロトコル プロトコルの実装者
HTTP, FTP, SMTP
(アプリケーション層)
アプリケーション(ネットワークAPIによってはシステム)
TCP, UDP
(トランスポート層)
システム
IP, IPSec
(インターネット層)
システム

この階層はOSI参照モデルと呼ばれるものであり、 実際にはまだ下に層があるのですが、ここでは深く取り上げません。 単純に、作業分担の構図と考えてください。 たとえば、アプリケーションがwebサーバーからHTMLファイルを取得したい場合は、 HTTPプロトコルでフォーマットされたデータをWinsockのsend関数に指定しますから、 HTTPプロトコルの実装者はアプリケーションということになります。 こうした作業分担によって生じる利点は、上位プロトコルが下位プロトコルの詳細を知らずに済むと同時に、 下位プロトコルが上位プロトコルの詳細を知らずに済むという点にあります。 事実、アプリケーションはTCPやIPのヘッダについて理解しておく必要はありませんし、 システムからしても、受け取ったデータがどのようなプロトコルでフォーマットされているかを意識する必要はありません。 単純に、TCPヘッダやIPヘッダを追加すればよいだけであるからです。

上記の作業分担について、さらに別の見方をすれば、下位プロトコルは上位プロトコルで必要な共通処理を集約しているともいえます。 アプリケーションがどのようなプロトコルを使用するにしても、 最終的にはポート番号やIPアドレスが必要になるわけであり、 上位プロトコルがこれらを含んだフォーマットになっていたとしたら、 上位プロトコルのフォーマットは非常に複雑なものになります。 しかし、どのプロトコルでも必要な情報を下位のプロトコルで実装すれば、 全ての上位プロトコルは間接的にそれを利用することができます。 この点を踏まえた場合、パケットモニタのようなツールがまず理解すべきプロトコルは、 TCPやIPといったプロトコルです。 HTTPについて理解しても、それはHTTPデータを受信した場合にしか意味を持ちませんが、 TCPやIPについて理解しておけば、受信したデータがどのようなアプリケーションプロトコルで実装されていても、 そのデータに追加されているヘッダからポート番号やIPアドレスを検出できることになります。

RAWソケットについて

既に述べたように、WinsockはTCPヘッダやIPヘッダの存在をアプリケーションから隠蔽していますが、 決してこれらの情報を取得することができないわけではありません。 RAWソケットと呼ばれるソケットを作成すれば、 ヘッダ情報を含んだデータを送受信することができます。

socket(AF_INET, SOCK_RAW, IPPROTO_IP);

プロセスが管理者として動作している場合は、socketの第2引数にSOCK_RAWを指定してRAWソケットを作成することができます。 このソケットを使用してデータを受信するrecvという関数を呼び出した場合、 受信したデータにはTCPヘッダやIPヘッダが含まれることになるため、 それらのヘッダを解析することで、相手のポート番号やIPアドレスを取得できるようになります。 この方法は、パケットモニタのようなアプリケーションでよく利用されています。



戻る