Network Programming (for Primer)

<URL:http://neptis.dsl.gr.jp/Network/>

このページにはUNIXネットワーク プログラミング入門編が記述されています。


ソケット通信モデル

ソケットによる通信プログラムといっても、 その振る舞いにはいくつか種類があります。 そしてどういう使い方が許されるかは実装に依存します。 そのうちで多くの処理系で実現されているのは以下の2種類です。

  1. ストリーム(stream)型
  2. データグラム(datagram)型

簡単に説明すると、ストリーム型は連続してデータの やり取りを行うことができる通信方式で、 データグラム型は不連続のデータを通信する方式です。 前者は全2重のFIFOで後者はパケット通信とも言えます。 両者は互いにエミュレートできますが、 普通は通信するデータの性質に応じて使い分けます。

UNIXで実装されている通信プログラムで有名なものは前者が多く、 たとえばTELNET,FTP(File Transfer Protocol), HTTP(HyperText Transfer Protocol), NNTP(Network News Transfer Protocol), SMTP(Simple Mail Transfer Protocol)が該当します。 X Window Systemもストリームを使っています。 後者はNFS(Network File Server)や DNS(Domain Name Service)等で利用されています。

ここで解説するのは前者です。

ソケット操作の基本関数(streamモデル)

細かい操作をするならば、 より多くのソケットを操作する関数を知る必要がありますが、 簡単な通信プログラムならば、以下の基本関数を知るだけで十分です。

表 streamモデルにおけるソケットの基本関数
関数 働き
socket() ソケットを生成しディスクリプタを得る
connect() ソケットを接続する
bind() ソケットにアドレスを割り当てる
listen() ソケットを受け状態にする
accept() 接続してきたソケットのディスクリプタを得る
shutdown() ソケットの接続を切る
close() ソケットを破棄する

ソケット ライブラリはクライアント/サーバ モデルの プログラムを作成する時によく利用されます。 一般にサーバ側はconnect()以外 全部用いますが、 クライアント側が用いるのはsocket(), bind(), connect(), shutdown(), close()で十分です。 そして多くの場合、クライアントはbind()は使わなくても大丈夫です。

ソケットの1対1の通信モデル

ソケットで1対1の通信を行うプログラムは次のような構造になります。 clinet()がクライアント側の記述でserver()がサーバ側の記述になります。

client() {
    int sd; /* socket descripter */
    struct sockaddr caddr;  /* address where is client */
    struct sockaddr addr;  /* address which server */

    sd = socket();
    bind(sd,&caddr,);
    connect(sd,&addr,);

    while(!end of work) {
        read(sd,) or write(sd,);
    }
    shutdown(sd,);
    close(sd);
}

server() {
    int sd; /* socket descripter */
    int csd; /* socket descripter for communicate with clinet */
    struct sockaddr caddr;  /* address where is client */
    struct sockaddr addr; /* server address for identify with client */

    sd = socket();
    bind(sd,&addr,);
    listen(sd,);

    csd = accept(sd,&caddr,);

    while(!end of work) {
        read(csd,) or write(csd,);
    }
    shutdown(csd,);
    close(csd);

    close(sd);
}

ここに示したのは厳密にはCのプログラムではありません。 呼び出す関数の関係を把握しやすいように略記してあります。 後で実際に動作するプログラムを示しますが、 それとこれとどこが対応しているか見比べてみてください。

ソケットとアドレス

ソケットには他のプロセスからそれぞれを識別するために アドレスが割り振られます。ソケットのアドレスは、 例えばファイルシステム上のパスであったり、 IPアドレスとポート番号であったりします。 ちなみにプロセス内での識別はディスクリプタを用います。

どういうアドレス空間を利用するかはsocket()の生成時に決定します。 具体的にどういうものが使えるかは実装に依存します。 現時点では一つのホストのUNIXファイル システムシステムを利用する UNIXドメイン(PF_UNIX)と、インターネット(IP-V4)にて利用する INETドメイン(PF_INET)が多くの処理系で利用できます。

作成するプログラムの性質にもよるのですが、 あまり特定のアドレス空間に固執したプログラムを作成しない方が 柔軟性のあるプログラムに仕上がるのでよいと思います。 (例えば X Window SystemはUNIXドメイン, INETドメイン, DECNETドメインのどれでも動くように記述されており、 わずかな変更で他のドメインにも対応できるように設計されています。)

1対1で通信するプログラム

以下に、UNIXで実際に動作するINETドメインで通信する ソケットを利用したプログラムを示します。

