[高同時ネットワーク通信アーキテクチャ] 1. Linux 上の単一クライアント接続用の TCP サーバー

目次

1. 機能一覧

1.ソケット方式

2.バインドメソッド

3. リスニング方法

4. acceptメソッド(ブロッキング機能)

5.recvメソッド(ブロッキング機能)

6.送信メソッド

7. closeメソッド

8.htonlメソッド

9.htonsメソッド

10.fcntlメソッド

2 番目に、コードの実装

1. ブロッキングサーバー

TCPサーバープログラムの大まかな流れ

TCPクライアントプログラムの大まかな流れ

完全なコード

2. ノンブロッキングサーバー

ノンブロッキングTCPサーバーの一般的な流れ

完全なコード


1. 機能一覧

1.ソケット方式

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

関数

  • 通信用のソケットを作成し、そのソケットを指すファイル記述子を返します。

パラメータ

  1. ドメイン: ソケットのプロトコル ファミリを指定します。一般的な値は AF_INET (IPv4) と AF_INET6 (IPv6) です。
  2. type: ソケットのタイプを指定します。一般的な値は SOCK_STREAM (接続指向の信頼できるバイト ストリーム) と SOCK_DGRAM (コネクションレス データグラム) です。
  3. プロトコル: プロトコルを指定します。通常は 0 が使用されます。これはデフォルトの選択を意味します。

戻り値

  • 成功した場合は、新しいソケットのファイル記述子を返します。エラーが発生した場合は、-1 が返され、errno が設定されます。

2.バインドメソッド

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

関数

  • ソケットを特定の IP アドレスとポートにバインドします。

パラメータ

  1. sockfd: ソケットによって返されたソケット記述子。
  2. addr: バインドするローカル アドレスを指す構造体 (通常は sockaddr_in または sockaddr_in6 構造体)。
  3. addrlen: ローカル アドレスの長さ (通常は sizeof(struct sockaddr_in) または sizeof(struct sockaddr_in6))。

戻り値

  • 成功した場合は 0 を返します。エラーが発生した場合は、-1 が返され、errno が設定されます。

3. リスニング方法

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);

関数

  • 指定されたソケットで接続リクエストのリッスンを開始します。

パラメータ

  1. sockfd: ソケットによって返されたソケット記述子。
  2. backlog: 接続の待機キューの最大長。接続要求の到着時にキューがいっぱいの場合、クライアントは ECONNREFUSED で示されるエラーを受信する可能性があります。また、基礎となるプロトコルが再送信をサポートしている場合は、接続が成功した場合に後で接続を再試行できるように、要求は無視されることがあります。

戻り値

  • 成功した場合は 0 を返します。エラーが発生した場合は、-1 が返され、errno が設定されます。

4. acceptメソッド(ブロッキング機能)

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

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

関数

  • 接続要求を受け入れ、クライアントと通信するための新しいソケット記述子を返します。

パラメータ

  1. sockfd: ソケットによって返されたソケット記述子。
  2. addr: クライアント アドレスを格納するために使用される構造体へのポインタ。通常は struct sockaddr_in 構造体として指定されます。
  3. addrlen: addr 構造体の長さを渡すために使用されます。

戻り値

  • 成功すると、これらのシステム コールは受け入れられたソケットのファイル記述子 (負ではない整数) を返します。エラーが発生した場合は、-1 を返し、errno を適切に設定し、addrlen を変更しないでください。

5.recvメソッド(ブロッキング機能)

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

関数

  • 接続されたソケットからデータを受信します。

パラメータ

  1. sockfd: accept によって返されたソケット記述子。
  2. buf: データを受信するためのバッファ。
  3. len: バッファの長さ。
  4. flags: 操作を受信するためのフラグ。通常は 0 に設定されます。

戻り値

  • 受信したバイト数を返します。エラーが発生した場合は -1 を返します。エラーが発生した場合、エラーを示す errno が設定されます。クライアント接続が閉じられた場合、戻り値は 0 になります。

6.送信メソッド

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

関数

  • 接続されたソケットにデータを送信します。

パラメータ

  1. sockfd: accept によって返されたソケット記述子。
  2. buf: 送信するデータを含むバッファ。
  3. len: 送信するデータの長さ。
  4. flags: 送信操作のフラグ。通常は 0 に設定されます。

