マルチスレッド | マルチプロセス | 同時実行性の高いネットワーク プログラミング

ここに画像の説明を挿入します

1. 複数プロセス同時実行サーバー

マルチプロセス同時サーバーは、クライアント接続を処理する複数のサブプロセスを作成することによって、複数のクライアント要求を同時に処理する機能を実現する、従来のサーバー アーキテクチャです。

コンセプト:

  1. サーバーが起動すると、メインプロセスが作成され、リスニングポートがバインドされます。
  2. クライアント接続要求があると、メインプロセスは接続を受け入れ、クライアント接続を処理する子プロセスを作成します。
  3. 子プロセスはクライアントと通信し、リクエストを処理し、応答を送信します。
  4. メイン プロセスは、新しい接続リクエストをリッスンし続けます。
  5. 子プロセスはタスクを完了した後、他の接続の処理を終了するか続行するかを選択し、必要に応じてサイクルを繰り返すかどうかを選択できます。

アドバンテージ:

  1. 高い同時処理能力: 各子プロセスはクライアント接続を独立して処理でき、マルチコア プロセッサを利用して高い同時処理能力を実現し、サーバーのスループットを向上させます。
  2. 安定性: 各子プロセスは独立しており、1 つのプロセスで問題が発生しても他のプロセスに影響を与えないため、サーバーの安定性とフォールト トレランスが向上します。
  3. シンプルで直感的: マルチプロセス モデルを使用して同時サーバーを実装するのは比較的簡単で、コードは非常に読みやすく、理解と保守が簡単です。
  4. クロスプラットフォーム: マルチプロセス同時サーバーの概念は、Linux プラットフォームに限定されず、さまざまなオペレーティング システムに適用できます。

欠点:

  1. リソース消費量が多い: 各子プロセスは、メモリ、ファイル記述子などの独立したリソースを必要とします。同時接続数が多いと、大量のシステム リソースが消費されます。
  2. プロセス間通信: サブプロセス間の通信にはパイプ、共有メモリなどの追加メカニズムが必要となり、複雑さが増します。
  3. コンテキスト切り替えのオーバーヘッド: プロセス間の切り替えのオーバーヘッドは比較的大きく、パフォーマンスに影響します。
  4. デバッグの難しさ: 各子プロセスは独立して実行されるため、デバッグと問題の特定が複雑になる可能性があります。

要約すると、マルチプロセス同時サーバーは、高度な同時処理のニーズを満たすことができ、安定性とシンプルさという利点があります。ただし、リソースの消費量が多い、デバッグが難しいなどの欠点もあり、実際のニーズやアプリケーションのシナリオに基づいて適切なサーバー アーキテクチャを選択することが重要です。

場合

TCP 通信に基づくマルチプロセスを使用すると、サーバーは同時に複数のクライアントに接続できます。大文字と小文字の変換を実装します。

サービス.c

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

