このプログラムでやることは、
ここで注意して欲しいことは、サーバーの場合、 次の二種類のソケット、
forkと関係するので、
このことはしっかり頭に入れておいて下さい。
/*
* server_test.c
*
* コンパイル: cc server_test.c -lsocket -lnsl (Solaris の場合)
* 実行: ./a.out IPアドレス
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERV_PORT 10001
#define BUFSIZE 1024
int
main(int argc, char *argv[]) {
struct sockaddr_in serv_addr, client_addr;
int dataSock, waitSock;
int addrlen;
int i, len;
char buf[BUFSIZE];
/* 準備手順1: 下準備 (受信ホスト (= 自分) の指定) */
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = INADDR_ANY;
/* 準備手順2: 接続待ち用ソケットを作成する */
waitSock = socket(PF_INET, SOCK_STREAM, 0);
/* 準備手順3: 待受用アドレスに bind する */
bind(waitSock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
/* 準備手順4: クライアントからの接続待ち受け可能状態にする */
listen(waitSock, 1);
/* 接続受付手順: クライアントからのコネクション確立要求を受け入れる */
addrlen = sizeof(client_addr);
bzero(&client_addr, addrlen);
dataSock = accept(waitSock, (struct sockaddr*)&client_addr, &addrlen);
/* ------------------------------------------------------------
ここまでが、送受信を開始するまでの準備段階
これから後が本番
といっても、client の場合とほとんど同じ
------------------------------------------------------------ */
/* 送受信手順: データの送受信 */
/* まずは送ってみる */
#define MSG "Hello, World.\n"
send(dataSock, MSG, strlen(MSG) + 1, 0);
/* 次は受けとってみる */
/* ここでは (なんとなく) 60 バイトずつ受けとっている
この数字 (60) にはあまり意味がない */
while ((len = recv(dataSock, buf, 60, 0)) > 0) {
/* 受けとったものが \0 で終わる文字列とは限らないので、
わざわざ for で一文字ずつ表示している */
for (i = 0; i < len; i++) {
putc(buf[i], stdout);
}
}
/* 終了手順: 後始末 */
close(waitSock);
close(dataSock);
return 0;
}
sockaddr_in 構造体の変数 (ここでは serv_addr)
に、自分の情報 (使う IP アドレス、ポート) をツッこむ
socket() でソケットを作る
bind() でソケットにアドレスを割り当てる
bind() ですね。
ちょっと説明しにくいですが、sockaddr_in
構造体の変数で指定したアドレス/ポートと実際のソケットを対応付ける感じです。
listen() を使って、待ち受けることができる状態にします
(ここで一つめの socket が使われます)。
accept()
になります。
accept()は、クライアントからの接続を待ち、
接続されたらそれを受け入れ、実際の通信用のソケット (二つめのソケット)
を生成します。
以降は、このソケットを使って通信することになります。
ここで bzero() という見慣れないのが出てきていますが、これは
client_addr を初期化しているだけです。
あまり気にしないで、そのまま書いておきましょう。
accept()
によって生成されます。
client_test.c と比較してみて下さい。
MSG 以外は全く一緒になっています)。
telnet [サーバーが動いてるホスト] [待受ポート]
としてみましょう
(↑のプログラムをそのままコンパイルしていて、同じホストで telnet
するのであれば、
telnet localhost 10001 とします)。
終了するには、Ctrl-] とすると、
telnet>
というプロンプトが出るので、そこで q [RET] とします。
ということで、サーバーとクライアントの両方のプログラムを自分で書く場合は、 サーバーを先に書いておくと、telnet で動作の確認ができてデバッグが楽です (stream の場合は)。
socket(), bind(),
listen(), accept(),
send(), recv(), のそれぞれでエラー処理が必要です。
man ペイジでそれぞれの関数の返り値を調べて、適切な処理を付加しましょう。
client_addr
がほとんど使われていないことに気付いたでしょうか。
と一行で書くことができます。dataSock = accept(waitSock, NULL, NULL);
client_addr
なんて使ってるの、ってことですが、これを指定しておけば、
接続元 (クライアント) の情報を取得することができるのです。
例えば、元のプログラムの accept() の次の行あたりに、
printf("Connected from %s .\n", inet_ntoa(*(struct in_addr *)&client_addr.sin_addr.s_addr));
とか入れてみましょう。