6. ネットワークプログラミング - ノンブロッキング

1.ソケットの4つのIOモデル

1.ブロッキングタイプ

最も一般的に使用/最も簡単/低効率

関数自体にはブロッキング プロパティはありませんが、ファイル記述子自体が原因で関数がブロックされます。

デフォルトでは、Linux によって作成されたソケットはブロックされます

2.ノンブロッキング

IO 操作でブロックしないようにプロセスを設定できます。ポーリングが必要です

多くの CPU リソースを占有する

3. 多重化

複数の IO を同時に操作する

指定した時間内にデータが到着したかどうかを検出するように設定できます

4. シグナルドリブン IO

ワンステップコミュニケーション

データがソケットに到着したら、シグナルを送信してユーザーに通知します

 2.ブロッキングIO

1.読み取りブロッキング

ソケット受信バッファに読み取るデータがない場合、read/recv/recvfrom などの呼び出しによってブロックが発生します。

データが到着すると、カーネルはプロセスを起動し、読み取りなどの関数を介してデータにアクセスします

プロセスのブロック中に事故が発生すると、プロセスは永久にブロックされます。

2.書き込み禁止

書き込みがブロックされる可能性は比較的低く、通常、書き込みバッファーが書き込み対象のデータを書き込めない場合に発生します。

データが書き込めない場合はブロッキング待ちに入る

送信バッファに十分なスペースがあると、カーネルは対応するプロセスを起動して書き込みます

ただし、UDP プロトコルでは送信バッファーがいっぱいになることはなく、書き込み操作の実行時に UDP ソケットがブロックされることはありません。

3. ノンブロッキング IO

すぐに完了できない IO 操作がある場合、システムはプロセスをスリープ状態にして待機させます。

ソケットをノンブロッキング モードに設定すると、システムはプロセスをスリープさせずに待機させず、エラーを直接返します。

プログラムがノンブロッキング モードでソケットを使用する場合、ループを使用して、読み取るデータがあるかどうか、ファイルの説明を継続的にチェックする必要があります。

アプリケーションのノンストップループ判定はCPUリソースを大量に消費するため、一般的には使用をお勧めしません

 4. ノンブロッキングの実装方法

        最初にソケット記述子を作成すると、システム カーネルはデフォルトでブロック IO になり、関数を使用してソケットを非ブロック状態に設定できます。

1.fcntl(ファイルディスクリプタ操作)

fcntl ( 文件描述词操作 )
    头文件:
        #include <unistd.h>
        #include <fcntl.h>
    定义函数 :
        int fcntl(int fd, int cmd);
        int fcntl(int fd, int cmd, long arg);
        int fcntl(int fd, int cmd, struct flock * lock);
