■
リチャードスティーブンス氏の著作より、簡単なTCP daytimeクライアントとサーバのコード。
- daytimetcpcli.c
#include "unp.h" int main(int argc, char **argv) { int socket_fd; int read_bytes; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; /* インターネットソケットアドレス構造体 */ if (argc != 2) err_quit("usage: a.out"); /* TCPソケットの作成。インターネット(AF_INET)のストリーム(SOCK_STREAM) * ソケット、つまりTCPソケットを作成する。 */ if ( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) err_sys("socket error"); /* サーバのIPアドレスとポートの指定 */ memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; /* アドレスファミリの指定 */ servaddr.sin_port = htons(13); /* ポート番号をホストバイトオーダーから * ネットワークバイトオーダーに変換(host * to network short)する。13はdaytime se * rverのウェルノウンポート。 */ /* 引数で指定したIPアドレスを変換(presentation to numeric)して構造体に格納する */ if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) err_quit("inet_pton error for %s", argv[1]); /* ソケットアドレス構造体で指定したサーバとのTCPコネクションを確立する。 * &serveraddrをキャストしている理由についてはP.60を参照のこと。 */ if (connect(socket_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) err_sys("connect error"); /* サーバからの応答をread()で読み取り、標準出力に書き出す。TCPはレコード境 * 界を持たないバイトストリームプロトコルであるため、使用には注意が必要。 */ while ( (read_bytes = read(socket_fd, recvline, MAXLINE)) > 0) { /* ゼロは他方のエンドのコネクションクローズ。負の数はエラー。 */ recvline[read_bytes] = '\0'; /* null terminate */ if (fputs(recvline, stdout) == EOF) err_sys("fputs error"); } if (read_bytes < 0) err_sys("read error"); /* TCPソケットもファイルディスクリプタなのでexit()によりクローズされる。 */ exit(0); }
こちらはサーバ。先頭が大文字の関数は、エラー処理を内包したラッパ関数であることを意味する。
- daytimetcpsrv.c
#include "unp.h" #includeint main(int argc, char **argv) { int listen_fd; int connected_fd; char buff[MAXLINE]; time_t ticks; struct sockaddr_in servaddr; listen_fd = Socket(AF_INET, SOCK_STREAM, 0); memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(13); /* daytime server */ /* 接続を待つIPアドレスの指定。 INADDR_ANYは、どのインターフェースに宛てた * クライアントからのコネクションでも受け付ける事を意味する。 * INADDR_ANY = (in_addr_t) 0x00000000 */ servaddr.sin_addr.s_addr = htonl(INADDR_ANY); /* ソケットにローカルアドレスservaddrを割り当てる。伝統的にこの処理は「ソケ * ットに名前をつける」と呼ばれる。 */ Bind(listen_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)); /* listen()を呼び出し、ソケットをリスニングソケットに変換する。これにより、 * カーネルがクライアントからのコネクションを受け付けるようになる。 * socket, bind, listenという3段階の処理は、全てのTCPサーバがリスニングデ * ィスクリプタを用意する為の通常の手段である。 */ Listen(listen_fd, LISTENQ); /* クライアントからのコネクションを受け付け、応答を返す。尚、このサーバは同 * 時に1クライアントしか扱えない事に留意すること。 */ for ( ; ; ) { /* accept()は、接続要求キューから要求を取り出し、接続済みソケットを作 * 成し、そのソケットのディスクリプタを返す。元のソケットは影響を受け * ない。尚、ここではクライアントプロセスのプロトコルアドレスの取得は * 行わない(NULL, NULL) */ connected_fd = Accept(listen_fd, (struct sockaddr *) NULL, NULL); ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); /* 接続済みディスクリプタに値を書き込む。これによりクライアントへデー * タが送信される。 */ Write(connected_fd, buff, strlen(buff)); /* クライアントとのコネクションをクローズする。これによりTCPコネクショ * ンの通常終了シーケンス(双方向のFIN等)が起動される。 */ Close(connected_fd); } }
サーバ側でクライアントのIPアドレスとポートを出力するには、daytimetcpsrv.cを以下のように変更する。
--- intro/daytimetcpsrv.c.org 1997-05-09 06:00:59.000000000 +0900 +++ intro/daytimetcpsrv.c 1997-06-06 20:40:57.000000000 +0900 @@ -5,7 +5,8 @@ int main(int argc, char **argv) { int listenfd, connfd; - struct sockaddr_in servaddr; + socklen_t len; + struct sockaddr_in servaddr, cliaddr; char buff[MAXLINE]; time_t ticks; @@ -21,7 +22,11 @@ main(int argc, char **argv) Listen(listenfd, LISTENQ); for ( ; ; ) { - connfd = Accept(listenfd, (struct sockaddr *) NULL, NULL); + len = sizeof(cliaddr); + connfd = Accept(listenfd, (struct sockaddr *) &cliaddr, &len); + printf("connection from %s, port %d\n", + Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)), + ntohs(cliaddr.sin_port)); ticks = time(NULL); snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));