プログラム中に注釈を入れてありますが、利用している システム コールおよびライブラリで意味がわからないものは、 一度はそれがどういうものかを オンライン マニュアルで確認してみてください。

ptop.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

/* このプログラムが利用するソケットのポート番号 */

#ifndef SOCK_INET_PORT
#define SOCK_INET_PORT 9000
#endif

int debug;

/* INETドメインのポート番号 */
int sock_in_port = SOCK_INET_PORT;

/* INETドメインのアドレス(IPV4) */
unsigned long sock_in_addr;

/* INETアドレス初期化用 */
struct sockaddr_in addr_zero;

int connect_port()
{
    struct sockaddr_in addr = addr_zero;
    int sd;

    /* STREAM型ソケットの生成。Protocol FamilyはINET */

    if((sd = socket(PF_INET,SOCK_STREAM,0)) < 0)
        return perror("socket"), -1;

    /* ソケットを接続する先のアドレス */

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(sock_in_addr);
    addr.sin_port = htons(sock_in_port);

    /* ソケットの接続。失敗したらソケットを破棄。 */
    if(connect(sd,(struct sockaddr *)&addr,sizeof addr) < 0) {
        perror("connect");
        
        if(close(sd) < 0)
            perror("close");
        return -1;
    }

    if(debug)
        fprintf(stderr,"connect %d, addr:%s port:%d\n",
                sd, inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
    
    return sd;          /* ソケットのディスクリプタを返す。 */
}

void client()
{
    int sd, n;
    char buf[BUFSIZ];

    /* 接続 */
    if((sd = connect_port()) < 0)
        return;

    /* 相手からデータを得る */
    while((n = recv(sd,buf,sizeof buf,0)) > 0)
        printf("%d bytes recived.\n",n);

    if(n < 0)
        perror("recv");

    /* 回線の切断 */
    if(shutdown(sd,2) < 0)
        perror("shutdown");

    /* ソケットの破棄 */
    if(close(sd) < 0)
        perror("close");
}

/* 接続待ち行列の大きさ。今回は1つだけ接続 */

int sock_backlog = 1;

int passive_port()
{
    struct sockaddr_in addr = addr_zero;
    int sd;

    /* STREAM型ソケットの生成。Protocol FamilyはINET */

    if((sd = socket(PF_INET,SOCK_STREAM,0)) < 0)
        return perror("socket"),-1;


    /* 生成したソケットに割り付けるアドレス */

    addr.sin_family = AF_INET;
    /* 任意のアドレスからの接続を許可 */
    addr.sin_addr.s_addr = 0;
    addr.sin_port = htons(sock_in_port);

    /* アドレスの割り付け。失敗したらソケットを破棄する。 */

    if(bind(sd,(struct sockaddr *)&addr,sizeof addr) < 0) {
        perror("bind");

    fail_return:
        if(close(sd) < 0)
            perror("close");
        return -1;
    }

    /* ソケットを受動状態にする */

    if(listen(sd,sock_backlog) < 0) {
        perror("listen");
        goto fail_return;
    }

    if(debug)
        fprintf(stderr,"passive %d, addr:%s port:%d\n",
                sd,inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));

    return sd;
}   

int clinet_port(int sd)
{
    int csd;
    struct sockaddr_in addr = addr_zero;
    size_t addr_len = sizeof addr;

    /* 接続してきたソケットのディスクリプタを得る */

    if((csd = accept(sd,(struct sockaddr *)&addr,&addr_len)) < 0) {
        perror("accept");
        return -1;
    }

    /* 接続してきたソケットのアドレスの確認 */
    if(debug)
        fprintf(stderr,"client %d, addr:%s port:%d\n",
                csd,inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));

    return csd;
}

void server()
{
    int sd, csd;
    int n,m;
    char buf[BUFSIZ];

    if((sd = passive_port()) < 0 || (csd = clinet_port(sd)) < 0)
        return;

    /* 受動ソケットの破棄 */
    if(close(sd) < 0)
        perror("close");

    /* sendによるデータの転送 */
    while(fgets(buf,sizeof buf,stdin)) {
        if((n = send(csd,buf,m = strlen(buf),0)) < 0) {
            perror("send");
            break;
        }
        printf("%d bytes (%d%%) send.\n",n,n * 100/m);
    }
    /* 回線の切断 */
    if(shutdown(csd,2) < 0)
        perror("shutdown");

    /* ソケットの破棄 */
    if(close(csd) < 0)
        perror("close");
}