戻り値

  • 成功すると、これらの呼び出しは送信されたバイト数を返します。エラーが発生した場合は、-1 が返され、errno が設定されます。

7. closeメソッド

#include <unistd.h>

int close(int fd);

関数

  • ファイル記述子を閉じ、関連リソースを解放します。

パラメータ

  1. fd: 閉じるファイル記述子。

戻り値

  • 成功するとゼロを返します。エラーが発生した場合は、-1 が返され、errno が設定されます。

8.htonlメソッド

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);

関数

  • ホスト バイト オーダーの 32 ビット (4 バイト) 符号なし整数をネットワーク バイト オーダーの整数に変換します。

9.htonsメソッド

#include <arpa/inet.h>

uint16_t htons(uint16_t hostshort);

関数

  • ホスト バイト オーダーの 16 ビット (2 バイト) 符号付き short 整数をネットワーク バイト オーダーの整数に変換します。

10.fcntlメソッド

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

関数

  • オペレーティング ファイル記述子の動作と属性は、ノンブロッキング IO に設定できます。

パラメータ

  1. fd: 設定するファイル記述子。
  2. cmd: fd に対する操作を実行するコマンド。一般的なコマンドは次のとおりです。通常は F_GETFL、F_SETFL を使用します。
    1. F_DUPFD: ファイル記述子をコピーします。
    2. F_GETFD: ファイル記述子フラグを取得します。
    3. F_SETFD: ファイル記述子フラグを設定します。
    4. F_GETFL: ファイルステータスフラグを取得します。
    5. F_SETFL: ファイルステータスフラグを設定します。
    6. F_GETLK:ファイルロックを取得します。
    7. F_SETLK:ファイルロックを設定します。
    8. F_SETLKW: ファイル ロックを設定し、ロックが利用できない場合は待ちます。

戻り値

  • 呼び出しが成功した場合の戻り値は運用コマンドに依存し、エラーが発生した場合は-1が返され、errnoが適切に設定されます。

2 番目に、コードの実装

1. ブロッキングサーバー

TCPサーバープログラムの大まかな流れ

  1. ソケットの作成:socketシステム コールを使用して TCP ソケットを作成します。ソケットはネットワーク通信のエンドポイントです。

  2. アドレスとポートのバインド (Bind): サーバーの IP アドレスとポート番号をソケットにバインドし、bindシステム コールを使用してバインド操作を完了します。

  3. 接続要求のリスニング (Listen): ソケットをリスニング状態にし、クライアントからの接続要求を待ちます。システムコールを使用してlisten、ソケットの待機キューの長さを設定します。

  4. 接続要求を受け入れる (Accept): クライアントが接続を要求した場合、acceptシステム コールを使用して接続要求を受け入れます。これにより、クライアントと通信するための新しいソケットが作成されますが、元のリッスン ソケットは引き続き新しい接続要求をリッスンします。

  5. 通信:受信したソケットをデータ通信に使用します。read/recvデータは、システムwrite/sendコールまたはその他の高度な I/O 関数を使用して送受信できます。

  6. ソケットを閉じる(Close): 通信が終了したら、closeシステムコールを使用してソケットを閉じ、関連リソースを解放します。

サンプルコード

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    // 创建套接字
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);

    // 绑定地址和端口
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;    //ipv4
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port = htons(8888);
    bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));

    // 监听连接请求
    listen(serverSocket, 5);

    // 接受连接请求
    int clientSocket = accept(serverSocket, NULL, NULL);

    // 进行通信
    char buffer[1024];
    read(clientSocket, buffer, sizeof(buffer));
    printf("Received message: %s\n", buffer);

    // 关闭套接字
    close(clientSocket);
    close(serverSocket);

    return 0;
}

TCPクライアントプログラムの大まかな流れ

  1. ソケットの作成:socketシステム コールを使用して TCP ソケットを作成します。

  2. サーバーのアドレスとポート番号を設定する:struct sockaddr_in構造体を使用してサーバーのアドレスとポート番号を表します。この構造にサーバーの IP アドレスとポート番号を入力します。

  3. サーバーへの接続 (Connect):connectシステムコールを使用してソケットをサーバーに接続します。サーバーのアドレスとポート番号を引数としてconnect関数に渡します。

  4. データ通信 (Communicate): 接続されたソケットを使用してデータの読み取りと書き込みを行います。データの読み取りと送信は、readおよびシステム コールを使用して行うことができます。write

  5. ソケットを閉じる(Close): 通信が完了したら、closeシステムコールを使用してソケットを閉じ、関連リソースを解放します。

