ブロックヘッダーの取得とヘッダーレスポンス

どのノードも初めてネットワークに接続する際、取得して検証すべき最も重要なデータはブロックヘッダーである。
ブロックヘッダーをダウンロード数ルト、複数のノードに完全なブロック非同期に要求してブロックのダウンロードを並列化できる
ブロックヘッダーを取得するコマンドはgetheadersと呼ばれる

7f110100: Protocol version, 4bytes, little-endian, 70015
01: Number of hashes, varint
a35b…00: Starting block, little-endian
0000…00: Ending block, little-endian

class GetHeaderMessage:
    command = b'getheaders'

    def __init__(self, version=70015, num_hashes=1,
        start_block=None, end_block=None):
        self.version = version
        self.num_hashes = num_hashes
        if start_block is None:
            raise RuntimeError('a start block is required')
        self.start_block = start_block
        if end_block is None:
            self.end_block = b'\x00' * 32
        else:
            self.end_block = end_block

    def serialize(self):
        result = int_to_little_endian(self.version, 4)
        result += encode_varint(self.num_hashes)
        result += self.start_block[::-1]
        result += self.end_block[::-1]
        return result

### ヘッダーレスポンス
ノードを作成し、ハンドシェイクした後、ヘッダーを要求できる

from io import BytesIO
from block import Block, GENESIS_BLOCK
from network import SimpleNode, GetHeadersMessage
node = SimpleNone('', testnet=False)
node.handshake()
genesis = Block.parse(BytesIO(GENESIS_BLOCK))
getheaders = GetHeadersMessage(start_block=genesis.hash())
node.send(getheaders)

## headrsメッセージ
02: Number of block headers
00…67: Block header
00: Number of transactions (always 0)

headersメッセージはvarintのヘッダー数(1~2000)
各ブロックヘッダーは80bytes

class HeadersMessage:
    command = b'headers'

    def __init__(self, blocks):
        self.blocks = blocks

    @classmethod
    def parse(cls, stream):
        num_headers = read_varint(stream)
        blocks = []
        for _ in range(num_headers):
            blocks.append(Block.parse(stream))
            num_txs = read_varint(stream)
            if num_txs != 0:
                raise RuntimeError('number of txs not 0')
        return cls(blocks)
from io import BytesIO
from network import SimpleNode, GetHeaderMessage, HeadersMessage
from block import Block, GENESIS_BLOCK, LOWEST_BITS
from helper import calculate_new_bits
previous = Block.parse(BytesIO(GENESIS_BLOCK))
first_epoch_timestamp = previous.timestamp
expected_bits = LOWEST_BITS
count = 1
node = SimpleNode('', testnet=False)
node.handshake()
for _ in range(19):
    getheaders = GetHeadersMessage(start_block=previous.hash())
    node.send(getheaders)
    headers = node.wait_for(HeadersMessage)
    for header in headers.blocks:
        if not header.check_pow():
            raise RuntimeError('bad PoW at block {}'.format(count))
        if header.prev_block != previous.hash():
            raise RuntimeError('discontinuous block at {}'.format(count))
        if count % 2016 == 0:
            time_diff = previous.timestamp - first_epoch_timestamp
            expected_bits = calculate_new_bits(previous.bits, time_diff)
            print(expected_bits.hex())
            first_epoch_timestamp = header.timestamp
        if header.bits != expected_bits:
            raise RuntimeError('bad bits at block {}'.format(count))
        previous = header
        count += 1

VerAckMessageは最小のネットワークメッセージ。AckはAcknoldgeの略称で確認の意味
VerAckMessage :The verack message acknowledges a previously-received version message, informing the connecting node that it can begin to send other messages.
※https://bitcoin-s.org/api/org/bitcoins/core/p2p/index.html

ネットワークハンドシェイクと接続

### ネットワークハンドシェイク
ネットワークハンドシェイクの方法
– AがBに接続するためにversionメッセージを送信
– Bがversionメッセージを受信し、verackメッセージで応答し、自身のversionメッセージを送信
※verackはボディー(ペイロード)が無く、メッセージ名だけのメッセージ
– Aはversionメッセージとverackメッセージを受信し、verackメッセージを送信
– Bはverackメッセージを受信し、通信を継続
ハンドシェイク後に通信できる
不正なトランザクションまたはブロックを送信した場合、通信が禁止または切断される可能性がある

### ネットワークへの接続

