ゾンビでもわかるC言語プログラミング

C言語入門者の応援をします

[C言語] TCPネットワークプログラミング 簡易サーバー作成

Index

1. はじめに

本稿では、socketシステムコールを使用して、簡単なechoサーバをC言語で作成する。

2. 使用するシステムコール

今回のTCP通信のネットワークプログラミングには、以下のシステムコールを使用する。

システムコール 使用用途
socket() ソケットを作成する
bind() アドレスに名前をつける
listen() 接続を待ち受ける
accept() 接続を受け付ける
write() or send() ソケットに書き込む
read() or recv() ソケットから読み込む
close() ソケットを閉じる

3. socketシステムコール

socketシステムコールでは、通信のためのソケット(出入り口)を作成する。
成功した場合、ソケットのファイルディスクリプターを返す。
エラーが発生した場合は -1 を返し、errno を適切に設定する。

socketシステムコールの書式

socketシステムコールの書式は以下になる。

#include <sys/types.h> // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>
int socket(int domain, int type, int protocol);  

domain

domainには、プロトコルファミリを指定する。
代表的なプロトコルファミリを以下に記す。

プロトコルファミリ 使用目的
AF_INET IPv4 インターネットプロトコル
AF_INET6 IPv6 インターネットプロトコル
AF_UNIX AF_LOCAL ローカル通信
AF_PACKET 低レベルのパケットインターフェース

今回は、IPv4を使用するので、AF_INET を指定する。

type

typeには、通信方式を指定する。
代表的な通信方式は以下になる。

通信方式 用途
SOCK_STREAM TCP通信
SOCK_DGRAM UDP通信
SOCK_RAW 生のネットワークプロトコルへのアクセス

今回はTCP通信を行うので、SOCK_STREAM を指定する。

protocol

プロトコルの公式番号を指定する。
プロトコルファミリとソケットタイプの組み合わせによって決まり、自明の時は 0 を指定する。
今回は、上記からIPv4のTCP通信と自明なので、0 を指定する。

4. bindシステムコール

bindシステムコールは、socketシステムコールで作成したソケットにアドレスを割り当てる。
成功した場合は 0 を返す。
エラーが発生した場合は -1 が返し、errno を適切に設定する。

bindシステムコールの書式

bindシステムコールの書式は以下になる。

#include <sys/types.h>    // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

三番目の引数である socklen_t は実際には、int である。

sockaddr_in構造体

ソケットに割り当てるアドレスは、第二引数に指定する。 AF_INET の場合は、以下の構造体になる。

struct sockaddr_in {
    sa_family_t    sin_family; // アドレスファミリ: AF_INET
    in_port_t      sin_port;   // ポート番号
    struct in_addr sin_addr;   // インターネットアドレス
};

struct in_addr {
    uint32_t       s_addr;     // アドレス
};

5. listenシステムコール

listenシステムコールでは、ソケットを接続待ちソケットとする。
接続待ちソケットは、acceptシステムコールで接続を受け付けられるソケットである。
成功した場合は 0 を返す。
エラーが発生した場合は -1 が返し、errno を適切に設定する。

listenシステムコールの書式

listenシステムコールの書式は以下になる。

#include <sys/types.h> // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>

int listen(int sockfd, int backlog);

backlog

backlogには、sockfd についての保留中の接続のキューの最大長を指定する。

6. acceptシステムコール

acceptシステムコールは、接続待ちソケット宛の接続要求のキューから先頭の接続要求を取り出し、接続済ソケットを作成する。
成功した場合、接続済ソケットのファイルディスクリプターを返す。
エラーが発生した場合は -1 を返し、errno を適切に設定する。

acceptシステムコールの書式

acceptシステムコールの書式は以下になる。

#include <sys/types.h> // 必須ではないが、互換性のためにインクルードする
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

7. ソースコード

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    int sockfd, new_sockfd;
    struct sockaddr_in addr, client_addr;
    socklen_t len;
    char c[1];

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    if(sockfd == -1) {
        perror("socket");
        exit(-1);
    }

    addr.sin_family = AF_INET;      // AF_INETを指定
    addr.sin_port = htons(12345);   // ポート番号を 123445 に指定
    addr.sin_addr.s_addr = 0;       // 自動的に自分のIPを割り当てる

    if( bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        perror("bind");
        exit(-1);
    }

    if( listen(sockfd, 5) == -1) {
        perror("listen");
        exit(-1);
    }

    while(1) {
        len = sizeof(client_addr);
        new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &len);
        if(new_sockfd == -1) {
            perror("accept");
            continue;
        }

        printf("from %s port %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        while(read(new_sockfd, c, 1) > 0) {
            write(1, c, 1);
        }

        close(new_sockfd);
    }

    close(sockfd);

    return 0;
}

8. 実行画面

プログラムを実行し、telnetでプログラムが待ち受けるポートに接続する。

% telnet localhost 12345
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello!!

telnetで "Hello!" と入力した時のプログラムの出力は以下になる。

% ./a.out
from 127.0.0.1 port 49195
Hello!!

参照