void realize_port(const char *port)
{
    struct servent *p;
    short in_port;

    if(sscanf(port,"%hd",&in_port) == 1)
         sock_in_port = in_port;
    else if((p = getservbyname(port,"tcp")) != NULL)
         sock_in_port = ntohs(p->s_port);
    else 
        perror("getservbyname");
}

void realize_host(const char *host)
{
    struct hostent *p;
    struct sockaddr_in addr;

    if((p = gethostbyname(host)) == NULL)
        herror("getservbyname");
    else {
        /* DNSにINETドメインの情報が登録されていない */
        if(p->h_addrtype != AF_INET) {
            fprintf(stderr,"unknow address type:%d\n",p->h_addrtype);
            exit(1);
        }
        /* DNSのINETドメインの情報がIPV4用では無い */
        if(p->h_length > sizeof addr.sin_addr) {
            fprintf(stderr,"too long addres length:%d/%d\n",
                    p->h_addrtype,sizeof addr.sin_addr);
            exit(1);
        }
        memcpy(&addr.sin_addr,p->h_addr,p->h_length);
        sock_in_addr = ntohl(addr.sin_addr.s_addr);
    }
}

int main(int argc,char **argv)
{
    int opt;
    int sflag = 0;

    char *host = "localhost";
    char *port = NULL;
    long in_addr;
    
    while((opt = getopt(argc,argv,"b:p:h:sv")) > 0)
        switch(opt) {
        case 'b':
            sock_backlog = atoi(optarg);
            break;
        case 'h':
            host = optarg;  /* 接続ホストの変更 */
            break;
        case 'p':
            port = optarg;  /* 接続ポートの変更 */
            break;
        case 's':
            sflag = 1;      /* 受け側として動作 */
            break;
        case 'v':
            debug++;
        }

    if(port)
        realize_port(port);

    if((in_addr = inet_addr(host)) != INADDR_NONE)
        sock_in_addr = ntohl(in_addr);
    else 
        realize_host(host);

    if(sflag)
        server();
    else
        client();

    return 0;
}

このプログラムは一つでサーバとクライアントを兼ねています。

% ./ptop -sv
のように-sオプションを付けて起動するとサーバとして動作します。

プログラムの確認には複数の端末を用意するとよいでしょう。 そして一方で上記のようにサーバとしてプログラムを起動しておき、 他方で、

% ./ptop -v -h server_host
として起動します。server_hostには サーバープログラムが動作しているマシンのホスト名を与えます。 どちらも同じホストで動作しているならば、 これに“localhost”を指示するとよいでしょう。

上記のように -v オプションを付けて起動すると、 少しだけ動作確認のための冗長メッセージを出力するようになります。

このプログラムは、サーバーは標準入力から入力したデータを クライアントに渡すと、すぐに終了します。 クライアント側もサーバからデータを受け取り終ると、 勝手に終了するようになっています。

サーバーとして動作している側で順にキーボードからデータを入力して、 クライアント側できちんと受け取れていることを確認して見て下さい。

また、手でキーボードから順に入力して見た場合と、

% ls | ./ptop -sv
のように、適当にリダイレクト(あるいはパイプ)した場合と、 どのような違いがあるのか確認してみるのもよいでしょう。

データーの送受信をするクライアント

後で紹介するサーバーで使うための、 簡単なデーターを送受信するクライアント プログラムを作成します。

このプログラムは簡単ですが、 やり取りするデーターについてある想定をしています。 小さいながらもプロトコルを必要とするプログラムなのです。

そのプロトコルは、次のような物です。

  1. 接続したクライアントからデータを送信する。
  2. クライアントはサーバーからの応答を待つ。
  3. 以上の繰り返し。

sendline.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

/* このプログラムが利用するソケットのポート番号 */

#ifndef SOCK_INET_PORT
#define SOCK_INET_PORT 9001
#endif

int debug;

/* INETドメインのポート番号 */
int sock_in_port = SOCK_INET_PORT;

/* INETドメインのアドレス(IPV4) */
unsigned long sock_in_addr;

/* INETアドレス初期化用 */
struct sockaddr_in addr_zero;

/* 上で定義されている内容と同じです。*/
int connect_port();

