非同期ソケット通信、簡易チャットプログラム -003-
前へ TOPへ 次へ

ストリームソケットの通信手順(TCPプロトコル)と関数の説明

TCP/IPの概要

ストリームソケットの通信手順は以下の図の通りです。 この手順はUNIXでもWindowsでも一緒です。また、ここで用いられる各関数はUNIXの場合socket.h、Windowsの場合winsock.hで型宣言されており、引数や戻り値も共通となっています。

ではまず,ストリームソケット(TCP/IP)のイメージを述べておきます。ストリームソケットは電話式コミュニケーションと覚えておくと便利です。 つまり電話になぞらえて設計した通信方式です。 通常,電話でのコミュニケーションに必要なのは電話回線に繋がれている受話器というハードウェアが2つ(自分の分と相手の分)と,電話局に登録されている受話器の識別子つまり電話番号というソフトウェア,ならびに電話をかける側がその電話番号を 知っているということが必要です。 ストリームソケットではこの手順を真似た形で事が運びます(下図)。

つまり,クライアントを電話をかける側,サーバを電話をとる側とみなし,

  1. 受話器の取得(サーバ側・クライアント側共にsocket関数の呼び出し)
  2. 受話器の登録(サーバ側のbind関数呼び出し
  3. 回線数の指定(サーバ側のlisten関数呼び出し)
  4. 受話器が鳴るのを待ちうけ(サーバ側のaccept関数呼び出し)
  5. 電話をかける(クライアント側のconnect関数呼び出し)

という手順で回線が接続されます。また回線が接続された状態では

  1. 話す(send関数の呼び出し)

  1. 聞く(recv関数の呼び出し)を交互に行って「会話」を行います。そして「会話」の終了時にはどちらからでも
  2. 受話器を切る(close関数の呼び出し)

ことによって回線を解放します。

ただし,実際の電話でのやりとりを考えてみれば分かるように、通常クライアント側で「聞く」ときにはサーバ側で「話し」、クライアント側で「話す」ときにはサーバ側では「聞く」状態になっている必要があります。

  1. サーバ(聞き手) ← クライアント(話し手)
  2. サーバ(話し手) → クライアント(聞き手)
  3. サーバ(聞き手) ← クライアント(話し手)
  4. ・・・・

このため,どちらが先に「聞き手」になり、もう片方が「話し手」になるかを予め決めておきます。 このような設計の方法を同期通信と呼びます。 これに対し、好きなときに「話し手」になったり「聞き手」にまわったりができるような設計の方法を非同期通信と呼びます。 非同期通信については後で述べます。

同期通信の手順

TCP/IPの接続手順

ではTCP/IPを使用する場合、具体的に何が「受話器」で何が「受話器の登録」なのか?そしてどのような手順で通信路の接続がなされるのかを説明していきます。

  1. まず、「受話器」に例えたソケットとは、OSが持っている資源の一部で、OSによって識別子(名前)が付けられています。 具体的にはSOCKET型(int型と等価)の整数で呼ばれています。 socket関数の戻り値がこの識別子となります。 socket関数の呼び出しが失敗に終わった場合、ソケットが取得できないことを表すため、戻り値はINVALID_SOCKETとなります。
  2. 次に「受話器の登録」とは、このソケットとローカルアドレスを結合(bind)することを意味します。 つまり単なる受話器に「電話番号」が付くわけです。 このため、「受話器の登録」にはbind関数が使われるのです。 従ってbind関数に必要なのは「受話器」、つまりソケットの識別子と、サーバ側のインターネットアドレスつまりIP(InternetProtocolの略)アドレス、それからポート番号です。 ポート番号とは個別のサーバ・クライアントペアの間で共通にしておくチャンネル番号と考えて下さい。 皆さんがよく知っているhttpやftp、rloginなんかも独自のポート番号を持っています(よく使われるアプリケーションプロトコルとポート番号の対応表についてはここを参照,またそれぞれのプロトコルの詳細な説明はここを参照)。 IPアドレスで決まる一つのホストの中には複数のプロトコルにおけるサーバが待ち構えているのでチャンネル番号で交通整理しないと混線しますよね。
  3. 「回線数の指定」とは、このサーバで最大何個のクライアントから同時に電話を受信できるか、を指定することを表します。 ソフトウェアの設計から言えば、待ち受けキューの数を決めています。 授業の範囲を超えるのでここでは説明しませんが、telnetdやhttpd、smpdやpopdなどの、いわゆる「XXデーモンプログラム」は複数のクライアントからの同時接続を許しています。 これを可能にしているのが複数キューの指定です。 これはlisten関数で行います。
  4. 接続待ちが可能になったサーバでは、早速相手からのcallを待ち受けます。 それがaccept関数です。 accept(受理)というといかにも電話を取った感じに聞こえますが、実際にはこの関数の中で相手からのcallを待ち受けます。 つまり、callがあるまでブロックされるわけです。 相手からのcallがあって初めてこのブロックから解放され、制御がaccept関数から戻ってくるのです。
  5. クライアント側では、一旦「受話器」を取得してしまえばあとは「電話をかける」だけです。 「電話をかける」、つまり相手をcallするには、connect関数を使います。 connect関数の引数として必要な情報は相手の「電話番号」つまり、サーバのIPアドレスと、待ち受けポート番号です。 ただし、ここでもサーバ側と同様な注意が必要です。 callしても相手が受話器をとってくれない限り先には進めないので、connect関数は相手サーバがacceptしてくれない限りブロック(待ち状態になる)されてしまうということです。
  6. 電話で「話す」ということは、ソケットに書込み(send関数またはwrite関数を使う)をすることです。
  7. 電話からの声を「聞く」ということは、ソケットからの読み込み(recv関数またはread関数を使う)をすることです。
  8. そして、会話が終わったら速やかに受話器を返納する必要があります。これがclose関数(Windowsではclosesocket関数)です。

ここで、connect関数(電話をかける)とaccept関数(電話が鳴るのを待つ)は上記の④と⑤のように、クライアント(かける側)とサーバ(うける側)で独立に発生します。 しかしながら回線が正常に接続される(電話が繋がる)と同時に、両者は同時にブロッキング状態から解放されるわけです。 このあたりは「電話が通じる」プロセスとそっくりですよね。

それでは、上の説明で出てきた関数を紹介します。

ソケット作成

ネットワーク通信を行うためにまず、サーバ側及びクライアント側でソケットを作成する必要があります。 ソケットの作成には以下のsocket()関数を使用します。関数が正常に終了すると戻り値にソケットディスクリプタ(SOCKET型)が返ってきます。 このソケットディスクリプタはデータ送受信の際に必要となりますので保持しておかなければなりません。

ソケットを閉じる

ソケット通信を終了するときにはclosesocket()関数を使用します。 使い終わったソケットは必ず閉じるよう心がけましょう。

ソケットのバインド

サーバ側ではソケットを生成後、そのソケットにbind()関数を使用してローカルアドレスとポートを割り当てておく必要があります。 クライアント側では特にアドレスやポートを結び付ける必要がありませんが、bind()関数を呼んでも特に問題はありません。

ソケットの接続待ち

サーバ側は相手から接続されるのを待っておく必要があります。 ソケットをバインドした後、listen()関数でソケットの接続待ちキュー数(回線数)を指定します。 これにより接続待ち状態になります。

接続の受け入れ

接続を確立してデータ通信を行うために、サーバ側では相手から接続要求がきた場合にaccept()関数で接続を受け入れなければなりません。

サーバへの接続

クライアント側はソケットを生成後、サーバと通信を行うためにconnect()関数でサーバに接続し、コネクションを確立する必要があります。

データの送信

コネクションが確立した相手に対しデータを送信する場合、send()関数を使用します。

データの受信

コネクションが確立した相手からデータを受け取る場合、recv()関数を使用します。


前へ TOPへ 次へ