web-dev-qa-db-ja.com

複数のプロセスがリスニングソケットを共有する方法はありますか?

ソケットプログラミングでは、リスニングソケットを作成し、接続するクライアントごとに、クライアントの要求を処理するために使用できる通常のストリームソケットを取得します。 OSは、背後で着信接続のキューを管理します。

2つのプロセスは、同じポートに同時にバインドすることはできません-とにかくデフォルトでは。

プロセスの複数のインスタンスを起動する方法(よく知られているOS、特にWindows)で、すべてがソケットにバインドされ、キューを効果的に共有する方法があるかどうか疑問に思っています。各プロセスインスタンスはシングルスレッドにすることができます。新しい接続を受け入れるときにブロックするだけです。クライアントが接続すると、アイドルプロセスインスタンスの1つがそのクライアントを受け入れます。

これにより、各プロセスは非常にシンプルなシングルスレッド実装となり、明示的な共有メモリを介さない限り何も共有できなくなり、ユーザーはインスタンスをさらに起動して処理帯域幅を調整できます。

そのような機能は存在しますか?

編集:「なぜスレッドを使用しないのか?」明らかにスレッドはオプションです。しかし、単一プロセスで複数のスレッドを使用する場合、すべてのオブジェクトは共有可能であり、オブジェクトが共有されないか、一度に1つのスレッドのみに表示されるか、絶対に不変であり、最も一般的な言語とランタイムには、この複雑さを管理するための組み込みサポートがありません。

少数の同一のワーカープロセスを開始することにより、defaultが共有されない並行システムが得られ、正しいビルドをより簡単に構築できます。スケーラブルな実装。

85

LinuxおよびWindowsの2つ(またはそれ以上)のプロセス間でソケットを共有できます。

Linux(またはPOSIXタイプOS)では、fork()を使用すると、分岐した子にすべての親のファイル記述子のコピーが作成されます。閉じないものは引き続き共有され、(たとえばTCP待機ソケット)を使用して)クライアントのaccept()新しいソケットに使用できます。多くの場合、Apacheを含む多くのサーバーが動作します。

Windowsでは、fork()システムコールがないことを除いて、基本的に同じことが当てはまります。そのため、親プロセスはCreateProcessまたは何かを使用して子プロセスを作成する必要があります(もちろん同じ実行可能ファイル)、それに継承可能なハンドルを渡す必要があります。

リスニングソケットを継承可能なハンドルにすることは、完全に些細な作業ではありませんが、それほどトリッキーでもありません。 DuplicateHandle()は、継承可能なフラグが設定された複製ハンドルを作成するために使用する必要があります(ただし、まだ親プロセスにあります)。次に、STARTUPINFO構造体のハンドルを、STDINOUT、またはERRハンドルとしてCreateProcessの子プロセスに与えることができます(そうしなかったと仮定します)他の用途に使用したい)。

編集:

MDSNライブラリを読むと、WSADuplicateSocketがこれを行うためのより堅牢で正しいメカニズムであるように見えます。親/子プロセスは、いくつかのIPCメカニズム(これはファイルシステムのファイルと同じくらい簡単かもしれませんが)

明確化:

OPの元の質問に対する答えとして、いいえ、複数のプロセスはbind();できません。元の親プロセスだけがbind()listen()などを呼び出し、子プロセスはaccept()send()recv()など.

89
MarkR

他のほとんどは、これが機能する技術的な理由を提供しています。以下にいくつかのpythonを実行して、自分でこれを示すことができます。

import socket
import os

def main():
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.bind(("127.0.0.1", 8888))
    serversocket.listen(0)

    # Child Process
    if os.fork() == 0:
        accept_conn("child", serversocket)

    accept_conn("parent", serversocket)

def accept_conn(message, s):
    while True:
        c, addr = s.accept()
        print 'Got connection from in %s' % message
        c.send('Thank you for your connecting to %s\n' % message)
        c.close()

if __== "__main__":
    main()

実際、2つのプロセスIDがリッスンしていることに注意してください。

$ lsof -i :8888
COMMAND   PID    USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  26972 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)
Python  26973 avaitla    3u  IPv4 0xc26aa26de5a8fc6f      0t0  TCP localhost:ddi-tcp-1 (LISTEN)