参数分析:
        fd --> 需要设置的文件描述符
        cmd --> 设置的功能
        F_DUPFD 用来查找大于或等于参数 arg 的最小且仍未使用的文件描述词, 并且复制参数 fd 的
        F_GETFD 取得 close-on-exec 旗标. 若此旗标的 FD_CLOEXEC 位为 0, 代表在调用 exec(
        F_SETFD 设置 close-on-exec 旗标. 该旗标以参数 arg 的 FD_CLOEXEC 位决定.
        F_GETFL 取得文件描述词状态旗标, 此旗标为 open()的参数 flags.
        F_SETFL 设置文件描述词状态旗标, 参数 arg 为新旗标, 但只允许 O_APPEND、O_NONBLOCK
        F_GETLK 取得文件锁定的状态.
        F_SETLK 设置文件锁定的状态. 此时 flcok 结构的 l_type 值必须是 F_RDLCK、F_WRLCK 或
        F_UNLCK. 如果无法建立锁定, 则返回-1, 错误代码为 EACCES 或 EAGAIN.
        F_SETLKW 同 F_SETLK 作用相同, 但是无法建立锁定时, 此调用会一直等到锁定动作成功为止

戻り値: 成功すると 0 を返し、失敗すると -1 を返します

2. 構造の定義

struct flcok
{
    short int l_type; //锁定的状态
    short int l_whence; //决定 l_start 位置
    off_t l_start; //锁定区域的开头位置
    off_t l_len; //锁定区域的大小
    pid_t l_pid; //锁定动作的进程
};
    l_type 有三种状态:
    F_RDLCK 建立一个供读取用的锁定
    F_WRLCK 建立一个供写入用的锁定
    F_UNLCK 删除之前建立的锁定
    l_whence 也有三种方式:
    SEEK_SET 以文件开头为锁定的起始位置.
    SEEK_CUR 以目前文件读写位置为锁定的起始位置
    SEEK_END 以文件结尾为锁定的起始位置.

3. 操作例

int socket_fd = socket(...........); // 创建一个套接字描述符
int state = fcntl(socket_fd , F_GETFL , 0) ; // 获得当前描述符的旗标
state |= O_NONBLOCK ; // 在原基础上增加非阻塞属性
fcntl(scoket_fd , F_SETFL , state ); // 把配置的好的旗标重新设置回描述符中

5.サーバーをノンブロッキング形式で書く

ノンブロッキング IO を使用して、すべてのクライアント (最大 20 クライアント) からの接続を受信できるサーバーを実装し、メッセージの受信後にメッセージの内容を出力します。

ノンブロッキング コード

#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>  // 包含了地址结构体的定义声明
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <fcntl.h>


// 设置一个结构体
typedef struct clinet
{
    int fd[20] ; // 用来保存每一个在线的用户的描述符
    int num ; // 记录当前在线人数
}Clinet ;


int add_cli(Clinet * P_cli ,int connect_fd)
{
    if ( P_cli->num >= 20 )
    {
        return -1 ;
    }
    
    // 把连接的套接字先设置为非阻塞状态
    int state = fcntl(connect_fd , F_GETFL); // 获取 描述符connect_fd 的属性 
    state |= O_NONBLOCK ; // 设置属性信息, 添加上非阻塞状态
    fcntl(connect_fd , F_SETFL ,state  ); // 把配置好的属性,设置回到 connect_fd 描述符中

    (P_cli->fd)[P_cli->num] = connect_fd ; // 把新连接上来的客户端的描述符保存下来
    P_cli->num ++ ; // 数组下标自加

    return 0 ;
}


int main(int argc, char const *argv[])
{
    // 创建一个套接字 (购买一台手机)
    int sock_fd = socket( AF_INET , SOCK_STREAM , 0 ); // 使用IPV4协议簇, 流式套接字(TCP)
    if (-1 == sock_fd)
    {
        perror("socket rror");
        return -1 ;
    }
    

    // 配置自己的IP和端口号等信息
    // struct sockaddr_in   // IPV4地址结构体
    // {
    //     u_short sin_family;// 地址族
    //     u_short sin_port;// 端口
    //     struct in_addr sin_addr;// IPV4 地址
    //     char sin_zero[8];
    // };
    int addrlen = sizeof(struct sockaddr_in);
    struct sockaddr_in my_addr = {
        .sin_addr.s_addr = htonl(INADDR_ANY) , // 设置服务器的地址, INADDR_ANY 指本机中任何一个地址
        .sin_family = AF_INET , // 使用 IPV4 协议簇
        .sin_port = htons(65000) // 设置服务器的端口号为  65000 
    };

    // 绑定地址信息 (IP + 端口号 + 协议簇)
    int ret_val =  bind(  sock_fd,  (struct sockaddr *)&my_addr, addrlen);
    if (ret_val == -1 )
    {
        perror("bind error");
        return -1 ;
    }else{
        printf(" bind succeed !!\n") ;
    }

    // 设置监听 (打开铃声)
    if(listen( sock_fd,  5 )) // 设置sock_fd 为监听套接字, 同时可以接收连接请求数为  5 + 4(默认值) 
    {
        perror("listen error");
        return -1 ;
    }

    // 设置监听套接字为非阻塞属性
    int state ;
    state = fcntl(sock_fd , F_GETFL); // 获取 描述符sock_fd 的属性 
    state |= O_NONBLOCK ; // 设置属性信息, 添加上非阻塞状态
    fcntl(sock_fd , F_SETFL ,state  ); // 把配置好的属性,设置回到  sock_fd 描述符中

    int connect_fd = -1 ;
    Clinet * P_cli = calloc(1, sizeof(Clinet));
    char * msg = calloc(1,128);
    while(1)
    {
        // 等待连接
        struct sockaddr_in from_addr;  // 由于前面已经 把 监听套接字sock_fd 设置为非阻塞状态
        connect_fd = accept( sock_fd , (struct sockaddr *)&from_addr, &addrlen);
        //由于是非阻塞状态,所以一开始为-1是正常的,-1后要重新来
        if ( connect_fd > 0) // 如果描述符大于0 则表示有客户端连接
        {
            printf("新客户端:%d 连接成功!!\n" , connect_fd);
            add_cli(P_cli , connect_fd);
        }
        
        // 轮询是否有数据到达
        for (int i = 0; i < P_cli->num ; i++)
        {
            // printf("LINE:%d\n" , __LINE__);
            bzero(msg , 128) ;
            // recv 的返回值与 read 返回值一致, 成功则返回实际读取的字节数
            ret_val = recv((P_cli->fd)[i] , msg , 128 , 0 );
            if (ret_val > 0)
            {
                printf("from %d , msg : %s" , P_cli->fd[i] , msg );
            }       
        }
    }
    return 0 ;
}

おすすめ

転載: blog.csdn.net/weixin_45981798/article/details/129912126