サンプルコード

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    // 创建套接字
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);

    // 设置服务器地址和端口号
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8888);
    serverAddress.sin_addr.s_addr = inet_addr("服务器IP地址");

    // 连接服务器
    connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));

    // 进行数据通信
    char *message = "Hello, server!";
    send(clientSocket, message, strlen(message),0);

    // 关闭套接字
    close(clientSocket);

    return 0;
}

完全なコード

  • accept と recv は両方ともブロック関数であり、accept の場合はクライアントの接続がブロックされ、recv の場合は接続されたクライアントのデータの読み取りがブロックされます。
  • クライアントとの継続的な通信を実現するには、recv をマスター ループに配置して、クライアントから送信されたデータを常に読み取る必要があります。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>


#define BUFFER_LENGTH   1024

//初始化服务端,返回其文件描述符
int init_server(int port){
    //返回服务端fd,通常为3,前面0,1,2用于表示标准输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(struct sockaddr_in));
    servAddr.sin_family = AF_INET;  //ipv4
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    servAddr.sin_port = htons(port);    //端口号
    
    //绑定IP和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接
    if(-1 == listen(sfd,SOMAXCONN))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

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

    if(argc < 2)return -1;

    int port = atoi(argv[1]);   //atoi:将字符串转换为整数
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);

    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);   //阻塞函数
    printf("client fd: %d\n",cfd);
    while (1)
    {
        char data[BUFFER_LENGTH]={0};
        int recvLen = recv(cfd,data,BUFFER_LENGTH,0);    //阻塞函数
        if(recvLen < 0){
            printf("recv client fd %d errno: %d\n",cfd,errno);
        }else if(recvLen == 0){
            printf("client fd %d close\n",cfd);
            close(cfd);     //关闭客户端文件描述符,释放资源
            break;
        }else{
            printf("recv client fd %d data: %s\n",cfd,data);
            send(cfd,data,recvLen,0);
        }
    }

    close(sfd);     //关闭服务端文件描述符,释放资源
    printf("server fd %d close\n",sfd);
    
    return 0;
}

実行結果

  • テスト ツール: NetAssist は、 クライアント ツールをシミュレートしてサーバー コードをテストします。

2. ノンブロッキングサーバー

ノンブロッキングTCPサーバーの一般的な流れ

  1. ソケットの作成:socketシステム コールを使用して TCP ソケットを作成します。

  2. ソケットをノンブロッキング モードに設定する:コマンドを通じてソケットのファイル ステータス フラグをノンブロッキング モードに設定するfcntl関数、つまりフラグを使用します。F_SETFLO_NONBLOCK

  3. アドレスとポートのバインド (Bind): サーバーの IP アドレスとポート番号をソケットにバインドし、bindシステム コールを使用してバインド操作を完了します。

  4. 接続要求の待機(Listen): ソケットを待機状態にして、クライアントからの接続要求を待ち、システムlistenコールを使用してソケットの待機キュー長を設定します。

  5. 接続要求を受け入れる (Accept):acceptシステムコールを使用して接続要求を受け入れます。これにより、クライアントと通信するための新しいソケットが作成されますが、元のリッスン ソケットは引き続き新しい接続要求をリッスンします。

  6. 新しいソケットをノンブロッキング モードに設定する: 同様に、fcntl関数を使用して新しいソケットをノンブロッキング モードに設定します。

  7. データ通信 (Communicate): ノンブロッキング ソケットを使用してデータの読み取りと書き込みを行います。read/recvデータは、システム コールまたは他のノンブロッキング I/O 関数を使用してwrite/send交換できます。

  8. ソケットを閉じる(Close): 通信が終了したら、closeシステムコールを使用してソケットを閉じ、関連リソースを解放します。

サンプルコード

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>