void handler(int sig)
{
    if (sig == SIGCHLD)
    {
        while(waitpid(0,NULL,WNOHANG)>0);
    }
}
int main(int argc, char const *argv[])
{
    //1.建立socket套接字
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket is err");
        return -1;
    }

    //2.bind绑定服务器ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int len = sizeof(caddr);
    int rev = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (rev < 0)
    {
        perror("bind is err");
        return 0;
    }

    //3.listen设置最大同时链接数量
    rev = listen(socked, 32);
    if (rev < 0)
    {
        perror("rev is err");
        return 0;
    }

    //4.建立子进程负责服务器给客户端发送消息,并且主进程不结束,此进程不结束
    char buf[1024] = {0};
    int size=0;
    while (1)
    {
        int acceptfd = accept(socked, (struct sockaddr *)(&caddr), &len);
        if (acceptfd < 0)
        {
            perror("accept is err");
            return 0;
        }
        pid_t pid = fork();
        if (pid < 0)
        {
            perror("pid is err");
            return 0;
        }
        else if (pid == 0)
        {
            //子进程,每个子进程维护一个客户端
            //不许要监听,直接关掉
            close(socked);
            while (1)
            {

                int flage = recv(acceptfd, buf, sizeof(buf), 0);
                if (flage < 0)
                {
                    perror("recv is err");
                }
                else if (flage == 0)
                {
                    printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
                    close(acceptfd);
                    break;
                }
                else
                {
                    size = strlen(buf);

                    for (int i = 0; i < size; ++i)
                    {
                        if (buf[i] >= 'a' && buf[i] <= 'z')
                            buf[i] = buf[i] + ('A' - 'a');
                        else
                            buf[i] = buf[i] + ('a' - 'A');
                    }
                    printf("%d %s\n",getpid(),buf);
                    send(acceptfd, buf, sizeof(buf), 0);
                }
            }
        }
        else
        {
            //主进程回收子线程资源
            close(acceptfd);
            //异步节约资源
            signal(SIGCHLD, handler);
        }
    }
    return 0;
}

client.c

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

int main(int argc, char const *argv[])
{
    //1.socket建立文件描述符
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket is err");
    }

    //2.connect连接服务器
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (flage < 0)
    {
        perror("connect is err");
    }

    //3.服务器端不断发送数据,接受服务器转化后的数据
    char buf[1024] = {0};
    while (1)
    {
        //memset(buf,0,sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        if (strncmp(buf,"quit#",5)==0)
        {
            break;
        }
        
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        send(fd, buf, sizeof(buf), 0);
        flage = recv(fd, buf, sizeof(buf), 0);
        if (flage < 0)
        {
            perror("recv is err");
        }
        else
        {
            fprintf(stdout, "%s\n", buf);
        }
    }
    close(fd);
    return 0;
}

2. マルチスレッド同時サーバー

マルチスレッド同時サーバーは、クライアント接続を処理する複数のスレッドを作成することによって、複数のクライアント要求を同時に処理する機能を実現するサーバー アーキテクチャです。マルチスレッド同時サーバーの概念といくつかの利点を次に示します。

コンセプト:

  1. サーバーが起動すると、メインスレッドが作成され、リスニングポートがバインドされます。
  2. クライアント接続要求があると、メインスレッドは接続を受け入れ、クライアント接続を処理するための 1 つ以上のスレッドを作成します。
  3. スレッドはクライアントと通信し、リクエストを処理し、応答を送信します。
  4. メインスレッドは新しい接続リクエストをリッスンし続けます。
  5. スレッドはタスクを完了した後、他の接続の処理を終了するか続行するかを選択し、必要に応じてサイクルを繰り返すかどうかを選択します。

アドバンテージ:

  1. リソース効率: プロセスと比較して、スレッドは作成および破棄のオーバーヘッドが少なく、占有するリソースが少なくなります。特に、ファイル記述子などの共有リソースを複数の接続間で共有できるため、リソースの使用効率が向上します。
  2. 応答速度: スレッド間の切り替えオーバーヘッドが小さく、メモリ空間が共有され、クライアント要求に迅速に応答でき、接続待ち時間が短縮されます。
  3. シンプルで直感的: マルチプロセスと比較して、マルチスレッド同時サーバーはよりシンプルで直感的であり、コードは非常に読みやすく、理解と保守が容易です。
  4. スケーラビリティ: スレッドを簡単に追加および削除して、変化する接続要件に適応し、より優れたスケーラビリティを実現できます。
  5. データの共有は簡単です。プロセス間通信などの追加メカニズムを使用せずに、スレッド間で共有されるデータ構造に直接アクセスできます。

