[TCP/IP] ソケットAPIリファレンス

### データ構造体
sockaddr構造体: 汎用のアドレス構造体
sockaddr_in構造体: インターネットアドレスファミリの構造体

### ソケットのセットアップ
socket(): TCPソケットまたはUDPソケットを作成
bind(): ローカルIPアドレスとローカルポートをソケットに割り当て
getsockname(): ソケットのローカル情報をsockaddr構造体に返す

### ソケットの接続
connect(): ローカルソケットと外部アドレスで指定したリモートソケットとの間にコネクションを確立
listen(): (ストリーム/TCPのみ): ソケットが接続待ち状態であることを示す
accept(): (ストリーム/TCPのみ): 既にIPアドレスとポートにバインドされているソケットへの接続要求を受け付ける
getpeername(): ソケットのリモート情報をsockaddr構造体に返す

### ソケットの通信
send(): バッファ内のバイトを指定のソケット経由で送信
sendto(): バッファ内のバイトを指定のソケット経由で送信
recv(): ソケットで受信したデータを指定のバイト数以下だけ指定のバッファ領域にコピーする
recvfrom(): ソケットで受信したデータを最大でも指定のバイト数だけ指定の場所にコピーする
close(): ソケットでの通信を終了する これ以上受信ができないというマークがつけられる
shutdown(): ソケットでの通信を終了する

### ソケットの制御
getsockopt(): ソケットのオプションを取得 オプションを使うとデフォルトの動作を変更できる
setsockopt(): ソケットのオプションを変更

### バイナリ/文字列お変換
inet_ntoa(): バイナリ表記(ネットワークバイト順)のIPアドレスをドット10進表記に変換
inet_addr():ドット10進表記のIPアドレスをバイナリ表記のIPアドレスに変換

### ホスト情報とサービス情報
gethostname(): 指定のバッファにローカルホスト名を返却
gethostbyname(): ホスト名を指定すると、gethostbyname()はそのホストの情報が記載されたhostent構造体を返す
gethostbyaddr(): IPアドレスを指定すると、gethostbyaddr()はそのIPアドレスを持つホストの情報が記載されたhostent構造体を返す
getservbyname(): サービス名とそのサービスを実装するプロトコルを指定すると、そのサービス情報が記載されたservent構造体を返す
getservbyport(): サービス情報が記載されたservent構造体を返す
hostent構造体
servent構造体

[TCP/IP] ドメインネームサービス

TCP/IPでは通信相手となるエンドポイントをIPとポートで表す
ほとんどのソケットAPIはネームサービスにアクセスできるようになっている
ネームサーバが利用する情報源はさまざまだが、主なものにDNS(分散データベース)とローカル設定データベースの2つがある
TCP、UDPがデータベースから情報を取得する手順はDNSプロトコルとして定義されている。

### 名前とIPの対応付け
struct hostent *gethostbyname(const char *hostName)
gethostbynameはhostent構造体を返却する

struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
}

ResolveName.c

#include <stdio.h>
#include <netdb.h>

unsigned long ResolveName(char name[]){
    struct hostent *host;

    if((host =gethostbyname(name)) ~~ NULL) {
        fprintf(stderr, "fethostbyname() failed");
        exit(1);
    }

    return *((unsigned long *) host->h_addr_list[0]);
}

こちらを使用するには次のように書く
echoServAddr.sin_addr.s_addr = ResolveName(servIP);

ip addressからhost nameを取得するには、gethostbyaddr()で取得できる
struct hostent *gethostbyaddr(const char *address, int addressLength, int addressFamily)

### 名前によるサービス情報の検索
struct servent *getservbyname(const char *serviceName, const char *protocol)

servent構造体
struct servent {
char *s_name;
char **s_aliases;
int s_sport;
char *s_proto;
}

#include <stdio.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>

unsigned short ResolveService(char service[], char protocol[]){

    struct servent *serv;
    unsigned short port;

    if((port=atoi(service)) == 0){
        if((serv = getservbyname(service, protocol)) == NULL) {
            fprintf(stderr, "getservbyname() failed");
            exit(1);
        }
        else {
            port = htons(port);
        }
        return port;
    }
}

[TCP/IP] マルチキャスト