import socket
from network immport NetworkEnvelop, VersionMessage
host = ''
port = 18333
socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.connect((host, port))
stream = socket.makefile('rb', None)
version = VersionMessage()
envelop = NetworkEnvelop(version.command, version.serialize())
socket.sendall(envelop.serialize())
while True:
    new_message = NetworkEnvelope.parse(stream)
    print(new_message)
class VerAckMessage:
    command = b'verack'

    def __init__(self):
        pass
    
    @classmethod
    def parse(cls, s):
        return cls()
    
    def serialize(self):
        return b''

SimpleNone

class SimpleNone:

    def __init__(self, host, port=None, testnet=False, logging=False):
        if port is None:
            if testnet:
                port = 18333
            else:
                port = 8333
        self.testnet = testnet
        self.logging = logging
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((host, port))
        self.stream = self.socket.makefile('rb', None)
    
    def send(self, message):
        envelope = NetworkEnvelop(
            message.command, message.serialize(), testnet=self.testnet)
        if self.logging:
            print('sending: {}'.format(envelop))
        self.socket.sendall(envelope.serialize())
    
    def read(self):
        envelop = NetworkEnvelop.parse(self.stream, testnet=self.testnet)
        if self.logging:
            print('receiving: {}'.format(envelop))
        return envelope

    def wait_for(self, *message_classes):
        command = None
        command_to_class = {m.command: m for m in message_classes}
        while command not in command_to_class.keys():
            envelope = self.read()
            command = envelop.command
            if command == VersionMessage.command:
                self.send(VerAckMessage())
            elif command == PingMessage.command:
                self.send(PongMessage(envelope.payload))
        return command_to_class[command].parse(envelope.stream())

他のNodeとのハンドシェイク

from network import SimpleNode, VersionMessage
node = SimpleNone('', testnet=True)
version = VersionMessage()
node.send(version)
verack_received = False
version_received = False
while not verack_received and not version_received:
    message = node.wait_for(VersionMessage, VerArckMessage)
    if message.command == VerArckMessage.command:
        verack_received = True
    else:
        version_received = True
        node.sed(VerAckMessage())

handshake

   def handshake(self):
        version = VersionMessage()
        node.send(version)
        self.wait_for(VerAckMessage)

ペイロード

ペイロードとはメッセージを送受信するデータ本体

Protocol version: 4bytes, little-endian
Network services of sender: 8bytes, little-endian
Timestamp, 8bytes, little-endian
Network service of receiver: 8bytes, little-endian
Network address of receiver, 16bytes, IPv4

Network port of receiver: 2bytes
Network services of sender: 8bytes, little-endian
Network address of sender: 16bytes, IPv4

Network port of sender: 2bytes
Nonce: 8bytes, used for communicating response
User agent
Optional flag of relay, based on BIP37(ブルームフィルターで使用)

VersionMessageクラス

class VersionMessage:
    command = b'version'

    def __init__(self, version=70015, services=0, timestamp=None,
                receiver_services=0,
                receiver_ip=b'\x00\x00\x00\x00', receiver_port=8333,
                sender_services=0,
                sender_ip=b'\x00\x00\x00\x00', sender_port=8333,
                nonce=None, user_agent=b'/programmingbitcoin:0.1/',
                latest_block=0, relay=False):
        self.version = version
        self.service = service
        if timestamp is None:
            self.timestamp = int(time.time())
        else:
            self.timestamp = timestamp
        self.receiver_services = receiver_services
        self.receiver_ip = receiver_ip
        self.receiver_port = receiver_port
        self.sender_services = sender_services
        self.sender_ip = sender_ip
        self.sender_port = sender_port
        if nonce is None:
            self.nonce = int_to_little_endian(randint(0, 2**64), 8)
        else:
            self.nonce = nonce
        self.user_agent = user_agent
        self.latest_block = latest_block
        self.relay

serialize

    def serialize(self):
        result = int_to_little_endian(self.version, 4)
        result += int_to_little_endian(self.services, 8)
        result += int_to_little_endian(self.timestamp, 8)
        result += int_to_little_endian(self.receiver_services, 8)
        result += b'\x00' * 10 + b'\xff\xff' + self.receiver_ip
        result += self.receiver_port.to_bytes(2, 'big')
        result += int_to_little_endian(self.sender_services, 8)
        result += b'\x00' * 10 + b'\xff\xff' + self.sender_ip
        result += self.sender_port.to_bytes(2, 'big')
        result += self.nonce
        result += encode_varint(len(self.user_agent))
        result += self.user_agent
        result += int_to_little_endian(self.latest_block, 4)
        if self.relay:
            result += b'\x01'
        else:
            result += b'\x00'
        return result

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

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