void sendline()
{
    int sd, n, ch;
    char buf[BUFSIZ], *t;
    size_t len;

    /* 接続 */
    if((sd = connect_port()) < 0)
        return;

    while(fgets(buf,sizeof buf,stdin)) {
        len = strlen(t = buf);

        /* 
           相手へデータを送る
           一度に送れない時は、全部送るまで繰り返す。
         */

        while((n = send(sd,t,len,0)) > 0 && n < len) {
            t += n;
            len -= n;
            if(debug)
                fprintf(stderr,"%d bytes send.\n",n);
        }

        if(n < 0)
            perror("send");

        /*
           相手からデータを得る
           改行文字を受け取ると読み込みを止める
         */

        while((n = recv(sd,buf,sizeof buf,0)) > 0) {
            printf("%.*s",n,buf);

            if(debug)
                fprintf(stderr,"%d bytes recived.\n",n);

            for(t = buf; *t; t++)
                if(*t == '\n')
                    goto read_end;
        }

    read_end:
        if(n < 0)
            perror("recv");
    }

    /* 回線の切断 */
    if(shutdown(sd,2) < 0)
        perror("shutdown");

    /* ソケットの破棄 */
    if(close(sd) < 0)
        perror("close");
}

/* 上で定義されている内容と同じです。*/
void realize_port(const char *port);
void realize_host(const char *host);

int main(int argc,char **argv)
{
    int opt;
    int sflag = 0;

    char *host = "localhost";
    char *port = NULL;
    long in_addr;
    
    while((opt = getopt(argc,argv,"p:h:v")) > 0)
        switch(opt) {
        case 'h':
            host = optarg;  /* 接続ホストの変更 */
            break;
        case 'p':
            port = optarg;  /* 接続ポートの変更 */
            break;
        case 'v':
            debug++;
        }

    if(port)
        realize_port(port);

    if((in_addr = inet_addr(host)) != INADDR_NONE)
        sock_in_addr = ntohl(in_addr);
    else 
        realize_host(host);

    sendline();

    return 0;
}

このプログラムの動作を確認する手っ取り早い方法は ローカルのエコー サーバー(echo server)に接続してみることです。 一般にUNIXのinetdサーバーにはエコー機能が内蔵されています。 このエコー機能は、与えた文字をそのまま返してくれるというものです。

次のようにコマンドラインからプログラムを起動すると、 その動作が確認できます。

% ./sendata -p echo
ABCDEFG
ABCDEFG
hello!
hello!
0123456789
0123456789
^D  - (EOF文字の入力)
%

文字を大文字に変換するサーバー

次に示すプログラムは、 「受け取った文字を大文字に変換して返す」という サーバー プログラムです。 先に紹介したsendline.cと 対にして使うことを想定したプログラムです。

caseup.c

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

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

#include "config.h"

/* このプログラムが利用するソケットのポート番号 */

#ifndef SOCK_INET_PORT
#define SOCK_INET_PORT 9001
#endif

int debug;

/* INETドメインのポート番号 */
int sock_in_port = SOCK_INET_PORT;

/* INETドメインのアドレス(IPV4) */
unsigned long sock_in_addr;

/* INETアドレス初期化用 */
struct sockaddr_in addr_zero;

/* 接続待ち行列の大きさ。3つまで待たせる */

int sock_backlog = 3;

/* 小文字を大文字に変換して返す */

char *str_case_up(char *s,size_t n)
{
    char *t = s;

    while(n--) {
        *t = toupper(*t);
        t++;
    }
    return s;
}

void do_reply(int sd)
{
    int n,m;
    char buf[BUFSIZ],*t;

    while((n = recv(sd,buf,sizeof buf,0)) > 0) {
        t = str_case_up(buf,n);

        /*
           相手へデータを送る
           一度に送れない時は、全部送るまで繰り返す。
         */
        while((m = send(sd,t,n,0)) > 0 && m < n)
            t += m, n -= m;
        
        if(m < 0) {
            perror("send");
            break;
        }
    }

    if(n < 0)
        perror("recv");

    /* 回線の切断 */
    if(shutdown(sd,2) < 0)
        perror("shutdown");

    /* ソケットの破棄 */
    if(close(sd) < 0)
        perror("close");
}

/* 上で定義されている内容と同じです。*/
int passive_port();
int clinet_port(int sd);

void case_up_server()
{
    int sd, csd;

    if((sd = passive_port()) < 0)
        return;

    while((csd = clinet_port(sd)) >= 0)
        do_reply(csd);

    /* 受動ソケットの破棄 */
    if(close(sd) < 0)
        perror("close");
}

/* 上で定義されている内容と同じです。*/
void realize_port(const char *port);
void realize_host(const char *host);

