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


90

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

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

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

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

そのような機能はありますか?

編集:「なぜスレッドを使用しないのですか?」明らかにスレッドはオプションです。ただし、1つのプロセスに複数のスレッドがある場合、すべてのオブジェクトは共有可能であり、オブジェクトが共有されないようにするか、一度に1つのスレッドからしか見えないようにするか、完全に不変であり、最も人気のある言語となるように細心の注意を払う必要があります。ランタイムには、この複雑さを管理するための組み込みサポートがありません。

少数の同一のワーカープロセスを開始することで、デフォルトが共有されない並行システムが得られ、正確でスケーラブルな実装を構築することがはるかに容易になります。


2
複数のプロセスにより、正確で堅牢な実装を作成しやすくなることに同意します。スケーラブルですが、よくわかりません。問題のドメインによって異なります。
MarkR 2009年

回答:


92

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

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

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

リスニングソケットを継承可能なハンドルにすることは、完全に簡単なことではありませんが、トリッキーすぎません。DuplicateHandle()継承可能なフラグが設定されている複製のハンドル(まだ親プロセスにあります)を作成するために使用する必要があります。その後、STARTUPINFO構造内のそのハンドルをCreateProcessの子プロセスにSTDINOUTまたはERRハンドルとして与えることができます(他の目的でそれを使用したくない場合)。

編集:

MDSNライブラリを読むと、これWSADuplicateSocketは、これを行うためのより堅牢で正しいメカニズムであるように見えます。親/子プロセスがIPCメカニズムによって複製される必要があるハンドルを計算する必要があるため、それは重要です(これはファイルシステムのファイルと同じくらい簡単な場合もあります)。

明確化:

OPの元の質問に答えて、いいえ、複数のプロセスはできませんbind()。ただ、元の親プロセスは呼ぶだろうbind()listen()子プロセスがちょうどでリクエストを処理し、などaccept()send()recv()など


3
SocketOptionName.ReuseAddressソケットオプションを指定することにより、複数のプロセスをバインドできます。
sipwiz 2009年

しかし、ポイントは何ですか?とにかく、プロセスはスレッドよりも重いです。
アントンティヒィ2009年

7
プロセスはスレッドよりも重いですが、明示的に共有されるものだけを共有するため、必要な同期が少なくて済み、プログラミングが簡単になり、場合によってはさらに効率的になります。
MarkR 2009年

11
さらに、子プロセスが何らかの方法でクラッシュまたは中断した場合、親に影響を与える可能性は低くなります。
MarkR 2009年

3
Linuxでは、fork()を使用せずにソケットを他のプログラムに「渡す」ことができ、Unixソケットを使用して親子関係がないことにも注意してください。
ラーリー2014

34

他のほとんどは、これが機能する技術的な理由を提供しています。これを自分で実証するために実行できる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 __name__ == "__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

2
つまり、1つの接続で、親または子のいずれかがそれを取得します。しかし、誰がそのつながりを得るかは不確定ですよね?
Hot.PxL 2015年

1
はい、私はそれがOSによって実行されるようにスケジュールされているプロセスに依存すると思います。
Anil Vaitla

14

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

見る:

AF_UNIXソケットを作成する場合:

コード例:


13

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

ここに良い説明があります:

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

                            June 6, 2007
             Md. Mansoor Peerbhoy <mansoor@zimbra.com>

...

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

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

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

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

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

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

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

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

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


11

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

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


LWNの記事からは、ほとんどの場合SO_REUSEPORT、スレッドプールが作成され、各ソケットは異なるスレッド上にありますが、グループ内の1つのソケットのみがを実行しacceptます。グループ内のすべてのソケットがそれぞれデータのコピーを取得することを確認できますか?
jww 2018年


3

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

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

ソケットはどのようにワーカーに渡されますか?この考え方は、ワーカーは別のプロセスであるということを覚えておいてください。
Daniel Earwicker 2009年

fork()、または上記の他のアイデアの1つ。または、ソケットI / Oをデータ処理から完全に分離することもできます。ペイロードをIPCメカニズムを介してワーカープロセスに送信します。OpenSSHと他のOpenBSDツールはこの方法を使用します(スレッドなし)。
HUAGHAGUAH 2009年

3

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

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

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

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

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


2

必要なのは、新しいクライアントをリッスンする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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.