マルチキャストはUDPユニキャストと似ているが、アドレスにメッセージ受信者の集合を指定する。
MutlicastSender.c

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){
    int sock;
    struct sockaddr_in multicastAddr;
    char *multicastIP;
    unsigned short multicastPort;
    char *sendString;
    unsigned char mutlicastTTL;
    unsigned int send StringLen;

    if((argc < 4) || (argc > 5)){
        fprintf(stderr, "Usage: %s <Multicast Address><Port><Send String>[<TTL>]\n", argv[0]);
        exit(1);
    }

    multicastIP = argv[1];
    multicastPort = atoi(argv[2]);
    sendString = argv[3];

    if(argc == 5)
        multicastTTL = atoi(argv[4]);
    else
        multicastTTL = 1;

    if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    if(setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void *) &multicastTTL, sizeof(multicastTTL)) < 0)
        DieWithError("setsockopt() failed");

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = inet_addr(multicastIP);
    broadcastAddr.sin_port = htons(broadcastPort);

    sendStringLen = strlen(sendString);
    for(;;){
        if (sendto(sock, sendString, sendStringLen, 0, (struct sockaddr *) &multicastAddr, sizeof(multicastAddr), sizeof(multicastAddr)) != sendStringLen)
            DieWithError("sendto() send a different number of bytes than expected.");
        sleep(3);
    }
}
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAXRECVSTRING 255

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){
    int sock;
    struct sockaddr_in multicastAddr;
    char *multicastIP;
    unsigned short multicastPort;
    char recvString[MAXRECVSTRING+1];
    int recvStringLen;
    struct ip_mreq multicastRequest;

    if(argc != 3){
        fprintf(stderr, "Usage: %s <Multicast IP><Port>]\n", argv[0]);
        exit(1);
    }

    multicastIP = argv[1];
    multicastPort = atoi(argv[2]);

    if((sock = socket(AF_INET, SOCK_GRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    broadcastAddr.sin_port = htons(multicastPort);

    if(bind(sock, (struct sockaddr *) &multicastAddr, sizeof(multicastAddr)) < 0)
        DieWithError("bind() failed");

    muticastRequest.imr_multiaddr.s_addr = inet_addr(multicastIP);
    multicastRequest.imr_interface.s_addr = htonl(INADDR_ANY);

    if(setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *) &multicastRequest, sizeof(multicastRequest))< 0)
        DieWithError("setsockopt() failed");

    if((recvStringLen = recvfrom(sock, recvString, MAXRECVSTRING, 0, NULL, 0)) < 0)
        DieWithError("recvfrom() failed");

    recvString[recvStringLen] = 
    printf("Recieved: %s\n", recvString);

    close(sock);
    exit(0);

}

ブロードキャストとマルチキャストのどちらを使用するかは受信を要求するホストの割合や通信相手に関する情報の有無などいくつかの条件を決める必要がある

[TCP/IP] ブロードキャストとマルチキャスト

1対1の通信はユニキャストと呼ぶ
接続帯域で多数に送信するには送信帯域を使い切ってしまう可能性があるが、データを一度だけコピーし、ネットワークに任せてしまえば、多数の相手にストリーム配信できる。コピー方法はブロードキャストとマルチキャストがある。
ブロードキャストは、ネットワークに接続する全てのホストがメッセージをコピー
マルチキャストは条件を満たす場合にのみ送信される
ブロードキャスト、マルチキャスト共に利用できるのはUDPソケットのみ。

### ブロードキャスト
ユニキャストとの違いはアドレス形式(255,255,255,255)

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string>
#include <unistd.h>

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){
    
    int sock;
    struct sockaddr_in broadcastAddr;
    char *broadcastIP;
    unsigned short broadcastPort;
    char *sendString;
    int broadcastPermission;
    unsigned int sendStringLen;

    if (argc < 4){
        fprintf(stderr, "Usage: %s <IP Address> <Port> <Send String>\n", argv[0]):
        exit(1);
    }

    broadcastIP = argv[1];
    broadcastPort = atoi(argv[2]);
    sendString = argv[3];

    if((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    broadcastPermission = 1;
    if(setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (void *) &broadcastPermission, sizeof(broadcastPermission)) < 0)
        DieWithError("setsockopt() failed");

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = inet(broadcastIP);
    broadcastAddr.sin_port = htons(broadcastPort);

    sendStringLen = strlen(sendString);
    for(;;){
        if(sendto(sock, sendString, sendStringLen, 0, (struct sockaddr *) &broadcastAddr, sizeof(broadcastAddr)) != sendStringLen)
            DieWithError("sendto() sent a different number of bytes than expected");

        sleep(3);
    }

BroadcastReciver.c

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAXRECVSTRING 255

void DieWithError(char *errorMessage);

int main(int argc, char *argv[]){

    int sock;
    struct sockaddr_in broadcastAddr;
    unsigned int broadcastPort;
    char recvString[MAXRECVSTRING+1];
    int recvStringLen;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <Broadcast Port>\n", argv[0]);
        exit(1);
    }

    memset(&broadcastAddr, 0, sizeof(broadcastAddr));
    broadcastAddr.sin_family = AF_INET;
    broadcastAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    broadcastAddr.sin_port = htons(broadcastPort);

    if(bind(sock, (struct sockaddr *) &broadcastAddr, sizeof(broadcastAddr)) < 0)
        DieWtihError("bind() failed");

    if((recvStringLen = recvfrom(sock, recvString, MAXRECVSTRING, 0, NULL, 0)) < 0)
        DieWtihError("recvfrom() failed");

    recvString[recvStringLen] = '\0';
    printf("Received: %s\n", recvString);

    close(sock);
    exit(0);

}

[TCP/IP] 多重化

Unixではselect関数を使用して、ペンディング中のI/Oをチェックするディスクリプタのリストを指定できる。
int select(int maxDescPlus1, fd_set *readDescs, fd_set *writeDescs, fd_set *exceptionDescs, struct timeval *timeout)

FD_ZERO(fd_set *descriptorVector) : fd_setにあるディスクリプタを全て削除
FD_CLR(int descriptor, fd_set *descriptorVector) : fd_setから削除
FD_SET(int descriptor, fd_set *descriptorVector) : 追加
FD_ISSET(int descriptor, fd_set *descriptorVector)

#include "TCPEchoServer.h"
#include <sys/time.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    int *servSock;
    int maxDescriptor;
    fd_set sockSet;
    long timout;
    struct timeval setTimeout;
    int running = 1;
    int noPorts;
    int port;
    unsigned short portNo;

    if(argc < 3) {
        fprintf(stderr, "Usage: %s <Timeout(secs.)><Port 1>... \n", argv[0]);
        exit(1);
    }

    timeout = atol(argv[1]);
    noPorts = argc - 2;

    servSock = (int *)malloc(noPorts * sizeof(int));

    maxDescriptor = -1;

    for (port = 0; port < noPorts; port++){
        portNo = atoi(argv[port + 2]);

        servSock[port] = CreateTCPServerSocket(portNo);

        if(servSock[port] > maxDescriptor)
            maxDescriptor = servSock[port];
    }

    printf("Starting server: Hit return to shutdown\n");
    while(running) {
        FD_ZERO(&sockSet);

        FD_SET(STDIN_FILENO, &sockSet);
        for(port = 0; port < noPorts; port++)
            FD_SET(servSock[port], &sockSet);

        setTimeout.tv_sec = timeout;
        setTimeout.tv_usec = 0;

        if(select(maxDescriptor + 1, &sockSet, NULL, NULL, &selTimout) == 0)
            printf("No echo request for %ld secs... Server still alive\n", timeout);
        else {
            if(FD_ISSET(STDIN_FILENO, &socket)){
                printf("Shutting down server\n");
                getchar();
                running = 0;
            }
            for (port = 0; port < noPorts; port++)
                if(FD_ISSET(servSock[port], &sockSet)){
                    printf("Request on port %d:", port);
                    HandleTCPClient(AcceptTCPConnection(servSock[port]));
                }
        }
    }
    for(port = 0; port < noPorts; port++)
        close(servSock[port]);

    free(servSock);
    exit(0);
}

