C言語の複数ソケット通信、FD監視と自動再接続

Uncategorized

ここでは、複数のソケット通信を同時に使う方法と、それぞれのソケット通信が切断されたときに自動で再接続する方法のコード例を見ていきます。

まず、サーバー側のコード例です。このコードは、1つのポートで最大10個のクライアントからデータを受信するものです。1 クライアントからデータが来たら即座に処理するために、select関数を使ってFD(ファイルディスクリプター)を監視しています。また、クライアントが切断されたらclose関数でFDを閉じて再利用できるようにしています。

※エラー処理は明らかに不足していますのでもし流用を考えている場合はコードをレビューしてから使用してください。

// サーバー側
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
 
#define PORT 12345 // ポート番号
#define MAX_CLIENTS 10 // 最大クライアント数
 
int main(void)
{
    int server_fd; // サーバー用FD
    int client_fd[MAX_CLIENTS]; // クライアント用FD配列
    int max_fd; // FDの最大値
    struct sockaddr_in server_addr; // サーバー用アドレス構造体
    struct sockaddr_in client_addr; // クライアント用アドレス構造体
    socklen_t client_len; // クライアント用アドレス構造体の長さ
    fd_set fds, readfds; // FD集合
    char buf[256]; // バッファ
    int i, n;
 
    // FD集合を初期化する
    FD_ZERO(&readfds);
 
    // サーバー用FDを作成する(IPv4, TCP)
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket");
        exit(1);
    }
 
    // サーバー用FDをFD集合に追加する
    FD_SET(server_fd, &readfds);
 
    // FDの最大値を更新する(今はサーバー用FDだけなのでそれが最大)
    max_fd = server_fd;
 
    // アドレス構造体にポート番号とIPアドレスを設定する(INADDR_ANYは任意のIPアドレス)
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
    // アドレス構造体とサーバー用FDを紐付ける(bind)
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind");
        exit(1);
    }
 
    // 接続待ち状態に入る(listen)
    if (listen(server_fd, MAX_CLIENTS) == -1) {
        perror("listen");
        exit(1);
    }
 
    printf("Waiting for connections...\n");
 
    while (1) {
        fds = readfds; // select関数は引数を書き換えるので毎回コピーして渡す
 
        // select関数で監視対象のFDに変化があるまで待つ(タイムアウトなし)
        if (select(max_fd + 1, &fds, NULL, NULL) == -1) { perror("select"); exit(1); }
    // サーバー用FDに変化があった場合(クライアントからの接続要求)
    if (FD_ISSET(server_fd, &fds)) {
        // クライアントとの接続を確立する(accept)
        client_len = sizeof(client_addr);
        for (i = 0; i < MAX_CLIENTS; i++) {
            if (client_fd[i] == 0) { // 空いているFDを探す
                client_fd[i] = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
                if (client_fd[i] == -1) {
                    perror("accept");
                    exit(1);
                }
                break;
            }
        }
        if (i == MAX_CLIENTS) { // 空きがなかった場合
            printf("Too many clients.\n");
            continue;
        }
 
        // クライアント用FDをFD集合に追加する
        FD_SET(client_fd[i], &readfds);
 
        // FDの最大値を更新する
        if (client_fd[i] > max_fd) {
            max_fd = client_fd[i];
        }
 
        printf("Client %d connected.\n", i + 1);
    }
 
    // クライアント用FDに変化があった場合(データの受信)
    for (i = 0; i < MAX_CLIENTS; i++) {
        if (client_fd[i] > 0 && FD_ISSET(client_fd[i], &fds)) {
            // データを受信する(recv)
            n = recv(client_fd[i], buf, sizeof(buf), 0);
            if (n == -1) {
                perror("recv");
                exit(1);
            }
            if (n == 0) { // クライアントが切断された場合
                // クライアント用FDを閉じる(close)
                close(client_fd[i]);
                // クライアント用FDをFD集合から削除する
                FD_CLR(client_fd[i], &readfds);
                // クライアント用FD配列を空にする
                client_fd[i] = 0;
                printf("Client %d disconnected.\n", i + 1);
            } else { // データが受信できた場合
              printf("Received from client %d: %s\n", i + 1, buf);
 
              // 受信したデータに応じて処理を行う(ここでは省略)
 
              // データを送信する(send)(ここではエコーバック)
              n = send(client_fd[i], buf, n, 0);
              if (n == -1) {
                  perror("send");
                  exit(1);
              }
          }
      }
  }
  return 0;
}

コメント

タイトルとURLをコピーしました