int main(int argc,char **argv)
{
    int opt;
    int sflag = 0;

    char *host = "localhost";
    char *port = NULL;
    long in_addr;
    
    while((opt = getopt(argc,argv,"b:p:h:sv")) > 0)
        switch(opt) {
        case 'b':
            sock_backlog = atoi(optarg);
            break;
        case 'h':
            host = optarg;  /* 接続ホストの変更 */
            break;
        case 'p':
            port = optarg;  /* 接続ポートの変更 */
            break;
        case 'v':
            debug++;
        }

    if(port)
        realize_port(port);

    if((in_addr = inet_addr(host)) != INADDR_NONE)
        sock_in_addr = ntohl(in_addr);
    else 
        realize_host(host);

    case_up_server();

    return 0;
}

このプログラムの動作確認を行なうには、コマンドラインから

% ./caseup &
とでも入力して、あらかじめこのプログラムを動作させておいてから、 sendlineでデータを送ってみます。 例えばsendlineを次のように起動すると、 ディレクトリの一覧がすべて大文字に変換して返されることが確認できます。
% ls | ./sendline
MAKEFILE
CASEUP
CASEUP.C
CASEUP.C~
FOO
PTOP
PTOP.C
SENDLINE
SENDLINE.C
SENDLINE.C~
%

複数のクライアントを同時に相手する仕組み(fork)

手っ取り早く複数クライアント対応にするには、 forkシステムコールを利用して、 相手をするサーバーを複数のプロセスとして 動作させるとよいでしょう。

サービス内容によってはリソースの排他制御が必要な場合もありますが、 この件に関しては、それは特に必要ありません。

caseup2.c

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

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

/* このプログラムが利用するソケットのポート番号 */

#ifndef SOCK_INET_PORT
#define SOCK_INET_PORT 9001
#endif

int debug;

/* INETドメインのポート番号 */
int sock_in_port = SOCK_INET_PORT;

/* INETドメインのアドレス(IPV4) */
unsigned long sock_in_addr;

/* INETアドレス初期化用 */
struct sockaddr_in addr_zero;

/* 接続待ち行列の大きさ。3つまで待たせる */

int sock_backlog = 3;

/* 上で定義されている内容と同じです。*/
int passive_port();
int clinet_port(int sd);
char *str_case_up(char *s,size_t n);
void do_reply(int sd);

/* 子プロセスの後始末用シグナル ハンドラー */

void sig_child_handler(int signum)
{
    int status;

    if(signum != SIGCHLD)
        return;

    if(wait(&status) < 0)
        perror("wait");
    else if(debug)
        fprintf(stderr,"child exit status: %d\n",status);

    if(signal(signum,sig_child_handler) == SIG_ERR)
        perror("signal");
}

void case_up_server2()
{
    int sd, csd;
    pid_t pid;

    if((sd = passive_port()) < 0)
        return;

    if(signal(SIGCHLD,sig_child_handler) == SIG_ERR)
        perror("signal");

    for(;;) {
        if((csd = clinet_port(sd)) < 0) {
            if(errno == EINTR)
                continue;
        }

        if((pid = fork()) < 0) {
            perror("fork");
            break;
        }
        
        /* 子プロセスが変換サービスを提供する */
        if(pid == 0) {
            /* 受動ソケットの破棄 */
            if(close(sd) < 0)
                perror("close");

            do_reply(csd);
            exit(0);
        }

        /* クライアント ソケットの破棄 */
        if(close(csd) < 0)
            perror("close");
    }

    /* 受動ソケットの破棄 */
    if(close(sd) < 0)
        perror("close");
}

/* 上で定義されている内容と同じです。*/
void realize_port(const char *port);
void realize_host(const char *host);
mainについてはcaseup.cのそれを case_up_server2() を呼び出すように変更したものを利用して下さい。

複数のクライアントを同時に相手する仕組み(select)

selectシステムコールを利用して複数のクライアントに対して応答する サーバーを示します。 ただしこの場合、今までに比べてプログラムが非常に複雑になるため、 理解しやすくするために、2段階にわたってプログラムを紹介します。

次に示すプログラムcaseup3.cは ほとんどの処理系で、 あまりサーバーに負荷がかかっていない状態においては ほぼ完全に動作するというものです。 (つまり、条件によっては期待通りに動作しない場合があるのです。)

ネットワーク プログラミングの経験者の方は、 とりあえず、プログラムをみてその理由を考えて見て下さい。

caseup3.c

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>

/* このプログラムが利用するソケットのポート番号 */

#ifndef SOCK_INET_PORT
#define SOCK_INET_PORT 9001
#endif

int debug;

/* INETドメインのポート番号 */
int sock_in_port = SOCK_INET_PORT;

/* INETドメインのアドレス(IPV4) */
unsigned long sock_in_addr;

