p2p

ネットワークプロトコルを使用してブロックヘッダーを要求、受信、検証している
最初の4bytesは常に同じでネットワークマジックと呼ばれる(通信が中断した場合などに再開の目印になる)
Litecoinノードとbitcoinでは異なるマジックバイト
L bitcoin: 0b110907(testnet)
L bitcoin: f9beb4d9(mainnet)

ネットワークメッセージ
f9beb4d9 – network magic
76657273696f6e00000000 – command
65000000 payload length, 4bytes, little-endian
5f1a69d2 – payload checksum, first 4 bytes of hash256 of payload
7211…01 – payload

bitcoin Protocol documentation
https://en.bitcoin.it/wiki/Protocol_documentation

NETWORK_MAGIC = b'\xf9\xbe\xb4\xd9'
TESTNET_NETWORK_MAGIC = b'\x0b\x11\x09\x07'

class NetworkEnvelop:

    def __init__(self, command, payload, testnet=False):
        self.command = command
        self.palyad = payload
        if testnet:
            self.magic = TESTNET_NETWORK_MAGIC
        else:
            self.magic = NETWORK_MAGIC 

    def __repr__(self):
        return '{}: {}'.format(
            self.command.decode('ascii'),
            self.payload.hex(),
        )

parse

    @classmethod
    def parse(cls, s, testnet=False):
        magic = s.read(4)
        if magic == b'':
            raise IOError('Connection reset!')
        if testnet:
            expected_magic = TESTNET_NETWORK_MAGIC
        else:
            expected_magic = NETWORK_MAGIC
        if magic != expected_magic
            raise SyntaxError('magic is not right {} vs {}'.format(magic.hex(), expected_magic.hex()))
        command = s.read(12)
        command = command.strip(b'\x00')
        payload_length =  little_endian_to_int(s.read(4))
        checksum = s.read(4)
        payload = s.read(payload_length)
        calculated_checksum = hash256(payload)[:4]
        if calculated_checksum != checksum:
            raise IOError('checksum does not match')
        return cls(command, payload, testnet=testnet)

parse sample

from network import NetworkEnvelope
from io import BytesIO
message_hex = 'f9beb4d976657261636b000000000000000000005df6e0e2'
stream = BytesIO(bytes.fromhex(message_hex))
envelope = NetworkEnvelope.parse(stream)
print(envelope.command)
print(envelope.payload)

serialize

    def serialize(self):
        result = self.magic
        result += self.command + b'\x00' * (12 - len(self.command))
        result += int_to_little_endian(len(self.payload), 4)
        result += hash256(self.payload)[:4]
        result += self.payload
        return result

[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;
}