Telnetとプログラムを実行した結果は次のとおりです。

$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign Host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to child
Connection closed by foreign Host.
$ telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Thank you for your connecting to parent
Connection closed by foreign Host.

$ python prefork.py 
Got connection from in parent
Got connection from in child
Got connection from in parent
30
Anil Vaitla

この質問はすでにMarkRとzackthehackによって完全に回答されているように見えますが、Nginxはリスニングソケット継承モデルの例であることを付け加えます。

良い説明は次のとおりです。

         Implementation of HTTP Auth Server Round-Robin and
                Memory Caching for NGINX Email Proxy

                            June 6, 2007
             Md. Mansoor Peerbhoy <[email protected]>

...

NGINXワーカープロセスのフロー

メインNGINXプロセスが構成ファイルを読み取り、構成された数のワーカープロセスに分岐した後、各ワーカープロセスは、それぞれのソケットセットでイベントを待機するループに入ります。

使用可能な接続がまだないため、各ワーカープロセスはリスニングソケットだけで開始されます。したがって、各ワーカープロセスに設定されたイベント記述子は、リスニングソケットだけで始まります。

(注)NGINXは、いくつかのイベントポーリングメカニズムのいずれか1つを使用するように構成できます。aio/ devpoll/epoll/eventpoll/kqueue/poll/rtsig/select

接続がリスニングソケット(POP3/IMAP/SMTP)のいずれかに到着すると、各NGINXワーカープロセスはリスニングソケットを継承するため、各ワーカープロセスはイベントポーリングから出現します。次に、各NGINXワーカープロセスはグローバルミューテックスの取得を試みます。ワーカープロセスの1つがロックを取得し、他のワーカープロセスはそれぞれのイベントポーリングループに戻ります。

一方、グローバルミューテックスを取得したワーカープロセスは、トリガーされたイベントを調べ、トリガーされた各イベントに必要なワークキューリクエストを作成します。イベントは、ワーカーがイベントを監視している記述子のセットからの単一のソケット記述子に対応します。

トリガーされたイベントが新しい着信接続に対応する場合、NGINXはリスニングソケットからの接続を受け入れます。次に、コンテキストデータ構造をファイル記述子に関連付けます。このコンテキストは、接続に関する情報(POP3/IMAP/SMTP、ユーザーがまだ認証されているかどうかなど)を保持します。次に、この新しく構築されたソケットが、そのワーカープロセスのイベント記述子セットに追加されます。

ワーカーはmutexを放棄し(他のワーカーに到着したイベントはすべて処理可能)、以前にキューに入れられた各リクエストの処理を開始します。各リクエストは、通知されたイベントに対応しています。通知された各ソケット記述子から、ワーカープロセスは、その記述子に以前関連付けられていた対応するコンテキストデータ構造を取得し、その接続の状態に基づいてアクションを実行する対応するコールバック関数を呼び出します。たとえば、新しく確立されたIMAP接続の場合、NGINXが最初に行うことは、標準IMAPウェルカムメッセージを
接続されたソケット(* OK IMAP4 ready)。

各ワーカープロセスは、未処理のイベントごとにワークキューエントリの処理を完了し、イベントポーリングループに戻ります。クライアントとの接続が確立されると、接続されたソケットの読み取り準備ができたときに読み取りイベントがトリガーされ、対応するアクションを実行する必要があるため、イベントは通常より高速です。

13
richardw

Unix/Linux上でAF__UNIXソケット(プロセス間ソケット)を介してソケットを共有できることを追加したいと思います。発生するように見えるのは、元のソケット記述子に多少似た新しいソケット記述子が作成されることです。この新しいソケット記述子は、AFUNIXソケットを介して他のプロセスに送信されます。これは、プロセスがファイル記述子を共有するためにfork()できない場合に特に役立ちます。たとえば、スレッドの問題のためにこれを防ぐライブラリを使用する場合。 Unixドメインソケットを作成し、 libancillary を使用して記述子を送信する必要があります。

見る:

AF_UNIXソケットを作成するには:

例のコード:

12
zachthehack