[TCP/IP] 制限付きマルチタスク

サーバが作成するプロセスの数を制限する
L あらかじめ設定された数だけプロセスを作成する

#include "TCPEchoServer.h" /* ヘッダーファイルをインクルード */

void ProcessMain(int servSock); /* プロセスのメインプログラム */

int main(int argc, char *argv[]) {
    int servSock;
    unsigned short echoServPort;
    pit_t processID;
    unsigned int processLimit;
    unsigned int processCt;

    if (argc != 3) {
        fprintf(stderr, "Usage: %s <Server Port> <FORK LIMIT>\n", argv[0]);
        exit(1);
    }

    echoServPort = atoi(argv[1]);
    processLimit = atoi(argv[2]);

    servSock = CreateTCPServerSocket(echoServPort);

    for(processCt=0; processCt < processLimit; processCt++)
        if((processID == fork()) < 0)
            DieWithError("fork() failed");
        else if(processID == 0)
            ProcessMain(servSock);

    exit(0);
}

void ProcessMain(int servSock) {

    int clntSock;

    for(;;) /* 処理を繰り返し実行 */
    {
        clntSock = AcceptTCPConnection(servSock);
        printf("with child process: %d\n", (unsigned int) getpid());
        HandleTCPClient(clntSock);
    }
}

