🇯🇵 日本語 | 🇺🇸 English
広告スペース

C言語 簡易クライアント・サーバ

テキストを送り合う最小のプログラムを、サーバ側とクライアント側の両方で作る。

クライアント・サーバとは

ネットワーク通信は「待ち受ける側(サーバ)」と「接続しに行く側(クライアント)」の役割分担で成り立ちます。まず自分のPC上で両方のプログラムを動かし、テキストをやり取りしてみます。
クライアント
echo_client

TCP:12345
サーバ
echo_server

今回作るもの(エコーサーバ)

なぜエコーから? 送受信が両方動くことを最短で確認できるので、ネットワーク初学者の入り口として定番のサンプルです。

APIの流れ(両側)

サーバとクライアントで呼ぶ関数が少し異なります。左右を並べて覚えましょう。
サーバ側
  1. socket() — ソケットを作る
  2. bind() — ポートに紐付け
  3. listen() — 待ち受け開始
  4. accept() — 接続を受け取る
  5. recv() / send()
  6. close()
クライアント側
  1. socket() — ソケットを作る
  2. connect() — サーバに接続
  3. send() / recv()
  4. close()
ポイント: bind/listen/accept がサーバ特有、connect がクライアント特有。それ以外は共通です。

サーバプログラム (echo_server.c)

クライアントから送られた文字列をそのまま返す最小のサーバです。1クライアント相手にして終了します。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 12345
#define BUF_SIZE 1024

int main(void) {
    // 1. ソケット作成
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd < 0) { perror("socket"); return 1; }

    // (推奨)アドレス再利用を許可
    int yes = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

    // 2. 待ち受けアドレスを準備
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  // すべてのIPで待ち受け
    addr.sin_port = htons(PORT);

    // 3. bind — ポートを紐付け
    if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("bind"); return 1;
    }

    // 4. listen — 待ち受け開始
    if (listen(listen_fd, 1) < 0) {
        perror("listen"); return 1;
    }
    printf("ポート %d で待ち受け中...\n", PORT);

    // 5. accept — 1クライアントを受け入れ
    struct sockaddr_in client_addr;
    socklen_t clen = sizeof(client_addr);
    int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &clen);
    if (conn_fd < 0) { perror("accept"); return 1; }
    printf("クライアント接続: %s\n", inet_ntoa(client_addr.sin_addr));

    // 6. 受信してそのまま返す(エコー)
    char buf[BUF_SIZE];
    ssize_t n;
    while ((n = recv(conn_fd, buf, BUF_SIZE - 1, 0)) > 0) {
        buf[n] = '\0';
        printf("受信: %s", buf);
        send(conn_fd, buf, n, 0);  // そのまま送り返す
    }

    // 7. 後片付け
    close(conn_fd);
    close(listen_fd);
    printf("接続を閉じました\n");
    return 0;
}
INADDR_ANY は「自分のどのIPアドレス宛でも受ける」意味。開発中はこれでOKです。特定のIPに限定する場合は inet_addr("127.0.0.1") などを指定します。

クライアントプログラム (echo_client.c)

標準入力から1行読み取り、サーバに送ってエコーを受信するシンプルな例です。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 12345
#define BUF_SIZE 1024

int main(void) {
    // 1. ソケット作成
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) { perror("socket"); return 1; }

    // 2. 接続先(localhost:12345)を設定
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 3. connect — サーバに接続
    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("connect"); return 1;
    }
    printf("サーバに接続しました。メッセージを入力してEnter:\n");

    // 4. 標準入力から1行読み取って送信
    char msg[BUF_SIZE];
    if (fgets(msg, BUF_SIZE, stdin) == NULL) return 1;
    send(sockfd, msg, strlen(msg), 0);

    // 5. 返ってきた文字列を受信
    char buf[BUF_SIZE];
    ssize_t n = recv(sockfd, buf, BUF_SIZE - 1, 0);
    if (n > 0) {
        buf[n] = '\0';
        printf("サーバからの返答: %s", buf);
    }

    // 6. 切断
    close(sockfd);
    return 0;
}
注意: 127.0.0.1(localhost)に接続しているので、同じPC上でサーバを動かしておく必要があります。別のマシンに接続する場合はそのマシンのIPアドレスに変えてください。

ビルドと動作確認

ターミナルを2枚開き、片方でサーバ、もう片方でクライアントを起動します。

ターミナル1(サーバ側)

$ gcc echo_server.c -o echo_server $ ./echo_server ポート 12345 で待ち受け中... クライアント接続: 127.0.0.1 受信: hello 接続を閉じました

ターミナル2(クライアント側)

$ gcc echo_client.c -o echo_client $ ./echo_client サーバに接続しました。メッセージを入力してEnter: hello サーバからの返答: hello
うまくいかないとき:
bind: Address already in use — ポートが他のプロセスで使用中。少し待って再試行、またはポート番号を変更。
connect: Connection refused — サーバが起動していないか、ポート番号/IPが違う。
• 繋がるけど応答なし — サーバが accept を呼ぶ前にクライアントを起動していないか確認。

チャレンジ課題

課題1: 大文字化サーバ
エコーではなく、受信した文字列を全部大文字にして返すサーバを作れ。toupper<ctype.h>)を使う。
課題2: 複数回やり取り
クライアントが「quit」と送るまで、何度でも送受信を繰り返すようにサーバとクライアントを改造せよ。
課題3: 複数クライアント対応
サーバが accept の後に fork() で子プロセスを作り、複数クライアントを同時に相手できるようにせよ。親プロセスは accept ループに戻る。
課題4: ポート番号をコマンドライン引数に
./echo_server 8080 のように起動できるよう、argv[1] をポート番号として読み取る。argc/argvの講座を参照。
次へ: HTTPクライアントを作る →
この記事をシェア
X(Twitter)でシェアはてブ