欠点:

  1. データ同期の問題: 複数のスレッド間でデータを共有すると、競合状態やデータの一貫性の問題が発生する可能性があり、スレッドの安全性を確保するためにロックまたはその他の同期メカニズムの使用が必要になります。
  2. デバッグの難しさ: 同じプロセスのメモリ空間がスレッド間で共有されるため、デバッグと問題の特定が複雑になることがあります。
  3. 並列処理の制限: マルチスレッド サーバーの並列処理は、システムで利用可能な CPU コアの数によって制限される場合があります。
  4. スレッド セーフを考慮する必要がある: スレッド セーフなコードを作成するには、データ競合やデッドロックなどの問題を回避するために、より多くの開発努力と考慮が必要になる場合があります。

要約すると、マルチスレッド同時サーバーは、高い同時処理ニーズを満たすことができ、リソース効率、応答速度、シンプルさ、拡張性という利点があります。ただし、データ同期の問題やデバッグの難しさなどの欠点もあり、実際のニーズやアプリケーション シナリオに基づいて適切なサーバー アーキテクチャを選択することが重要です。

場合

TCP 通信に基づくマルチスレッドを使用すると、サーバーは同時に複数のクライアントに接続できます。大文字と小文字の変換を実装します。

サービス.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>

struct client
{
    int acceptfd;
    struct sockaddr_in caddr;
};

void *my_pthread(void *p)
{
    char buf[1024] = {0};
    int size = 0;
    struct client *cl = (struct client *)p;
    int acceptfd = cl->acceptfd;
    struct sockaddr_in caddr = cl->caddr;
    while (1)
    {
        int flage = recv(acceptfd, buf, sizeof(buf), 0);
        if (flage < 0)
        {
            perror("recv is err");
        }
        else if (flage == 0)
        {
            printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
            close(acceptfd);
            break;
        }
        else
        {
            size = strlen(buf);

            for (int i = 0; i < size; ++i)
            {
                if (buf[i] >= 'a' && buf[i] <= 'z')
                    buf[i] = buf[i] + ('A' - 'a');
                else
                    buf[i] = buf[i] + ('a' - 'A');
            }
            send(acceptfd, buf, sizeof(buf), 0);
        }
    }

    close(acceptfd);
    return 0;
}

int main(int argc, char const *argv[])
{

    //1.建立socket套接字
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket is err");
        return -1;
    }

    //2.bind绑定服务器ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int len = sizeof(caddr);
    int rev = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (rev < 0)
    {
        perror("bind is err");
        return 0;
    }

    //3.listen设置最大同时链接数量
    rev = listen(socked, 32);
    if (rev < 0)
    {
        perror("rev is err");
        return 0;
    }

    //4.建立子进程负责服务器给客户端发送消息,并且主进程不结束,此进程不结束

    int i = 0;
    pthread_t tid;
    struct client ti[1024] = {0};
    while (1)
    {
        int acceptfd = accept(socked, (struct sockaddr *)(&caddr), &len);
        if (acceptfd < 0)
        {
            perror("accept is err");
            return 0;
        }
        ti[i].acceptfd = acceptfd;
        ti[i].caddr = caddr;
        pthread_create(&tid, NULL, my_pthread, (void *)&ti[i]);
        pthread_detach(tid);
        ++i;
    }
    return 0;
}

client.c

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

int main(int argc, char const *argv[])
{
    //1.socket建立文件描述符
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket is err");
    }

    //2.connect连接服务器
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (flage < 0)
    {
        perror("connect is err");
    }

    //3.服务器端不断发送数据,接受服务器转化后的数据
    char buf[1024] = {0};
    while (1)
    {
        //memset(buf,0,sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        if (strncmp(buf,"quit#",5)==0)
        {
            break;
        }
        
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        send(fd, buf, sizeof(buf), 0);
        flage = recv(fd, buf, sizeof(buf), 0);
        if (flage < 0)
        {
            perror("recv is err");
        }
        else
        {
            fprintf(stdout, "%s\n", buf);
        }
    }
    close(fd);
    return 0;
}

おすすめ

転載: blog.csdn.net/m0_73731708/article/details/132910150