指定されたプロセスしか生成しないため、スケジューリングによるオーバーヘッドを削減できる。

[TCP/IP] クライアントごとにスレッド作成

クライアントごとに新しいプロセスを生成するのはコストが高い
OSはプロセス作成の度にメモリ、スタック、ファイルなどを状態をコピーしなければならないため

#include "TCPEchoServer.h"
#include <pthread.h> /* posixスレッドに必要 */

void *ThreadMain(void *arg);

struct ThreadArgs {
    int clntSock;
};

int main(int argc, char *argv[]){

    int servSock;
    int clntSock;
    unsigned short echoServPort;
    pthread_t threadID;
    struct ThreadArgs *threadArgs; /* スレッドの引数構造体 */

    if(argc != 2) {
        fprintf(stderr, "Usage: %s <SERVER PORT>\n", argv[0]);
        exit(1);
    }

    echoServPort = atoi(argv);

    servSock = CreateTCPServerSocket(echoServPort);

    for(;;){
        clntSock = AcceptTCPConnection(servSock);

        /* クライアント引数用にメモリを確保 */
        if((threadArgs = (struct ThreadArgs *) malloc(sizeof(struct ThreadArgs)))==NULL)
            DieWithError("malloc() failed")
        threadArgs -> clntSock = clntSock;

        /* クライアントスレッド生成 */
        if(pthread_create(&threadID, NULL, ThreadMain, (void *) threadArgs) != 0)
            DieWithError("pthread_create() failed");
        printf("with thread %ld\n", (long int) threadID);
    }
}

void *TheadMain(void *threadArgs) {
    int clntSock;

    pthread_detach(pthread_self());

    clntSock = ((struct ThreadArgs *) threadArgs) -> clntSock;
    free(threadArgs);

    HandleTCPClient(clntSock);

    return (NULL);
}

[TCP/IP] ソケットのマルチタスク

プロセスやスレッドという仕組みを利用すれば、1つのクライアント処理を独立して動作するサーバのコピーに任せることができる。
=> clientごとにプロセス、スレッド、制限付きマルチタスクを作成する

### クライアントごとにプロセスを作成
Unixでは新しいプロセスの作成にはfork()を使用する
クライアントからの接続を受けるたびに自身のコピーを作成する

#include "TCPEchoServer.h" /* ヘッダーファイルをインクルード */
#include <sys/wait.h> /* waitpid()に必要 */

int main(int argc, char *argv[]) {
    int servSock;
    int clntSock;
    unsigned short echoServPort;
    pit_t processID;
    unsigned int childProcCount = 0;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <Server Port>", argv[0]);
        exit(1);
    }

    echoServPort = atoi(argv[1]);

    servSock = CreateTCPServerSocket(echoServPort);

    for(;;) {
        clntSock = AcceptTCPConnection(servSock);
        if((processID = frok())< 0)
            DieWithError("fork() failed");
        else if (processID == 0) {
            close(servSock);
            HandleTCPClient(clntSock);

            exit(0);
        }

        printf("with child process: %d\n", (int) processID);
        close(clntSock);
        childProcCount++;

        while (childProcCount) {
            processID = waitpid((pid_t) - 1, NULL, WNOHANG);
            if(processID < 0)
                DieWithError("waitpid() failed");
            else if(processID == 0)
                break;
            else
                childProcCount--;
        }
    }
}
#include <stdio.h> /* printf(), fprintf() */
#include <sys/socket.h> /* socket(), bind(), connect() */
#include <arpa/inet.ht> /* sockaddr_in, inet_ntoa() */
#include <stdlib.h> /* atoi() */
#include <string.h> /* memset() */
#include <unistd.h> /* close() */

