2016-04-06 19 views
1

私はCでネットワークインタフェースをリッスンし、クライアントを受け入れるシンプルなサーバー/クライアントプログラムを実行しています。各クライアントは、フォークされたプロセスで処理されます。C:fork()子プロセスが切断されたときに親に通知します。

私が持っている目標は、クライアントが子プロセスから切断されると親プロセスに知らせることです。

現在、私のメインループは次のようになります。

for (;;) { 

    /* 1. [network] Wait for new connection... (BLOCKING CALL) */ 
    fd_listen[client] = accept(fd_listen[server], (struct sockaddr *)&cli_addr, &clilen); 

    if (fd_listen[client] < 0) { 
     perror("ERROR on accept"); 
     exit(1); 
    } 


    /* 2. [process] Call socketpair */ 
    if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fd_comm) != 0) { 
     perror("ERROR on socketpair"); 
     exit(1); 
    } 


    /* 3. [process] Call fork */ 
    pid = fork(); 

    if (pid < 0) { 
     perror("ERROR on fork"); 
     exit(1); 
    } 

    /* 3.1 [process] Inside the Child */ 
    if (pid == 0) { 

     printf("[child] num of clients: %d\n", num_client+1); 
     printf("[child] pid: %ld\n", (long) getpid()); 

     close(fd_comm[parent]);  // Close the parent socket file descriptor 
     close(fd_listen[server]); // Close the server socket file descriptor 

     // Tasks that the child process should be doing for the connected client 
     child_processing(fd_listen[client]); 

     exit(0); 
    } 
    /* 3.2 [process] Inside the Parent */ 
    else { 

     num_client++; 
     close(fd_comm[child]);  // Close the child socket file descriptor 
     close(fd_listen[client]); // Close the client socket file descriptor 

     printf("[parent] num of clients: %d\n", num_client); 

     while ((w = waitpid(-1, &status, WNOHANG)) > 0) { 
      printf("[EXIT] child %d terminated\n", w); 
      num_client--; 
     } 
    } 

}/* end of while */ 

それはすべてがうまく機能し、私が持っている唯一の問題は、ブロッキングacceptコールに(おそらく)です。

上記のサーバーに接続すると、新しい子プロセスが作成され、child_processingが呼び出されます。

私はそのクライアントとの接続を切断するときしかし、メインの親プロセスはそれについて知っていないと出力printf("[EXIT] child %d terminated\n", w);

しないんしかし、私はた後、第2のクライアントを接続したときに最初のクライアントは、メインを切断しましたループは最終的にwhile ((w = waitpid(-1, &status, WNOHANG)) > 0)の部分を処理することができ、最初のクライアントが切断されていることを伝えます。

その後に接続と切断を行うクライアントが1つしかない場合、メインの親プロセスは、切断されたかどうかを判断できません。

クライアントが既に残っていることを親プロセスに伝える方法はありますか?

+0

使用スレッドとしてそれらを残します。 'fork()'モデルは基本的に時代遅れです。 – EJP

+0

@EJP、決して 'fork()'は時代遅れです。以下の回答を参照してください - スレッドでネイティブに**利用できない**ソリューションを実際に提供します。 – SergeyA

答えて

0

あなたのwaitpidの使用量がある:-)

UPDATE

私はCとの本当の初心者ですとして、あなたはあなたの答えに、いくつかの短いスニペットを提供する場合、それはいいだろう、私は実際にそれを理解することができますので、正しくありません。

のwaitpid():子供はその後、その後、終了していない場合、コールは0を取得しますので、あなたは非ブロッキング呼び出しを持って成功した、状態 変更された子プロセスのプロセスIDを返します。 WNOHANGが指定され、pidで指定された1つ以上の子(ren) が存在し、まだ状態が変更されていない場合、0は返されます。 です。エラーの場合、-1が返されます。

あなたはすぐにwhileループから外れます。もちろん、最初の子が終了すると後でキャッチすることができ、もう1つでwaitpidを再度処理することができます。

あなたは、私が直接終了を管理することがないあなたを示唆していますが、どんな子どもの終了をキャッチさせ、その後、適切ハンドラでwaitpidを呼び出しますSIGCHLD信号通過することができます待つために、非ブロッキング呼び出しを持っている必要がありますとおり:

void handler(int signal) { 
    while (waitpid(...)) { // find an adequate condition and paramters for your needs 
} 

... 
struct sigaction act; 
act.sa_flag = 0; 
sigemptyset(&(act.sa_mask)); 
act.sa_handler = handler; 
sigaction(SIGCHLD,&act,NULL); 
... // now ready to receive SIGCHLD when at least a children changes its state 
+0

シグナルを使用して、クライアントが子から切断されたことを親に伝えることができます。 – Aeldred

+0

@ Jean-BaptisteYunèsは、SIGCHLDの使用法の例を挙げることができます。 – lockdoc

0

私が正しく理解していれば、一度に複数のクライアントにサービスを提供したいので、子が終了していなければブロックしないという点でwaitpidの呼び出しは正しいです。

ただし、問題は、accept経由で新しいクライアントを待つ間に非同期子プロセスを処理できる必要があることです。 あなたがPOSIXyシステムを扱っていて、単にSIGCHLDハンドラを設定し、シグナルがマスクされていない(sigprocmask、IIRCがデフォルトでマスクされていない)のであれば、EINTRは子が終了すると新しいクライアントが接続するのを待っている間に、EINTRを適切に処理できます。

これは、子プロセスが終了すると、SIGCHLDシグナルが親プロセスに自動的に送信されるためです。一般的に、acceptのようなシステムコールは、待機中に信号が受信された場合、EINTR( "中断")のエラーを返します。

はしかし、まだあなたがacceptを呼び出すだけで前に子供が(すなわち、間にすでにwaitpidacceptを持っている場合)を終了競合状態、存在することになります。これを克服するには、2つの主要な可能性があります。

  1. ではなく、メインループのあなたのSIGCHLDハンドラ内のすべての子の終了処理を、実行します。しかし、シグナルハンドラ内で何ができるかにはかなりの制限があるので、これは実現可能ではないかもしれません。たとえば、printfに電話することはできません(ただし、writeを使用することもできます)。

    私はあなたがこの道を行くことをお勧めしませんが、最初はもっと柔軟に思えるかもしれませんが、後で実行できないと判明するかもしれません。

  2. SIGCHLDシグナルハンドラのノンブロッキングパイプの一端に書き込みます。メインループ内で直接acceptを呼び出す代わりに、poll(またはselect)を使用して、ソケットとパイプの読み取り側の両方の準備ができているかどうかを調べ、それぞれを適切に処理します。

    Linuxの場合(OpenBSDでは他のものについてはわかりません)ppollman page)を使用すると、パイプを作成する必要がなくなります(この場合、信号をマスクしたままでマスクしておく必要があります)ポーリング操作であり、ppollEINTRで失敗した場合は、信号が受信されたことを知っているので、waitpidに電話する必要があります。 SIGCHLD用のシグナルハンドラを設定する必要がありますが、何もする必要はありません。

    Linuxのもう一つの選択肢は、signalfdman page)を使用してパイプを作成し、シグナルハンドラを設定する必要性を避けることです(私は思っています)。これを使用する場合は、SIGCHLDシグナル(sigprocmaskを使用)をマスクする必要があります。 poll(またはそれに相当するもの)がsignalfdがアクティブであることを示している場合は、シグナル・データを読み取って(信号をクリアします)、waitpidを呼び出して子を刈り取ります。

    さまざまなBSDシステムでは、pollの代わりにkqueueOpenBSD man page)を使用し、シグナルハンドラを確立する必要なくシグナルを監視できます。

    その他のPOSIXシステムでは、上記のようにppollと同様の方法でpselectdocumentation)を使用することができます。

    libeventのようなライブラリを使用して、OSの仕様を抽象化することもできます。

glibcのマニュアルはselectを使用してのan exampleています。これらの機能の詳細については、pollのマニュアルページ、ppollpselectを参照してください。 Libeventの使用にはonline bookがあります。

ラフglibcのドキュメントから借りselectを、使用例(および変更):あなたが現在acceptを持って

/* Set up a pipe and set signal handler for SIGCHLD */ 

int pipefd[2]; /* must be a global variable */ 
pipe(pipefd); /* TODO check for error return */ 
fcntl(pipefd[1], F_SETFL, O_NONBLOCK); /* set write end non-blocking */ 

/* signal handler */ 
void sigchld_handler(int signum) 
{ 
    char a = 0; /* write anything, doesn't matter what */ 
    write(pipefd[1], &a, 1); 
} 

/* set up signal handler */ 
signal(SIGCHLD, sigchld_handler); 

は、サーバーソケットやパイプの読み出し側の状態を確認する必要があります。

他のメカニズムを使用して
fd_set set, outset; 
struct timeval timeout; 

/* Initialize the file descriptor set. */ 
FD_ZERO (&set); 
FD_SET (fdlisten[server], &set); 
FD_SET (pipefds[0], &set); 
FD_ZERO(&outset); 

for (;;) { 
    select (FD_SETSIZE, &set, NULL, &outset, NULL /* no timeout */)); 
    /* TODO check for error return. 
     EINTR should just continue the loop. */ 

    if (FD_ISSET(fdlisten[server], &outset)) { 
     /* now do accept() etc */ 
    } 

    if (FD_ISSET(pipefds[0], &outset)) { 
     /* now do waitpid(), and read a byte from the pipe */ 
    } 
} 

は、一般的に簡単ですので、私は運動:)

関連する問題