/* INETアドレス初期化用 */
struct sockaddr_in addr_zero;

/* 接続待ち行列の大きさ。3つまで待たせる */

int sock_backlog = 3;

/* 上で定義されている内容と同じです。*/
int passive_port();
int clinet_port(int sd);
char *str_case_up(char *s,size_t n);

int send_reply(int sd)
{
    int n;
    char buf[BUFSIZ];

    if((n = recv(sd,buf,sizeof buf,0)) < 0)
        perror("recv");
    else if(n) {
        /* 相手へデータを送る */
        if(send(sd,str_case_up(buf,n),n,0) < 0)
            perror("send");
        else
            return 0;   /* 応答ができた */
    }

    return -1; /* 読み込むデータが無いかエラーが発生した */
}

#ifndef N_CLIENTS
#define N_CLIENTS 10
#endif

void case_up_server3()
{
    int f,i,j, sd, csd;

    int fdwidth;
    fd_set readfds;

    int csds[N_CLIENTS];
    int n_client = 0;

    if((sd = passive_port()) < 0)
        return;

    FD_ZERO(&readfds);

    for(;;) {
        fdwidth = sd;
        FD_SET(sd,&readfds);

        /* selectで検査するクライアントポートの設定 */
        for(i = 0; i < n_client; i++) {
            if(fdwidth < csds[i])
                fdwidth = csds[i];
            FD_SET(csds[i],&readfds);
        }

        fdwidth++;

        if(select(fdwidth,&readfds,NULL,NULL,NULL) < 0) {
            perror("select");
            break;
        }

        /* 
           読み込み可能なポートの処理
           (クライアントに対する応答)
         */

        for(i = j = 0; i < n_client; i++) {
            csd = csds[i];
            if(FD_ISSET(csd,&readfds)) {
                FD_CLR(csd,&readfds);

                if(send_reply(csd) < 0) {
                    /* 回線の切断 */
                    if(shutdown(csd,2) < 0)
                        perror("shutdown");
                    /* クライアント ソケットの破棄 */
                    if(close(csd) < 0)
                        perror("close");
                    continue;
                }
            }
            csds[j++] = csds[i];
        }

        n_client -= i - j;

        /* 接続して来たクライアントの登録 */
        if(FD_ISSET(sd,&readfds)) {
            if((csd = clinet_port(sd)) < 0)
                ;
            else if(n_client < N_CLIENTS)
                csds[n_client++] = csd;
            else {
                fprintf(stderr,"too many clients:%d\n",n_client);

                /*  登録に失敗したクライアント ソケットの破棄 */
                if(close(csd) < 0)
                    perror("close");
            }
        }
    }

    /* 受動ソケットの破棄 */
    if(close(sd) < 0)
        perror("close");
}

/* 上で定義されている内容と同じです。*/
void realize_port(const char *port);
void realize_host(const char *host);
mainについてはcaseup.cのそれを case_up_server3() を呼び出すように変更したものを利用して下さい。

caseup4.c

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/time.h>

/* このプログラムが利用するソケットのポート番号 */

#ifndef SOCK_INET_PORT
#define SOCK_INET_PORT 9001
#endif

int debug;

/* INETドメインのポート番号 */
int sock_in_port = SOCK_INET_PORT;

/* INETドメインのアドレス(IPV4) */
unsigned long sock_in_addr;

/* INETアドレス初期化用 */
struct sockaddr_in addr_zero;

/* 接続待ち行列の大きさ。3つまで待たせる */

int sock_backlog = 3;

/* 上で定義されている内容と同じです。*/
int passive_port();
int clinet_port(int sd);
char *str_case_up(char *s,size_t n);

struct sock_buf {
    char *buf, *ptr;
    size_t buf_len;
    int flag;
};

enum {
    sb_error = 01,
    sb_eof = 02,
    si_read = 010,
    si_write = 020,
    si_close = 040,
};

struct sock_buf *new_sock_buf(struct sock_buf *p)
{
    p->buf = p->ptr = NULL;
    p->buf_len = BUFSIZ;
    p->flag = 0;
    
    return p;
}

void delete_sock_buf(struct sock_buf *p)
{
    if(p->buf)
        free(p->buf);
}

char *sock_buf_alloc(struct sock_buf *p)
{
    if(!p->buf && !(p->ptr = p->buf = malloc(p->buf_len))) {
        fprintf(stderr,"malloc %u: %s\n",p->buf_len,strerror(errno));
        p->flag |= sb_error;
    }
    return p->buf;
}

#ifndef min
#define min(a,b) ((a) < (b) ? (a) : (b))
#endif