これが元の質問にどの程度関連しているかはわかりませんが、Linuxカーネル3.9にはTCP/UDP機能を追加するパッチがあります:TCPおよびSO_REUSEPORTソケットオプションのUDPサポート。同じホスト上の複数のソケットを同じポートにバインドし、マルチコアシステム上で実行されるマルチスレッドネットワークサーバーアプリケーションのパフォーマンスを向上させることを目的としています。詳細については、LWNリンク を参照してください。参照リンクに記載されているLinuxカーネル3.9 のLWN SO_REUSEPORT:

sO_REUSEPORTオプションは非標準ですが、他の多くのUNIXシステム(特に、アイデアが生まれたBSD)で同様の形式で利用できます。マルチコアシステムで実行されているネットワークアプリケーションから最大のパフォーマンスを引き出すための便利な代替手段が、フォークパターンを使用せずに提供されるようです。

10
Walid

Linux 3.9以降では、ソケットにSO_REUSEPORTを設定してから、複数の無関係なプロセスでそのソケットを共有できます。これはプリフォーク方式よりも単純であり、シグナルのトラブル、子プロセスへのfdリークなどはありません。

Linux 3.9はソケットサーバーを記述する新しい方法を導入しました

SO_REUSEPORTソケットオプション

4
Benoît

単一のタスクを持ち、その唯一の仕事は着信接続をリッスンすることです。接続を受信すると、接続を受け入れます-これにより、個別のソケット記述子が作成されます。受け入れられたソケットは、使用可能なワーカータスクの1つに渡され、メインタスクはリスニングに戻ります。

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}
3
HUAGHAGUAH

Windows(およびLinux)では、1つのプロセスがソケットを開き、そのソケットを別のプロセスに渡すことができます。その結果、その2番目のプロセスもそのソケットを使用できます(必要に応じて順番に渡します)。 。

重要な関数呼び出しはWSADuplicateSocket()です。

これにより、既存のソケットに関する情報が構造に取り込まれます。この構造は、選択したIPCメカニズムを介して、別の既存のプロセスに渡されます(既存と言うことに注意してください-WSADuplicateSocket()を呼び出すとき、受信するターゲットプロセスを指定する必要があります発信情報)。

次に、受信プロセスはWSASocket()を呼び出して、この情報の構造を渡し、基礎となるソケットへのハンドルを受け取ることができます。

両方のプロセスは、同じ基になるソケットへのハンドルを保持するようになりました。

3
user82238

必要なのは、新しいクライアントをリッスンし、接続が確立したら接続をハンドオフする1つのプロセスのように聞こえます。スレッド間でこれを行うのは簡単で、.Netでは、BeginAcceptなどのメソッドを使用して、多くの配管を処理できます。プロセスの境界を越えて接続を渡すことは複雑であり、パフォーマンス上の利点はありません。

または、複数のプロセスをバインドし、同じソケットでリッスンすることができます。

TcpListener tcpServer = new TcpListener(IPAddress.Loopback, 10090);
tcpServer.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
tcpServer.Start();

while (true)
{
    TcpClient client = tcpServer.AcceptTcpClient();
    Console.WriteLine("TCP client accepted from " + client.Client.RemoteEndPoint + ".");
}

上記のコードをそれぞれ実行する2つのプロセスを起動すると動作し、最初のプロセスがすべての接続を取得するようです。最初のプロセスが強制終了されると、2番目のプロセスが接続を取得します。このようなソケット共有では、Windowsがどのプロセスが新しい接続を取得するかを正確に決定する方法がわかりませんが、簡単なテストでは最も古いプロセスが最初に接続を取得することを示しています。最初のプロセスがビジーである場合、またはそのような何かが共有されているかどうかはわかりません。

2
sipwiz

HTTPを使用している場合のWindowsでの別のアプローチ(多くの複雑な詳細を回避する)は、 HTTP.SYS を使用することです。これにより、複数のプロセスが同じポートで異なるURLをリッスンできます。 Server 2003/2008/Vista/7では、これがIISの仕組みなので、ポートを共有できます。(On XP SP2 HTTP.SYSがサポートされています、IIS5.1はそれを使用しません。)

他の高レベルAPI(WCFを含む)は、HTTP.SYSを使用します。

2
Richard