Linuxのプログラミングシステム - ソケットI / Oの多重プログラミング・ネットワーク(選択)サーバプログラミング

I / O多重化モデル

コールは、選択するか、世論調査では、代わりに実際のI / Oシステムコールにブロックする、1に両方のシステムコールでブロックされました。データグラムソケット読みを待って、コールブロッキングを選択します。選択戻りは、アプリケーション・バッファにコールrecevfromデータグラムをコピーし、読みやすい条件をソケットとき。
ここに画像を挿入説明

これは、また、モデル選択機能では、5つのI / Oモデルの一つである
複数が存在する場合、マルチスレッドと単一プロセスのコンテキスト内で多重化マルチプロセス、I / Oに比べ同時接続要求、マルチスレッドまたはこれらのプロセスやスレッドのほとんどが塞がれているが、接続ごとにスレッドやプロセスを作成するためのマルチプロセスモデルが必要。CPUコアの数は、一般的に大きくないので、4つのコアスレッドが1000を実行するように、例えば、各スレッドのタイムスロットは非常に短いが、非常に頻繁なスレッド切り替え。これが問題です。処理スレッドの複数に接続されたI / Oマルチプレクサは、のみ(スレッドプールによってサポートされている)スレッドが可能な各接続のために開くことができる状態、レディ状態を監視する必要があるが、このようなプロセスは、ラインAを必要とします数大幅にCPUのオーバーヘッドとコンテキストスイッチのメモリのオーバーヘッドを減少させる、低下させました。

他のモデルは、詳細を選択し、サーバーのプログラミングに入り、ここで、詳細には触れません。

機能を選択

この関数は、プロセスが唯一の指定した時間後に、1つまたは複数のイベントの複数のを待って、そしてそれを覚ますためにカーネルに指示することができます。それは非常にここで、関数の定義は、理解できないことがあります。

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
返回: 若有就绪描述符则为其数目,超时为 0 ,出错为 -1

nfds個:パラメータ指定+ 1試験すべき記述子の最大値であり、試験されるべき記述子の数、

特定の条件が、我々はNULLポインタに設定することができる状態に興味がない場合は3つの中間パラメータのreadfds、writefdsおよびexceptfdsは、私たちが読みたいテストカーネル、書き込み、および異常な状態記述子を指定しました

タイムアウト:秒、マイクロ秒数は、それが指定されたファイルディスクリプタを待つためにカーネルに指示しますどのくらいの時間を過ごすために、任意の準備ができて。