/* 読み込み バッファに蓄えられている データ数 */
#define sock_buf_read_len(bp) ((bp)->ptr - (bp)->buf)

int sock_buf_fill(int sd,struct sock_buf *p,size_t len)
{
    int n,rlen;

    if(p->flag & sb_error)
        return -1;

    /* bufに空きがなければ読み込まない */
    if(!p->buf || (rlen = p->buf_len - sock_buf_read_len(p)) == 0)
        return 0;

    len = min(len,rlen);

    if((n = recv(sd,p->ptr,len,0)) < 0) {
        fprintf(stderr,"recv %d,%u: %s\n",sd,len,strerror(errno));
        p->flag |= sb_error;
    }
    else if(n)
        p->ptr += n;

    return n;
}

/* 書きだしバッファに蓄えられている データ数 */
#define sock_buf_write_len(bp) ((bp)->ptr - (bp)->buf)

int sock_buf_flush(int sd,struct sock_buf *p)
{
    int n,len;

    if(p->flag & sb_error)
        return -1;

    if(!p->buf || (len = sock_buf_write_len(p)) == 0)
        return 0;

    if((n = send(sd,p->buf,len,0)) < 0) {
        fprintf(stderr,"send %d,%u: %s\n",sd,len,strerror(errno));
        p->flag |= sb_error;
    }
    else if(n < len)
        memmove(p->buf + n,p->buf,len - n);

    return n;
}

union sock_info;

struct sock_accept {
    int flag,sd;
    void (*close_proc)(union sock_info *);
    void (*read_proc)(union sock_info *);
};

struct sock_client {
    int flag,sd;
    void (*close_proc)(union sock_info *);
    void (*read_proc)(union sock_info *);
    void (*write_proc)(union sock_info *);
    struct sock_buf rbuf,wbuf;
};

union sock_info {
    int flag;
    struct sock_accept a;
    struct sock_client c;
};

#ifndef N_CLIENTS
#define N_CLIENTS 10
#endif

union sock_info sinfo[N_CLIENTS];
int n_client;

void sock_accept_close(union sock_info *si)
{
    if(close(si->a.sd) < 0)
        perror("close");
}

struct sock_client *new_sock_client(struct sock_client *s,int sd);

void sock_accept_read(union sock_info *si)
{
    int csd;

    if((csd = clinet_port(si->a.sd)) < 0)
        return;

    if(n_client < N_CLIENTS)
        new_sock_client(&sinfo[n_client++].c, csd);
    else {
        fprintf(stderr,"too many clients:%d\n",n_client);

        /*  登録に失敗したクライアント ソケットの破棄 */
        if(close(csd) < 0)
            perror("close");
    }
}

struct sock_accept *new_sock_accept(struct sock_accept *s,int sd)
{
    s->flag = si_read;
    s->sd = sd;
    s->close_proc = sock_accept_close;
    s->read_proc = sock_accept_read;

    return s;
}

void sock_client_close(union sock_info *si)
{
    /* 回線の切断 */
    if(shutdown(si->c.sd, 2) < 0)
        perror("shutdown");

    /* クライアント ソケットの破棄 */
    if(close(si->c.sd) < 0)
        perror("close");

    delete_sock_buf(&si->c.wbuf);
    delete_sock_buf(&si->c.rbuf);
}

void sock_client_read(union sock_info *si)
{
    struct sock_client *s = &si->c;
    size_t len;
    int n;

    if((len = s->wbuf.buf_len - sock_buf_write_len(&s->wbuf)) == 0) {
        s->flag &= ~si_read;
        return;
    }

    if((n = sock_buf_fill(s->sd, &s->rbuf, len)) <= 0)
        s->flag |= si_close;
    else {
        memcpy(s->wbuf.ptr, str_case_up(s->rbuf.buf,n), n);
        s->wbuf.ptr += n;
        s->flag |= si_write;
        s->rbuf.ptr = s->rbuf.buf;
    }
}

void sock_client_write(union sock_info *si)
{
    struct sock_client *s = &si->c;
    size_t len;
    int n;

    if((len = sock_buf_write_len(&s->wbuf)) == 0) {
        s->flag &= ~si_write;
        return;
    }

    if((n = sock_buf_flush(s->sd,&s->wbuf)) < 0)
        s->flag |= si_close;
    else if(n < len)
        s->wbuf.ptr -= n;
    else {
        s->wbuf.ptr = s->wbuf.buf;
        s->flag &= ~si_write;
    }
}

