7

Windows では、ほとんどの種類のハンドルを子プロセスに継承できます。TCP ソケットも継承できることが期待されます。ただし、特定のレイヤード サービス プロバイダーがインストールされている場合、これは期待どおりに機能しません (Symantec の PCTools などの A/V 製品は、お客様のアプリケーションで問題を引き起こしていました)。

Microsoft が WinSock を構築した方法では、SOCKET を正しく継承できると期待できますか?

4

1 に答える 1

11

簡潔な答え

いいえ、SOCKET を継承可能としてマークしないでください。特定のレイヤード サービス プロバイダー (LSP) がインストールされている場合、継承されたハンドルを子で使用することはできません。

追加の苛立ちとして、関連する問題「TCP SOCKETS を非継承とマークできますか?」を参照してください。. 簡単に言えば、ソケットを継承できることに頼ることはできませんが、ソケットの継承を止めることもできません!

説明

残念ながら、これは Microsoft 独自の例やドキュメント ( KB150523など) に反します。簡単に言うと、レイヤード サービス プロバイダーは、Microsoft がサードパーティ ソフトウェアに提供し、WinSock DLL 内のアプリケーションと Microsoft の TCP/UDP スタックの間に自分自身を挿入する方法です。一部の LSP の機能により、プロセス間でソケットを転送することが困難になります。これは、LSP が存在する必要がある各ソケットにローカル情報を関連付けるためです。

LSP は WinSock 関数にのみフックできます。たとえば、DuplicateHandle特定の LSP がインストールされている場合、SOCKET の呼び出しは機能しません。これは、ハンドルレベルの関数であり、LSP には必要な情報をコピーする機会が与えられないためです。(これはドキュメントに簡潔に、しかし明確に述べられていDuplicateHandle ます)。

同様に、SOCKET ハンドルを継承可能に設定しようとすると、LSP に通知せずにハンドルがコピーされ、同じ結果になります。重複したハンドルは、子プロセスの Winsock によって認識されない可能性があります。典型的なエラーは、WSAENOTSOCK (10038、「非ソケットでのソケット操作」)、または ERROR_INVALID_HANDLE (6、「ハンドルが無効です」) です。

リダイレクトされた stdin と stdout で子を起動し、データを送信し、子の stdin で EOF を通知してデータを処理することを認識し、子が戻るのを待つ Windows プログラムを作成するとします。

さらに、想像上の形式の起動が実行されると仮定します。つまり、あなたの子はおそらくまったく子ではないことを意味します (たとえば、すぐに終了する必要があるラッパーを起動するための gksu/runas で、ソケット接続だけが残ります)。クライアント)。したがって、待機する子の PID がありません。

動作は次のようになります。

int main(int argc, char* argv[]) {
  int handles[2];
  socketpair(AF_UNIX, SOCK_STREAM, 0, handles);
  if (fork()) {
    // child
    close(handles[0]);
    dup2(handles[1], 0);
    dup2(handles[1], 1);
    execl("clever-app", "clever-app", (char*)0);
  }

  // parent
  close(handles[1]);
  char* data[100];
  write(handles[0], data, sizeof(data)); // should at least check for EINTR...

  // tell the app we called there's nothing more to read from stdin: 
  shutdown(handles[0], SHUT_WR);

  // wait until child has exited (discarding all output)
  while (read(handles[0], data, sizeof(data)) >= 0) ;

  // now continue with the rest of the program...
}

回避策

レイヤード サービス プロバイダーのないマシンでは、接続された TCP ソケットのペアを作成し、1 つを子に stdin/stdout として継承すると、正しく動作します。Windows での動作の回避策としてこれを使用するのは魅力的ですsocketpair(ナンスを送信することを忘れないでください!)。

残念ながら、SOCKET を確実に継承することはできません。Windows でほぼ同等の機能を持つものを作成するには、名前付きパイプを使用する必要があります。を呼び出す前に、 /とフレンドCreateProcessを使用する代わりに、接続された HANDLES のペアを作成します(オーバーラップする親ハンドル用)。(標準入力として使用される子のハンドルはオーバーラップしてはなりません!) 子のハンドルは継承可能に設定でき、子はそれを介して通常どおり通信します。CreateNamedPipeConnectNamedPipeGetOverlappedResult

クライアントへのデータのパイプ処理が完了したら、親ハンドルでFlushFileBuffersandを呼び出します。CloseHandle

さらなる問題

  1. ハンドルのみを使用して、続行する前に子が終了するのを待つのはどうですか? パイプだけで直接それを行う方法はありません。Windows パイプは半分閉じることはできません。これを行う方法:

    1. (Windows に合うようにゆがめられた Unix の方法) 接続されたハンドルの別のダミー ペアを作成し、子プロセスでそれらの 1 つを継承しますが、子プロセスにそれを伝えません (標準ハンドルとしてアタッチしないでください)。次に、親の 2 番目のハンドルを待って、子がいつ終了するかを検出できます。
    2. (Windows-y の方法) 面倒ですが、かなり強力です。名前付きパイプを使用して、親の子への実際のプロセス ハンドルを取得します。これは、Windows で子が終了するのを待つ「正しい」方法です。(これは実際には Unix ではできないことです。プロセスの親だけがプロセスの終了を直接待つことができますOpenProcess。pid をハンドルに変換します。OpenProcessUnix でこれを不可能にする競合状態を取り除くことができます。) それでも、このようなプロセス ハンドルを使用することは、場合によっては、それを送信するために 2 番目の名前付きパイプ接続が必要になることに気付くので、非常に面倒です。 runas ラッパーの書き方について。
  2. 落とし穴: 親が標準入力への書き込みを終了したという通知を子はどのように受け取るのでしょうか? 親が を呼び出そうとするとDisconnectClient、子は通常の EOF を取得しません。実行しようとしている内容によっては、これが問題になる場合があります。親が SOCKET をシャットダウンすると、 が得られますがfeof、ハンドルが子の stdin に接続されている場合、子は EOF シグナルを受信せずに読み取りエラーを受け取ります。これにより、子プロセスが通常の標準入力に接続されている場合とまったく同じように動作しない可能性があります。代わりに、親で CloseHandle を呼び出すと、子で正しい動作が得られます。

于 2012-08-09T18:30:44.600 に答える