struct timeval {
  long   tv_sec;      /* seconds */
  long   tv_usec;     /* microseconds */  

:このパラメータは、三つの可能性がある
。:永遠1.待機準備が唯一の良いI / OでファイルディスクリプタNULLポインタへのパラメータのために、返された
記述子に:一定の期間2.待ちを準備を返すようにするときのI / Oが、構造体timevalは、秒とマイクロ秒数を追加することにより、指定されたこのパラメータによって指さない以上、
3は待機しません:ポーリングと呼ばれる記述子、直後にリターンをご確認ください(ポーリング)。この目的のために、機能は体timeval構造を指している必要があり、前記タイマー(秒、マイクロ秒)は0でなければなりません。

ディスクリプタの集合演算子

selcet使用記述子セット、各整数記述子の各ビットに対応する整数の典型的アレイ。

void FD_ZERO(fd_set *fd_set);
void FD_SET(int fd,fd_set *fdset);
void FD_CLR(int fd,fd_set *fdset);
int FD_ISSET(int fd,fd_set *fdset);

;しかし、我々はFD_SET記述子セットの定義を使用し、FD_ZERO()初期化するために必要な
ファイル記述子のコレクションに興味のFD_SET();
FD_CLRマクロ()が除去され、
FD_ISSET()指定のどのセットを決定しますファイルディスクリプタも、この機能を介して送信されるクライアントデータに接続することができ、サーバコードで、これらのコレクションは、新しいクライアントが接続(listenfd)を要求することができ、準備ができている、あなたは今、新しいを受信する時間であることを、サーバーのノウハウを聞かせすることができますクライアント(受け入れる)または読み取り(読み取り)。

関数が戻るを選択

selcet機能が戻った後、ディスクリプタの状態ビットが(応答では発生しません)ファイルディスクリプタに対応する濃度はクリアされず、その後、FD_ISSET()クエリの記述は準備ができて、そのため(新しいクライアントが接続を要求する、または要求データに接続されたクライアント);
我々が選択するために呼び出しで、listenfdを聴くために、これらのすでに確立された接続と記述子を保存する前に配列を使用する必要があるので、すべてのリターンは、それがブロックされ、クリアされますので、 、配列の値は、文字セットに一つずつ割り当てられなければなりません。

ダイヤグラム

サーバーの初期状態

用途は選択サーバーを作成した後、クライアントがサーバーのステータスに接続されていません:
ここに画像を挿入説明
この黒い点は、ソケットlistenfdに作成され、
サーバーが読み取り専用の記述子のセットを保持し、その後、記述子0,1,2の位置がなるように設定されています標準入力、標準出力、標準エラー、直後リスニング記述
ここに画像を挿入説明
今回、唯一の監視用に作成された記述子、我々は配列に格納された記述子は、一般的に3である必要があり、リサイクルFD_SET機能、この説明文字サイズ対応するビットが1であるという認識に文字セットに追加されたファイル記述子アレイ、文字セットに存在するが、プログラムはブロック選択します。

最初のクライアント接続

サーバーとの接続を確立するための最初のクライアントは、リスナーが他のSELECTビットの中で、読みやすい記述子となり、オープンスタンダードに戻るためにクリアされると、プログラムは新しいファイルディスクリプタを返す、と記述が追加されて受け入れる関数を呼び出しますアレイ、最初のクライアント接続は、典型的には4であり、
ここに画像を挿入説明
このとき、アレイ及び記述子セットはこれです:
ここに画像を挿入説明
今から、各プログラムの新しい記述子は、レコードの配列によって接続されており、説明を追加する必要がありますフーフォーカスがあるFD4

2番目のクライアントを接続します

クライアントが接続されていることを注意、文字セットを持つ配列:
ここに画像を挿入説明
プログラムは、新たに接続されたクライアント側レコード、項目記述子が-1で埋めるすなわち最初のサイズでなければなりません、そして、彼は文字セットに追加し、FD5、すなわち、対応するビットが1に設定されます。

この時間を想定、最初のクライアントがデータを端末に送信した後に切断接続、関数が返すブロックを選択し、着信データを処理し、次に出た記述子アレイは次のように設定され-1( )他のゲストを待って、部屋をクリーンアップ;
ときselcetリターン:
ここに画像を挿入説明
この時点では、1つのディスクリプタは準備ができているかを調べるためにFD_ISSET()を呼び出し、その後、あなたは、関連する読み取りと書き込みを見つけることができます!もちろん、複数のサーバは、データに送信することができる、それは新しいクライアントに接続することができる。同様に、位置に応じて、位置0に応じて、我々は各記述子はそう、配列に格納されているため;次は再び1つの割り当てをブロックすることができる前にゼロにセット心配ない
クライアントが終了した後の配列:
ここに画像を挿入説明
以前は、次の障害物を選択し、それが割り当てられてRSETの配列の内容になります。
選択すると、モニタ記述子セットしていきます以上の記述子よりも、兵士たちは、土壌のカバーに水をブロックします。

サーバーコードを選択

/*********************************************************************************
 *      Copyright:  (C) 2020 Xiao yang System Studio
 *                  All rights reserved.
 *
 *       Filename:  select_server.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(03/09/2020)
 *         Author:  Lu Xiaoyang <[email protected]>
 *      ChangeLog:  1, Release initial version on "03/09/2020 06:29:03 PM"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <libgen.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>

#define ARRAY_SIZE(x)   (sizeof(x)/sizeof(x[0]))   //用来计算数组大小(多少个项)

static inline void print_usage(char *progname);   //打印帮助信息
int socket_Be_ready(char *listen_ip,int listen_port);   //将服务器的socket(),bind(),listen() 包装成一个函数;

int main(int argc,char *argv[])
{
    char              buf[1024];
    char              *progname;
    int               listenfd,clifd;
    int               maxfd = 0;
    int               serv_port;
    int               rv;
    int               daemon_run = 0;   //与程序后台运行有关
    fd_set            allset;          //添加感兴趣的文件描述符的集合位;
    int               fds_array[1024];   //用来存放文件描述符的数组
    int               found = 0;
    int               i;
    int               opt = 0;
    struct option     opts[] = {
        {"port",required_argument,NULL,'p'},
        {"daemon",no_argument,NULL,'d'},
        {"help",no_argument,NULL,'h'},
        {NULL,0,NULL,0}
    };                                  //上下这一块用来接收命令行参数执行对应的赋值或者打印相关信息
    
    progname = basename(argv[0]);
    while((opt = getopt_long(argc,argv,"p:dh",opts,NULL)) != -1)
    {
        switch(opt)
            {
                case 'p':
                    serv_port = atoi(optarg);
                    break;

                case 'h':
                    print_usage(progname);
                    break;

                case 'd':
                    daemon_run = 1;
                    break;

                default:
                    break;
            }
    }

    if(!serv_port)   //未接收到用户传的端点;
    {
        print_usage(progname);
        return -1;
    }

    if((listenfd = socket_Be_ready(NULL,serv_port)) < 0)   //创建服务器;
    {
        perror("Socket create failure");
        return -2;
    }

    printf(" server start listen on port[%d]\n",serv_port);

    if(daemon_run)
    {
        daemon(0,0);   //用户加入了 -d 选项后,服务器将会在后台运行,不会占用窗口;
    }

    for(i = 0;i < ARRAY_SIZE(fds_array);i++)
    {
        fds_array[i] = -1;   //最小的文件描述符为0;将数组全部置-1,相当于把房间清空,等待人进来居住;可使用循环判断 fds_arry[i] < 0 来判断该位置是否空;
    }

    fds_array[0] = listenfd;   //将用来监听的文件描述符存放在数组的第一个位置;
   
    for( ; ; )
    {
        FD_ZERO(&allset);
        for(i = 0;i < ARRAY_SIZE(fds_array);i++)
        {
            if(fds_array[i] < 0)
                continue;
            maxfd = fds_array[i] > maxfd ? fds_array[i] : maxfd;
            FD_SET(fds_array[i],&allset);
        }                                        //selcet在返回后会清零未就绪的描述符对应位,所以每一次使用都要赋值,找到最大位;

        rv = select(maxfd+1,&allset,NULL,NULL,NULL);   //程序阻塞在此,等待字符集中的响应;
        if(rv < 0)
        {
            perror("select failure");
            break;
        }

        else if(rv == 0)
        {
            printf("Time Out\n");
            break;
        }

        if(FD_ISSET(listenfd,&allset))   //新的客户端发来连接请求,则使用 accept() 函数处理;
        {
           if((clifd = accept(listenfd,(struct sockaddr  *)NULL,NULL)) < 0)
           {
               perror("Accept new client failure");
               continue;
           }
           for(i = 1;i < ARRAY_SIZE(fds_array);i++)   //找到一个为 -1 的项,说明这个位置未被占用;
           {
               if(fds_array[i] < 0)
               {
                   printf("Put new clifd[%d] into fds_array[%d]\n",clifd,i);
                   fds_array[i] = clifd;   //将新接收的客户端的文件描述符放入文件描述符数组中;
                   found = 1;
                   break;
               }
           }

           if(!found)
           {
               printf("Put new client into array failure:full\n");
               close (clifd);
           }
        }

        else   //已连接的客户端发来数可读;
        {
            for(i = 1;i < ARRAY_SIZE(fds_array);i++)
            {
               if(fds_array[i] < 0 || !FD_ISSET(fds_array[i],&allset))   //往响应的客户端读写;
                   continue;
            
               rv = read(fds_array[i],buf,sizeof(buf));
               if(rv <= 0)
               {
                   printf("Read from client[%d] failure:%s\n",fds_array[i],strerror(errno));
                   close(fds_array[i]);
                   fds_array[i] = -1;
               }  
               else
               {
                   printf("Read %d bytes data from client[%d] : %s\n",rv,fds_array[i],buf);
                   int j = 0;
                   for(j = 0;j < rv;j++)
                   {
                       buf[j] = toupper(buf[j]);
                   }

                   rv = write(fds_array[i],buf,rv);
                   if(rv <= 0)
                   {
                       printf("Write to client[%d] failure:%s\n",fds_array[i],strerror(errno));
                       close(fds_array[i]);
                       fds_array[i] = -1;
                   }
                }
               
            }
        }
    }

    return 0;
}

void print_usage(char *progname)
{
    printf("progname usage:\n");
    printf("-p(--port) for port you will bind\n");
    printf("-d(--deamon) the programe will run at background\n");
    printf("-h(--help) print help massage\n");

    return;
}

int socket_Be_ready(char *listen_ip,int serv_port)
{
    int                    listenfd;
    int                    on = 1;
    struct sockaddr_in     servaddr;
    socklen_t              addrlen = sizeof(servaddr);

    if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
    {
        perror("socket() failure");
        return -1;
    }
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(serv_port);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(bind(listenfd,(struct sockaddr *)&servaddr,addrlen) < 0)
    {
        printf("Bind failure:%s\n",strerror(errno));
        return -1;
    }

    listen(listenfd,13);

    return listenfd;

}

        
    

サーバーの欠点を選択

1.選択するための各呼び出し、多くのFDカーネルモードへのユーザーモードをコピーするFDの必要性のセットから、オーバーヘッドが大きくなりますが、
2 SELECTは、FDとすべてが各呼び出しで渡されたカーネル、オーバーヘッドを通過する必要がありますとき、多くのFDも素晴らしい、
ファイルディスクリプタ3.selectサポートの数が少なすぎると、デフォルトは1024です。が、3よりも効率的なサーバを選択し、TCP、TCPとマルチプロセスバージョンのTCPマルチスレッド・バージョンのシングルプロセスバージョンと比較して、

公開された10元の記事 ウォン称賛20 ビュー2391

おすすめ

転載: blog.csdn.net/weixin_45121946/article/details/104774086