[C言語]POSIXスレッドを用いたechoサーバ



サーバの仕組みとスレッド

通常、Webサーバなどは親プロセスがポートをListenし、接続があったら、子プロセスを生成し、その後の(クライアントへのデータ送信等の)処理は、子プロセスに任せるというフローをとる。

そうすることで、親プロセスはクライアントへデータ送信が終わるのを待つ必要がなくなり、どんどん接続を受けることが可能になる。
接続数が多くなると、当然、子プロセスが多くなり、サーバへの過負荷となる。

そこで、子プロセスではなく、スレッドでクライアントの処理を行うと負荷低減につながる((スレッドの生成は子プロセスの生成より軽量))

echoサーバのPOSIXスレッド化

[C言語]Socket間通信 echoサーバを作るで作成したechoサーバにptheadを組み込み、複数クライアントに対して同時処理ができるように改良する。POSIXスレッドについて知りたければ、まずは[C言語]POSIXスレッドプログラミングを参照。

課題

  • 1回の通信が終わっても、Listenし続けるように変更する
  • threadで返答を返すよう変更する

コード

#include <sys/types.h>
#include <sys/socket.h> /* socket(), bind(), listen(), accept(), recv() */
#include <netinet/in.h> /* htons() */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>     /* exit() */
#include <pthread.h>    /* pthread_create(), pthread_detach() */

#define PORT    8823    /* Listenするポート */
#define MAXDATA 1024    /* 受信バッファサイズ */

void reply(void *);

int main(void)
{
    struct sockaddr_in saddr;
    struct sockaddr_in caddr;

    int listen_fd;
    int conn_fd;

    int len = sizeof(struct sockaddr_in);

    pthread_t worker;

    /* ソケットの生成 */
    if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /*
     * saddrの中身を0にしておかないと、bind()でエラーが起こることがある
     */
    bzero((char *)&saddr, sizeof(saddr));

    /* ソケットにアドレスとポートを結びつける */
    saddr.sin_family        = PF_INET;
    saddr.sin_addr.s_addr   = INADDR_ANY;
    saddr.sin_port          = htons(PORT);
    if (bind(listen_fd, (struct sockaddr *)&saddr, len) < 0) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    /* ポートをListenする */
    if (listen(listen_fd, SOMAXCONN) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    printf("Start Listening Port : %d...\n", PORT);

    while (1) {
        /* 接続要求を受け付ける */
        if ((conn_fd = accept(listen_fd, (struct sockaddr *)&caddr, &len)) < 0) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        /* スレッドの生成 */
        if (pthread_create( &worker, NULL, (void *)reply, (void *)conn_fd) != 0) {
            perror("pthread_create");
            exit(EXIT_FAILURE);
        }
        pthread_detach(worker);
    }
    /* Listeningソケットを閉じる */
    close(listen_fd);

}

void reply(void *fd) {
    int conn_fd = (int)fd;

    int rsize;
    char buf[MAXDATA]; /* 受信バッファ */

    /* 送信されたデータの読み出し */
    do {
        rsize = recv(conn_fd, buf, MAXDATA, 0);

        if (rsize == 0) { /* クラアイントが接続を切ったとき */
                break;
        } else if (rsize == -1) {
                perror("recv");
                exit(EXIT_FAILURE);
        } else {
                write(conn_fd, buf, rsize);
        }
    } while (1);

    if ( close(conn_fd) < 0) {
        perror("close");
        exit(EXIT_FAILURE);
    }

    printf("Connection closed.\n");
}

ポイント

  • スレッドにする部分を関数にする
  • 生成したスレッドをdetach状態にする
  • while(1)ループでListenし続ける

参考文献

マスタリングTCP/IP 入門編 第4版
マスタリングTCP/IP 入門編
マスタリングTCP/IP 応用編
マスタリングTCP/IP 応用編
マルチコアCPUのための並列プログラミング―並列処理&マルチスレッド入門
マルチコアCPUのための並列プログラミング

参考URL

Pthreadによる複数クライアントに対するサービスの同時提供
http://www.coins.tsukuba.ac.jp/~yas/coins/syspro-2004/2004-05-10/echo-server-pthread.html
4.2 どうしてソケットがクローズしてくれないのでしょうか?
http://www.kt.rim.or.jp/~ksk/sock-faq/unix-socket-faq-ja-4.html#ss4.2

構築環境

FreeBSD 4.10-RELEASE

Comments are closed.