ネットワークプログラミング: 2. リアクターの原理と実装

ネットワークプログラミング: リアクターの原理と実装

1.エポールとは

epoll は、多数のファイル記述子 (FD) を処理するために Linux カーネルによって改良されたポーリングです。これは、Linux での多重化 IO インターフェイス select/poll の拡張バージョンです。これにより、のみアクティブなプログラムのパフォーマンスを大幅に向上させることができます。多数の同時接続 その状況におけるシステムの CPU 使用率。

2. epollの特徴

イベントを取得するとき、リッスンされている記述子セット全体を走査する必要はなく、カーネル IO イベントによって非同期に起動され、Ready キューに追加された記述子セットを走査するだけです。

実際、Linux が燃え始めたのは epoll の後でした

3. epoll のインターフェースとは何ですか

  1. epoll_create(int)
    • Fengchaoボックスを作成するのと同じです
  2. epoll_ctl(epfd、OPERA、fd、ev)
    • 各人が支払う管理費と管理者の追加・削除に相当します
    • 1 つ目は各 epoll 用、2 つ目は操作用、3 つ目は各 fd 用、4 つ目は各イベント用です
  3. epoll_wait(epfd、イベント、evlength、タイムアウト)
    • 宅配業者が宅配業者を取りに行く場合と比較して、タイムアウトはどのくらいの頻度で発生しますか (午前に 1 回、午後に 1 回)
    • events はバッグ、evlength はバッグの大きさです
    • 2 番目は返されるイベントの数です
    • 3つ目はイベントの長さです
    • 4つ目は時間です

4. epoll コードの例

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<pthread.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<string.h>

#define BUFFER_LENGTH 128
#define EVENTS_LENGTH 128

char rbuffer[BUFFER_LENGTH] = {
    
    0};
char wbuffer[BUFFER_LENGTH] = {
    
    0};

int main(){
    
    
    // fd创建的时候默认是阻塞的
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) return -1;

    // listenfd绑定一个固定的地址
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(9999);

    if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))){
    
    
        // 返回-1 失败
        return -2;
    }

    listen(listenfd, 10); // 相当于是迎宾的人

		// 传的参数只要大于0就可以了
    // 这个函数的参数一开始是设置就绪的最大数量的, 但其实用链表来串就绪集合就可以了
    int epfd = epoll_create(1); 
    struct epoll_event ev, events[EVENTS_LENGTH];
    // 为什么一开始要添加事件, 不应该是内核有事件通知我吗
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    // 这个关注一个可读的事件,后面有事件了才会通知我们
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
    // printf("fd : %d \n", epfd);

    // 服务器 7 * 24 运行就是因为这个函数
    // 所有的循环都是这样操作的
    while(1){
    
     
        // 如果是-1,直到有事件才返回,没事件就会阻塞
        // 0代表立即返回, timeout时间是毫秒
        int nready = epoll_wait(epfd, events, EVENTS_LENGTH, 0);
        // nready 有数据的时候返回是大于0的数
        // printf("-----nready -> %d\n", nready);
        int i = 0;
        for(i = 0; i < nready; i++){
    
    
            int clientfd = events->data.fd;
            // 用accept处理
            if(listenfd == clientfd){
    
    
                struct sockaddr_in client;
                socklen_t len = sizeof(client);
                // 新增加的fd, 也加到可读的fd集合中
                int connfd = accept(listenfd, (struct sockaddr*)&client, &len);
                printf("accept: %d\n", connfd);
                ev.events = EPOLLIN;
                ev.data.fd = connfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }else if(events[i].events & EPOLLIN) {
    
     //clientfd
                // char rbuffer[BUFFER_LENGTH] = {0};
                int n = recv(clientfd, rbuffer, BUFFER_LENGTH, 0);
                if(n > 0){
    
    
                    rbuffer[n] = '\0';
                    printf("recv: %s, n: %d\n", rbuffer, n);
                    memcpy(wbuffer, rbuffer, BUFFER_LENGTH);
                    int j = 0;
                    for(j = 0; j < BUFFER_LENGTH; j++){
    
    
                        rbuffer[j] = 'A' + (j % 26);
                    }
                    // send
                    ev.events = EPOLLOUT;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);
                }
            }else if(events[i].events & EPOLLOUT){
    
    
                int sent = send(clientfd, wbuffer, BUFFER_LENGTH, 0);
                printf("sent: %d\n", sent);
            }
        }
    }
}

5.リアクターとは

Reactor モードは、同時 I/O を処理するための比較的一般的なモードです。同期 I/O に使用されます。中心的な考え方は、処理されるすべての I/O イベントを中央の I/O マルチプレクサに登録し、同時に処理することです。メインスレッド/プロセスがマルチプレクサでブロックされています

I/O イベントが到着するか準備が完了すると (ファイル記述子またはソケットが読み書き可能になると)、マルチプレクサは事前に登録されていた対応する I/O イベントを返し、対応するプロセッサに配布します。

(複数の fd がバッファを共有すると、パケットがスタックする現象が発生します。ここでリアクターが作成されます。各 fd に対応するイベントは独自のものである必要があり、分離が行われます)

6. リアクターの利点

  1. 応答が速く、Reactor 自体はまだ同期しているにもかかわらず、単一の同期時間によってブロックされる必要はありません。
  2. プログラミングは比較的単純なので、複雑なマルチスレッドと同期の問題を最大限に回避し、マルチスレッド/プロセスの切り替えオーバーヘッドを回避できます。
  3. スケーラビリティ。Reactor インスタンスの数を増やすことで、CPU リソースを簡単に最大限に活用できます。
  4. 再利用性、リアクターフレームワーク自体は特定のイベント処理ロジックとは無関係であり、再利用性が高い

7. レベルトリガーとエッジトリガー

レベルトリガー

ファイル記述子に関連付けられた読み取りカーネル バッファが空ではなく、読み取るデータがある限り、常に読み取り可能信号を送信して通知します。ファイル記述子に関連付けられたカーネル書き込みバッファがいっぱいでない場合 (存在する限り)は書き込むためのスペースです)、常に書き込み可能な信号通知を発行します

  1. epoll はデフォルトで水平方向にトリガーされます
  2. データが少ないときはLTして一気に読むことが多いです。
  3. レベルトリガーを使用してIOをブロックできます

エッジトリガー

ファイル記述子に関連付けられた読み取りカーネル バッファーが空から空以外に変化すると、読み取り可能な信号が送信されて通知されます。

ファイル記述子に関連付けられた書き込みカーネル バッファーがfullからfull に変化すると、書き込み可能シグナルが送信されて通知されます。

  1. 1 回トリガーし、読み取るデータがなくなるまで周期的に読み取ります
  2. データはバッファに保存されます
  3. ビッグデータを使用する場合、ET は実際には LT と変わりません
  4. エッジトリガーはrecvへのループであり、ブロッキングIOに陥ることはできません

ゼロサウンドアカデミーの無料公開講座がおすすめ 個人的に先生の教え方が良かったと思うのでシェアしたいと思います。

Linux、Nginx、ZeroMQ、MySQL、Redis、fastdfs、MongoDB、ZK、ストリーミング メディア、CDN、P2P、K8S、Docker、TCP/IP、コルーチン、DPDK、その他の技術コンテンツを今すぐ学習

おすすめ

転載: blog.csdn.net/weixin_44839362/article/details/128990675