void DieWithError(char *errorMessage);
void HandleTCPClient(int clntSocket); /* TCPクライアント処理関数 */
int CreateTCPServerSocket(unsigned short port); /* TCPサーバソケット */
int AcceptTCPConnection(int servSock); /* TCP接続要求を受け付ける */
#include <sys/socket.h> /* socket(), bind(), connect() */
#include <arpa/inet.h> /* sockaddr_inとinet_ntoa() */
#include <string.h> /* memset() */

#define MAXPENDING 5 /* 未処理の接続要求の最大値 */

void DieWithError(char *errorMessage); /* エラー処理関数 */

int CreateTCPServerSocket(unsigned short port) {
    int sock; /* 作成するソケット */
    struct sockaddr_in echoServAddr; /* ローカルアドレス */

    /* 着信接続用のソケット作成 */
    if((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        DieWithError("socket() failed");

    /* ローカルアドレスの構造体作成 */
    memset(&echoServAddr, 0, seizeof(echoServAddr)); /* 構造体を0で埋める */
    echoServAddr.sin_family = AF_INET; /* インターネットアドレスファミリ */
    echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* ワイルドカード */
    echoServAddr.sin_port = htons(port); /* ローカルポート */

    /* ローカルアドレスへバインド */
    if(bind(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0)
        DieWithError("bind() failed");

    /* 「接続要求をリスン中」というマークをソケットにつける */
    if(listen(sock, MAXPENDING) < 0)
        DieWithError("listen() failed");

    return sock;
}
#include <stdio.h> /* printf() */
#include <sys/socket.h> /* accept() */
#include <arpa/inet.h> /* sockaddr_in, inet_ntoa() */

void DieWithError(char *errorMessage); /* エラー処理関数 */

int AcceptTCPConnection(int servSock) {
    int clntSock; /* クライアントのソケットディクリプタ */
    struct sockaddr_in echoClntAddr; /* クライアントのアドレス */
    unsigned int clntLen; /* クライアントのアドレス構造体の長さ */

    /* 入出力のパラメータのサイズをセット */
    clnLen = sizeof(echoClntAddr);

    /* クライアントからの接続要求を待つ */
    if((clntSock = accept(servSock, (struct sockaddr *) &echoClntAddr, &clntLen)) < 0)

    /* clntSockはクライアントに接続済 */
    printf("Handling client %s\n", inet_ntoa(echoClntAddr.sin_addr));

    return clntSock;

}

[TCP/IP] タイムアウト

unsigned int arlarm(unsigned int secs)

alermはタイマーを開始する
タイマーが切れると、SIGALARMシグナルがプロセスに送られ、SIGALRMハンドラ関数がある場合はそれが実行される

#include <stdio.h> /* printf(), fprintf()に必要 */
#include <sys/socket.h> /* socket(), bind(), connect()に必要 */
#include <arpa/inet.h> /* sockaddr_in, inet_ntoaに必要 */
#include <stdlib.h> /* atoi()に必要 */
#include <string.h> /* memset()に必要 */
#include <unistd.h> /* close()に必要 */
#include <fcntl.h> /* fcntl()に必要 */
#include <sys/file.h> /* O_NONBLOCK, FASYNCに必要 */
#include <signal.h> /* signal(),SIGLRMに必要 */
#include <error.h> /* errnoに必要 */

#define ECHOMAX 255 /* エコー文字列の最大長 */
#define TIMEOUT_SECS 2 /* 再送信までの秒数 */
#define MAXTRIES 5 /* 最大試行回数 */

int tries=0; /* エコー文字列の最大長 */

void DieWithError(char *errorMessage); /* エラー処理関数 */
void CatchAlarm(int ignored);

int main(int argc, char *argv[]){
    int sock; /* ソケットディスクリプタ */
    struct sockaddr_in echoServAddr; /* エコーサーバアドレス */
    struct sockaddr_in fromAddr; /* エコー送信元のアドレス */
    unsigned short echoServPort; /* エコーサーバポート */
    unsigned int fromSize; /* recvfrom()アドレスの入出力サイズ */
    struct sigaction myAction; /* シグナルハンドラの設定用 */
    char *servIP;  /* サーバのIP */
    char *echoString; /* 送信する文字列 */
    char echoBuffer[ECHOMAX+1]; /* エコー文字列受信用バッファ */
    int echoStringLen; /* エコー文字列の長さ */
    int respStringLen; /* 受信した応答の長さ */

    /* 引数が正しいか確認 */
    if((argc < 3) || (argc > 4)){
        fprintf(stderr, "Usage: %s <Server IP><Echo Word>[<Echo Port>]\n", argv[0]);
        exit(1);
    }

    servIP = argv[1]; /* 1つ目の引数: サーバのIP */
    echoString = argv[2]; /* 2つ目の引数: エコー文字列 */

    if((echoStringLen = strlen(echoString)) > ECHOMAX) /* 入力した文字列の長さをチェック */
        DieWithError("Echo word too long");

    if (argc == 4)
        echoServPort = atoi(argv[3]); /* 指定ポートがあれば使用 */
    else
        echoServPort = 7; /* 7はエコーサービスのwellknown port */

    /* UDPデータグラムソケット作成 */
    if((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    /* アラームシグナル用シグナルハンドラのセット */
    myAction.sa_handler = CatchAlarm;
    if(sigfillset(&myAction.sa_mask) < 0) /* ハンドラ内では全てをブロック */
        DieWithError("sigfillset() failed");
    myAction.sa_flags = 0;

    if(sigaction(SIGALRM, &myAction, 0) < 0) /* ハンドラ内では全てをブロック */
        DieWithError("sigaction() failed for SIGALRM");

    /* サーバのアドレス構造体 */
    memset(&echoServAddr, 0, sizeof(echoServAddr)); /* 構造体に0埋め */
    echoServAddr.sin_family = AF_INET; /* インターネットアドレスファミリ */
    echoServAddr.sin_addr.s_addr = inet_addr(servIP); /* サーバIP */
    echoServAddr.sin_port = htons(echoServPort); /* サーバポート */

    /* 文字列をサーバに送信 */
    if (sendto(sock, echoString, echoStringLen, 0, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) != echoStringLen)
        DieWithError("sendto() sent a different number of bytes than expected");        

    /* 応答を受信 */
    fromSize = sizeof(fromAddr);
    alarm(TIMEOUT_SECS);
    while((respStringLen = recvfrom(sock, echoBuffer, ECHOMAX, 0,
        (struct sockaddr *) &fromAddr, &fromSize)) < 0)
        if (errno = EINTR){
            if (tries < MAXTRIES) {
                printf("timed out, %d more tries..\n", MATRIES - tries);
                if(sendto(sock, echoString, echoStringLen, 0, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) != echoStringLen)
                    DieWithError("sendto() failed");
                alarm(TIMEOUT_SECS);
            }
            else
                DieWithError("No Response");
        }
        else
            DieWithError("recvfrom() failed");
        alarm(0);

        /* 受信データをNULL文字で終端させる */
        echoBuffer[respStringLen] = '\0';
        printf("Received: %s\n", echoBuffer);

        close(sock);
        exit(0);
}

void CatchAlarm(int ignored) {
    tries += 1;
}

[TCP/IP] ノンブロッキングI/O

ソケットの呼び出しは、デフォルトでは要求されたアクションが完了するまでプログラムの実行をブロックする
ブロックされた関数のプロセスはOSによって一時停止の状態に置かれる
=> 望ましくないブロッキング動作を制御する仕組みが必要な時に使われるのがノンブロッキングソケット

int fcntl(int socket, int. command, long argument)
L commandにF_SETFLを指定し、argumentにフラグ名を指定する

### 非同期I/O
SIGIOシグナルを送信することで、ソケットで発生したI/O関連のイベントをプロセスに通知する

#include <stdio.h> /* printf(), fprintf()に必要 */
#include <sys/socket.h> /* socket(), bind(), connect()に必要 */
#include <arpa/inet.h> /* sockaddr_in, inet_ntoaに必要 */
#include <stdlib.h> /* atoi()に必要 */
#include <string.h> /* memset()に必要 */
#include <unistd.h> /* close()に必要 */
#include <fcntl.h> /* fcntl()に必要 */
#include <sys/file.h> /* O_NONBLOCK, FASYNCに必要 */
#include <signal.h> /* signal(),SIGLRMに必要 */
#include <error.h> /* errnoに必要 */

#define ECHOMAX 255 /* エコー文字列の最大長 */

void DieWithError(char *errorMessage); /* エラー処理関数 */
void UseIdleTime(); /* アイドル時間を利用する関数 */
void SIGIOHandler(int signalType); /* SIGIOのハンドラ関数 */

int sock; /* ソケット */

int main(int argc, char *argv[]){
    struct sockaddr_in echoServAddr; /* ローカルアドレス */
    unsigned short echoServPort; /* サーバのポート番号 */
    struct sigaction handler; /* シグナル処理のアクションを定義 */

    if(argc != 2) { /* 引数が正しいかの確認 */
        fprintf(stderr, "Usage: %s <Server Port\n>", argv[0]);
        exit(1);
    }

    echoServPort == atoi(argv[1]); /* ローカルポート */

    /* データグラムの送受信に使うソケットを作成 */
    if((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
        DieWithError("socket() failed");

    /* サーバのアドレス構造体をセットアップ */
    memset(&echoServAddr, 0, sizeof(echoServAddr)); /* 構造体を0埋め */
    echoServAddr.sin_family = AF_INET; /* インターネットアドレスファミリ */
    echoServAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* ワイルドカードを使用 */
    echoServAddr.sin_port = htons(echoServPort); /* ローカルポート */

    /* ローカルアドレスへのインバインド */
    if(bind(sock, (struct sockaddr *) &echoServAddr, sizeof(echoServAddr)) < 0)
        DieWithError("bind() failed");

    /* SIGIOのシグナルハンドラを設定 */
    handler.sa_handler = SIGIOHandler;
    /* 全シグナルをマスクするマスクを作成 */
    if(sigfillset(&handler.sa_mask) < 0)
        DieWithError("sigfillset() failed");

    /* フラグなし */
    handler.sa_flags = 0;

    if(sigaction(SIGIO, &handler, 0) < 0)
        DieWithError("sigaction() failed for SIGIO");

    /* ソケットを所有してSIGIOメッセージを受信させる */
    if (fcntl(sock, F_SETIWN, getpid()) < 0)
        DieWithError("Unable to set process owner to us");

    /* ノンブロッキングI/OとSIGIO送信をセットアップ */
    if (fcntl(sock, F_SETFL, O_NONBLOCK | FASYNC) < 0)
        DieWithError("Unable to put client sock into nonblocking/async mode");

    /* 処理を離れて他の仕事をする */

    for(;;)
        UseIdleTime();
}

void UseIdleTime() {
    printf(".\n");
    sleep(3);
}

void SIGIOHandler(int signalType) {
    struct sockaddr_in echoClntAddr; /* データグラムの送信元アドレス */
    unsigned int clntLen; /* アドレス長 */
    int recvMsgSize; /* データグラムのサイズ */
    char echoBuffer[ECHOMAX]; /* データグラムのバッファ */

    do {
        /* 入出力パラーメタのサイズを設定 */
        clntLen = sizeof(echoClntAddr);

        if((recvMsgSize = recvfrom(sock, echoBuffer, ECHOMAX, 0, (struct sockaddr *) &echoClntAddr, &clntLen)) < 0){
            /* recvfrom()がブロックしようとした場合(許容される唯一のエラー) */
            if(errno != EWOULDBLOCK)
                DieWithError("recvfrom() failed");
        } else {
            printf("Handling client %s\n", inet_ntoa(echoClntAddr.sin_addr));

            if(sendto(sock, echoBuffer,recvMsgSize, 0 (struct sockaddr *) &echoClntAddr, sizeof(echoClntAddr)) != recvMsgSize)
                DieWithError("sendto() failed");
        }

    } while (recvMsgSize >= 0);
    /* 全ての受信が完了 */
}