TCPを使う

ここでは、Mac OS XでTCPによる通信をするプログラムを書く方法を説明します。

TCPとは

インターネットでの通信の90%以上はTCPによるものだと言われています。 通信を行うプログラムを書く場合、ほとんどがTCPによるものになると思います。

インターネットは信頼性のない通信路です。 信頼性がないインターネットでは、通信中にパケットロス、ビットエラー、順番の入れ替えなどが発生する可能性があります。 TCPは、そのような通信路上の障害を隠蔽してくれます。 具体的には、パケットロスが発生したら再送を行ったり、順番が入れ替わると正しい順番に直したりしています。

TCPによる通信はサーバとクライアントの2者間で行われます。 サーバは通信要求が来るまで待ち続けます。 TCPによる通信は、クライアントがサーバに対して接続要求を出すことから始まります。 サーバが接続要求を受け付けるとクライアントとサーバの間に仮想的な接続(バーチャルサーキット)が出来上がります。

プログラムを書く場合、そのソケットがそのバーチャルサーキットを表します。 ユーザがソケットに対して書き込みを行うと、反対側のソケットにそのデータがそのまま転送されます。 そのため、ソケットの両側のユーザは通信路上でのパケットロスなどの障害を気にすることなく、ソケットに対する書き込みと読み出しを行えば通信が行えてしまいます。

TCPプログラミング

TCPによる通信を行うプログラムの書き方はサーバとクライアントで異なります。 以下に、サーバとクライアントのプログラム作成手順概要を示します。

サーバ
サーバは、クライアントからの接続要求を待ちます。 接続要求を待つには、どのような待ち方をするかを設定しないといけません。 設定する情報としては、例えば、接続待ちをするTCPポート番号があります。 一度接続が出来上がってしまえば、サーバとクライアントで通信方法に違いはありません。
  • ソケットを作る
  • 接続待ちをするIPアドレスとポートを設定する
  • ソケットに名前をつける(bindする)
  • 接続待ちする
  • クライアントからの接続を受け付ける
  • 通信を行う
クライアント
クライアントは、特定のIPアドレス+TCPポート番号で接続待ちをしているサーバに対して接続要求を出します。 接続が成功すると、通信を開始できます。 一度接続が出来上がってしまえば、サーバとクライアントで通信方法に違いはありません。
  • ソケットを作る
  • 接続相手を設定する
  • 接続する
  • 通信を行う

単純なTCPサーバ

Mac OS Xでの簡単なサーバのサンプルを以下に示します。 コードを簡単にするため、エラー処理は省いてあります。 実際にコードを書く場合にはエラー処理も行ったコードにして下さい。


#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int
main()
{
 int sock0;
 struct sockaddr_in addr;
 struct sockaddr_in client;
 socklen_t len;
 int sock;

 /* ソケットの作成 */
 sock0 = socket(AF_INET, SOCK_STREAM, 0);

 /* ソケットの設定 */
 addr.sin_family = AF_INET;
 addr.sin_port = htons(12345);
 addr.sin_addr.s_addr = INADDR_ANY;
 addr.sin_len = sizeof(addr);

 bind(sock0, (struct sockaddr *)&addr, sizeof(addr));

 /* TCPクライアントからの接続要求を待てる状態にする */
 listen(sock0, 5);

 /* TCPクライアントからの接続要求を受け付ける */
 len = sizeof(client);
 sock = accept(sock0, (struct sockaddr *)&client, &len);

 /* 5文字送信 */
 write(sock, "HELLO", 5);

 /* TCPセッションの終了 */
 close(sock);

 /* listen するsocketの終了 */
 close(sock0);

 return 0;
}

上記TCPサーバは、接続してきたクライアントに対して「HELLO」という文字列を送信して終了します。

単純なTCPクライアント

簡単なクライアントのサンプルを以下に示します。 コードを簡単にするため、エラー処理は省いてあります。 実際にコードを書く場合にはエラー処理も行ったコードにして下さい。


#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int
main()
{
 struct sockaddr_in server;
 int sock;
 char buf[32];
 int n;

 /* ソケットの作成 */
 sock = socket(AF_INET, SOCK_STREAM, 0);

 /* 接続先指定用構造体の準備 */
 server.sin_family = AF_INET;
 server.sin_port = htons(12345);
 server.sin_addr.s_addr = inet_addr("127.0.0.1");

 /* サーバに接続 */
 connect(sock, (struct sockaddr *)&server, sizeof(server));

 /* サーバからデータを受信 */
 memset(buf, 0, sizeof(buf));
 n = read(sock, buf, sizeof(buf));

 printf("%d, %s\n", n, buf);

 /* socketの終了 */
 close(sock);

 return 0;
}

上記TCPクライアントは、サーバに接続すると文字列を受信して表示します。

上記サーバとクライアントの利用方法ですが、まず、サーバ側のプログラムを実行して下さい。 サーバ側のプログラムを実行してある状態でクライアント側のプログラムを実行すると「HELLO」という文字列のやり取りが行われます。

127.0.0.1はlocalhost(自分)を表しています。 そのため、サーバとクライアントのプログラムを実行は同一ホストで行わなければなりません。 クライアントプログラムでサーバのIPアドレスを指定している部分を適切な値に変更すると別ホストでの通信が可能になります。 自分のIPアドレスを知りたい場合には「ifconfig -a」コマンドを利用するとIPアドレスを知ることが出来ます。

このサンプルを実行するためには、サーバとクライアント両方をコンパイルして実行しなければならないわけですが、同じディレクトリに両方を入れて単純にgccを行ってしまうと両方ともa.outになってしまいます。 それでは両方を実行できないので、サーバとクライアント両方を同じディレクトリでコンパイルしたい場合には、a.outではない無いファイル名でコンパイル結果を出力してください。 例えば、サーバ側をserverという名前の実行ファイルにしたい場合には「gcc -o server serversample.c」という風にコンパイルして下さい。 同様にクライアント側をclientという名前の実行ファイルにしたい場合には「gcc -o client clientsample.c」という風にコンパイルして下さい。