int main() {
    // 创建套接字
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);

    // 设置套接字为非阻塞模式
    int flags = fcntl(serverSocket, F_GETFL, 0);
    fcntl(serverSocket, F_SETFL, flags | O_NONBLOCK);

    // 绑定地址和端口
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddress.sin_port = htons(8888);
    bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));

    // 监听连接请求
    listen(serverSocket, 5);

    while (1) {
        // 接受连接请求
        struct sockaddr_in clientAddress;
        socklen_t clientAddressLength = sizeof(clientAddress);
        int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength);

        if (clientSocket > 0) {
            // 设置新的套接字为非阻塞模式
            flags = fcntl(clientSocket, F_GETFL, 0);
            fcntl(clientSocket, F_SETFL, flags | O_NONBLOCK);

            // 进行数据通信
            char buffer[1024];
            ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer));
            if (bytesRead > 0) {
                // 读取到数据
                printf("Received message from client: %s\n", buffer);
            }

            // 关闭客户端套接字
            close(clientSocket);
        }
    }

    // 关闭服务端套接字
    close(serverSocket);

    return 0;
}

完全なコード

  • ソケットをノンブロッキング モードに設定します。
  • 以下は、上記のコード部分がサーバーをノンブロッキングに設定するかどうかをテストするために使用されます。
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#include <fcntl.h>


#define BUFFER_LENGTH   1024

int init_server(int port){
    //获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    if(-1 == sfd){
        printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //设置服务端套接字为非阻塞模式
    int flags = fcntl(sfd,F_GETFL,0);
    fcntl(sfd,F_SETFL,flags | O_NONBLOCK);

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(struct sockaddr_in));
    servAddr.sin_family = AF_INET;  //ipv4
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);   //0.0.0.0
    servAddr.sin_port = htons(port);
    //服务端绑定ip地址和端口号
    if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in)))
    {
        printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    //监听该套接字上的连接请求
    if(-1 == listen(sfd,SOMAXCONN))
    {
        printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }

    return sfd;
}

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

    if(argc < 2)return -1;

    int port = atoi(argv[1]);
    int sfd = init_server(port);
    printf("server fd: %d\n",sfd);

    //接受连接请求
    struct sockaddr_in clientAddr;
    socklen_t len = sizeof(struct sockaddr_in);
    int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);
    if(cfd == -1){
        printf("accept error code: %d codeInfo: %s\n",errno,strerror(errno));
        return -1;
    }
    printf("client fd: %d\n",cfd);
    //设置新的套接字为非阻塞模式
    int flags = fcntl(cfd,F_GETFL,0);
    fcntl(cfd,F_SETFL,flags | O_NONBLOCK);

    while (1)
    {
        char data[BUFFER_LENGTH]={0};
        int recvLen = recv(cfd,data,BUFFER_LENGTH,0);
        if(recvLen < 0){
            printf("recv client fd %d errno: %d\n",cfd,errno);
        }else if(recvLen == 0){
            //客户端断开连接
            printf("client fd %d close\n",cfd);
            close(cfd);     //关闭客户端文件描述符,释放资源
            break;
        }else{
            printf("recv client fd %d data: %s\n",cfd,data);
            send(cfd,data,recvLen,0);
        }
    }

    close(sfd);     //关闭服务端文件描述符,释放资源
    printf("server fd %d close\n",sfd);
    
    return 0;
}

ランニング効果

  • コンパイルして正常に実行した後、次のエラーが報告され、サーバーが正常に実行できなくなります。

問題分析

  • Linux の「リソースが一時的に利用できません」(リソースが一時的に利用できない) に対応するエラー コードは、EAGAIN (エラー値 11) または EWOULDBLOCK です。このエラー コードは通常、ノンブロッキング I/O 操作中に発生し、現在利用可能なリソースがないか、操作が進行中であることを示します。
  • ネットワーク プログラミングで、読み取りまたは書き込み操作にノンブロッキング モードでソケットを使用する場合、利用可能なデータがない場合、または書き込み操作をすぐに完了できない場合、このエラー コードが返されることがあります。これは、ノンブロッキング モードでの I/O 操作はノンブロッキングであるためです。つまり、それらはすぐに完了するか、待機せずにエラー コードを返します。

解決

  • 非同期 I/O (非同期 I/O): 非同期 I/O 操作を使用すると、現在のスレッドをブロックすることなく、すべての I/O 操作の後に戻ることができます。Linux は、aio_read や aio_write などの非同期 I/O 関数を提供します。非同期 I/O 操作では、操作の完了時に通知を受けるコールバック関数を登録できます。

対処するための追跡調査。

おすすめ

転載: blog.csdn.net/weixin_43729127/article/details/131578334