struct sock_client *new_sock_client(struct sock_client *s,int sd)
{
    s->flag = si_read;
    s->sd = sd;
    s->close_proc = sock_client_close;
    s->read_proc = sock_client_read;
    s->write_proc = sock_client_write;

    new_sock_buf(&s->wbuf);
    new_sock_buf(&s->rbuf);

    if(!sock_buf_alloc(&s->wbuf) || !sock_buf_alloc(&s->rbuf))
        s->flag = si_close;

    return s;
}

void case_up_server4()
{
    int f,i,j, sd;
    int fdwidth;
    fd_set readfds,writefds;

    if((sd = passive_port()) < 0)
        return;

    new_sock_accept(&sinfo[n_client++].a, sd);

    FD_ZERO(&readfds);
    FD_ZERO(&writefds);

#define csd(i) (sinfo[(i)].a.sd)

    for(;;) {
        fdwidth = 0;

        /* selectで検査するクライアントポートの設定 */

        for(i = 0; i < n_client; i++) {
            if(fdwidth < csd(i))
                fdwidth = csd(i);
            if(sinfo[i].flag & si_read)
                FD_SET(csd(i), &readfds);
            if(sinfo[i].flag & si_write)
                FD_SET(csd(i), &writefds);
        }

        if(select(++fdwidth,&readfds,&writefds,NULL,NULL) < 0) {
            perror("select");
            break;
        }

        for(i = j = 0; i < n_client; i++) {
            if(FD_ISSET(csd(i),&writefds)) {
                FD_CLR(csd(i),&writefds);
                (*sinfo[i].c.write_proc)(sinfo + i);
            }
            if(FD_ISSET(csd(i),&readfds)) {
                FD_CLR(csd(i),&readfds);
                (*sinfo[i].a.read_proc)(sinfo + i);
            }
            if(sinfo[i].flag & si_close) {
                (*sinfo[i].a.close_proc)(sinfo + i);
                if(debug)
                    fprintf(stderr,"closed %d\n",csd(i));
                continue;
            }
                
            sinfo[j++] = sinfo[i];
        }

        n_client -= i - j;
    }

    for(i = 0; i < n_client; i++)
        (*sinfo[i].a.close_proc)(sinfo + i);
}

/* 上で定義されている内容と同じです。*/
void realize_port(const char *port);
void realize_host(const char *host);
mainについてはcaseup.cのそれを case_up_server4() を呼び出すように変更したものを利用して下さい。

ストリーム ライブラリとの併用

UNIXの標準Cライブラリを併用すると、 多くの場合プログラムの記述量を減らすことができます。 ただし、いつでも標準ライブラリが使えるわけでなく、 プログラムの用途によっては使えない場合もあります。

sendline.cの場合は、丁度うまくいく例です。 sendline.cにおけるsendlineを 次に示すsendline2に変えても、 (厳密には異なりますが、)ほぼ同等な機能を提供できます。

ssendline.c

void sendline2()
{
    int sd;
    char buf[BUFSIZ];
    FILE *fp;

    /* 接続 */
    if((sd = connect_port()) < 0)
        return;
    
    if((fp = fdopen(sd,"r+")) == NULL) {
        fprintf(stderr,"fdopen %d: %s\n",sd,strerror(errno));

        /* 回線の切断 */
        if(shutdown(sd,2) < 0)
            perror("shutdown");
        /* ソケットの破棄 */
        if(close(sd) < 0)
            perror("close");

        return;
    }

    while(fgets(buf,sizeof buf,stdin)) {
        fputs(buf,fp);

        if(fflush(fp) < 0) {
            perror("fflush");
            break;
        }
        rewind(fp);

        if(!fgets(buf,sizeof buf,fp)) {
            if(ferror(fp))
                perror("fgets");
            break;
        }
        rewind(fp);

        fputs(buf,stdout);
    }

    /* 回線の切断 */
    if(shutdown(fileno(fp),2) < 0)
        perror("shutdown");

    /* ストリームの破棄 */
    if(fclose(fp) < 0)
        perror("fclose");
}

sendline2では、ソケットの入出力を並行して行なう必要がないため、 ストリーム バッファは一つだけ利用してrewindで切替えながら利用しています。 私の知る範囲ではNNTP, HTTP, Whois Protocolが同じように実装できます。

用途によって2つ以上のストリームを用意する必要があります。 FTP, TELNETがこれに該当します。


同じような内容のサイトの紹介

C言語によるUNIXネットワークプログラミング入門
http://www.ueda.info.waseda.ac.jp/~toyama/network/

iwao@dsl.gr.jp