여러 프로세스가 청취 소켓을 공유하는 방법이 있습니까?


90

소켓 프로그래밍에서 청취 소켓을 만든 다음 연결하는 각 클라이언트에 대해 클라이언트의 요청을 처리하는 데 사용할 수있는 일반 스트림 소켓을 얻습니다. OS는 백그라운드에서 들어오는 연결 대기열을 관리합니다.

기본적으로 두 프로세스는 동시에 같은 포트에 바인딩 할 수 없습니다.

잘 알려진 OS, 특히 Windows에서 프로세스의 여러 인스턴스를 시작하여 모두 소켓에 바인딩되어 효과적으로 대기열을 공유하는 방법이 있는지 궁금합니다. 그러면 각 프로세스 인스턴스는 단일 스레드가 될 수 있습니다. 새 연결을 수락하면 차단됩니다. 클라이언트가 연결되면 유휴 프로세스 인스턴스 중 하나가 해당 클라이언트를 수락합니다.

이를 통해 각 프로세스는 명시적인 공유 메모리를 통하지 않는 한 아무것도 공유하지 않는 매우 간단한 단일 스레드 구현을 가질 수 있으며 사용자는 더 많은 인스턴스를 시작하여 처리 대역폭을 조정할 수 있습니다.

그러한 기능이 있습니까?

편집 : "왜 스레드를 사용하지 않습니까?" 분명히 스레드는 옵션입니다. 그러나 단일 프로세스에 여러 스레드가있는 경우 모든 개체를 공유 할 수 있으며 개체가 공유되지 않거나 한 번에 하나의 스레드에만 표시되는지 또는 절대적으로 변경 불가능하며 가장 널리 사용되는 언어 및 런타임에는 이러한 복잡성을 관리하기위한 기본 제공 지원이 없습니다.

소수의 동일한 작업자 프로세스를 시작하면 기본값 이 공유되지 않는 동시 시스템을 얻을 수 있으므로 정확하고 확장 가능한 구현을 훨씬 쉽게 구축 할 수 있습니다.


2
여러 프로세스를 통해 정확하고 강력한 구현을 더 쉽게 만들 수 있다는 데 동의합니다. 확장 가능하지만 확실하지 않습니다. 문제 도메인에 따라 다릅니다.
MarkR

답변:


92

Linux와 Windows에서도 두 개 이상의 프로세스간에 소켓을 공유 할 수 있습니다.

Linux (또는 POSIX 유형 OS)에서를 사용 fork()하면 분기 된 자식이 모든 부모 파일 설명 자의 복사본을 갖게됩니다. 닫히지 않은 모든 항목은 계속 공유되며 (예 : TCP 수신 소켓 사용) accept()클라이언트의 새 소켓에 사용될 수 있습니다 . 이것은 대부분의 경우 Apache를 포함하여 작동하는 서버 수입니다.

Windows에서는 fork()시스템 호출이 없기 때문에 부모 프로세스가 CreateProcess자식 프로세스 (물론 동일한 실행 파일을 사용할 수 있음)를 만들거나 상속 가능한 핸들을 전달해야한다는 점을 제외하면 기본적으로 동일합니다.

청취 소켓을 상속 가능한 핸들로 만드는 것은 완전히 사소한 작업은 아니지만 너무 까다 롭지는 않습니다. DuplicateHandle()상속 가능한 플래그가 설정된 중복 핸들을 만드는 데 사용해야합니다 (하지만 여전히 상위 프로세스에 있음). 그런 다음 STARTUPINFO구조의 해당 핸들을 CreateProcess의 하위 프로세스에 STDIN, OUT또는 ERR핸들 (다른 용도로 사용하고 싶지 않다고 가정)으로 지정할 수 있습니다.

편집하다:

MDSN 라이브러리를 읽으면 WSADuplicateSocket이 작업을 수행하는보다 강력하거나 올바른 메커니즘 인 것으로 보입니다 . 부모 / 자식 프로세스가 어떤 핸들이 일부 IPC 메커니즘에 의해 복제되어야하는지 알아 내야하기 때문에 여전히 중요하지 않습니다 (파일 시스템의 파일처럼 간단 할 수 있음).

설명:

OP의 원래 질문에 대한 대답으로, 아니요, 여러 프로세스는 할 수 없습니다 bind(). 단지 원래의 부모 프로세스는 부를 것이다 bind(), listen()등, 자식 프로세스는 단지의 요청 처리하는 것 accept(), send(), recv()


3
SocketOptionName.ReuseAddress 소켓 옵션을 지정하여 여러 프로세스를 바인딩 할 수 있습니다.
Aaron Clauson

그러나 요점이 무엇입니까? 어쨌든 프로세스는 스레드보다 더 무겁습니다.
Anton Tykhyy

7
프로세스는 스레드보다 무겁지만 명시 적으로 공유 된 항목 만 공유하므로 동기화가 덜 필요하므로 프로그래밍이 더 쉽고 경우에 따라 더 효율적일 수 있습니다.
MarkR

11
또한 자식 프로세스가 어떤 식 으로든 충돌하거나 중단되면 부모에게 영향을 줄 가능성이 적습니다.
MarkR

4
또한 리눅스에서는 fork ()를 사용하지 않고 소켓을 다른 프로그램에 "전달"할 수 있으며 Unix 소켓을 사용하여 부모 / 자식 관계가 없습니다.
Rahly

35

대부분의 다른 사람들은 이것이 작동하는 기술적 이유를 제공했습니다. 다음은이를 직접 입증하기 위해 실행할 수있는 몇 가지 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()

실제로 두 개의 프로세스 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 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
따라서 하나의 연결에 대해 부모 또는 자식이 가져옵니다. 그러나 연결을 얻는 사람은 결정적이지 않습니다.
Hot.PxL

1
예, OS에서 실행하도록 예약 된 프로세스에 따라 다릅니다.
Anil Vaitla 2015 년

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 작업자 프로세스가 전역 뮤텍스를 획득하려고 시도합니다. 작업자 프로세스 중 하나는 잠금을 획득하고 다른 프로세스는 각각의 이벤트 폴링 루프로 돌아갑니다.

한편, 글로벌 뮤텍스를 획득 한 작업자 프로세스는 트리거 된 이벤트를 검사하고 트리거 된 각 이벤트에 대해 필요한 작업 대기열 요청을 생성합니다. 이벤트는 작업자가 이벤트를 감시하는 설명자 집합의 단일 소켓 설명자에 해당합니다.

트리거 된 이벤트가 새로 들어오는 연결에 해당하는 경우 NGINX는 청취 소켓에서 연결을 수락합니다. 그런 다음 컨텍스트 데이터 구조를 파일 설명자와 연결합니다. 이 컨텍스트는 연결에 대한 정보 (POP3 / IMAP / SMTP 여부, 사용자가 아직 인증되었는지 여부 등)를 보유합니다. 그런 다음 새로 구성된 소켓이 해당 작업자 프로세스에 대한 이벤트 설명자 세트에 추가됩니다.

이제 작업자는 뮤텍스를 포기하고 (다른 작업자에 도착한 모든 이벤트가 처리 될 수 있음을 의미 함) 이전에 대기열에 있던 각 요청을 처리하기 시작합니다. 각 요청은 신호를받은 이벤트에 해당합니다. 신호를받은 각 소켓 설명자에서 작업자 프로세스는 해당 설명자와 이전에 연결되었던 해당 컨텍스트 데이터 구조를 검색 한 다음 해당 연결 상태에 따라 작업을 수행하는 해당 콜백 함수를 호출합니다. 예를 들어, 새로 설정된 IMAP 연결의 경우 NGINX가 가장 먼저 수행하는 작업은
연결된 소켓 에 표준 IMAP 환영 메시지를 쓰는 것입니다 (* OK IMAP4 준비).

각 작업자 프로세스는 미해결 이벤트마다 작업 대기열 항목 처리를 완료하고 이벤트 폴링 루프로 돌아갑니다. 클라이언트와 연결이 설정되면 연결된 소켓이 읽을 준비가 될 때마다 읽기 이벤트가 트리거되고 해당 작업을 수행해야하기 때문에 이벤트는 일반적으로 더 빠릅니다.


11

이것이 원래 질문과 얼마나 관련이 있는지는 모르겠지만 Linux 커널 3.9에는 TCP / UDP 기능을 추가하는 패치가 있습니다. SO_REUSEPORT 소켓 옵션에 대한 TCP 및 UDP 지원; 새로운 소켓 옵션을 사용하면 동일한 호스트의 여러 소켓을 동일한 포트에 바인딩 할 수 있으며 멀티 코어 시스템에서 실행되는 멀티 스레드 네트워크 서버 응용 프로그램의 성능을 향상시킬 수 있습니다. 자세한 내용은 참조 링크에 언급 된대로 Linux Kernel 3.9 의 LWN 링크 LWN SO_REUSEPORT에서 찾을 수 있습니다 .

SO_REUSEPORT 옵션은 비표준이지만 여러 다른 UNIX 시스템 (특히 아이디어가 시작된 BSD)에서 유사한 형식으로 사용할 수 있습니다. 포크 패턴을 사용하지 않고도 멀티 코어 시스템에서 실행되는 네트워크 애플리케이션에서 최대 성능을 끌어낼 수있는 유용한 대안을 제공하는 것 같습니다.


LWN 기사에서 거의 SO_REUSEPORT모든 소켓이 다른 스레드에 있지만 그룹의 한 소켓 만 accept. 그룹의 모든 소켓이 각각 데이터 사본을 받는지 확인할 수 있습니까?
jww


3

들어오는 연결을 수신하는 것이 유일한 작업 인 단일 작업이 있습니다. 연결이 수신되면 연결을 수락합니다. 이것은 별도의 소켓 설명자를 생성합니다. 수락 된 소켓은 사용 가능한 작업자 작업 중 하나로 전달되고 기본 작업은 다시 수신 상태로 돌아갑니다.

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

소켓은 작업자에게 어떻게 전달됩니까? 아이디어는 작업자가 별도의 프로세스라는 것입니다.
Daniel Earwicker

fork () 또는 위의 다른 아이디어 중 하나입니다. 또는 소켓 I / O를 데이터 처리에서 완전히 분리 할 수도 있습니다. IPC 메커니즘을 통해 작업자 프로세스에 페이로드를 보냅니다. OpenSSH 및 기타 OpenBSD 도구는 스레드없이이 방법을 사용합니다.
HUAGHAGUAH

3

Windows (및 Linux)에서는 한 프로세스가 소켓을 열고 해당 소켓을 다른 프로세스로 전달하여 두 번째 프로세스가 해당 소켓을 사용할 수 있도록 할 수 있습니다 (원하는 경우 차례로 전달). .

중요한 함수 호출은 WSADuplicateSocket ()입니다.

이것은 기존 소켓에 대한 정보로 구조를 채 웁니다. 그런 다음이 구조는 선택한 IPC 메커니즘을 통해 다른 기존 프로세스로 전달됩니다.

수신 프로세스는 WSASocket ()을 호출하여이 정보 구조를 전달하고 기본 소켓에 대한 핸들을 수신 할 수 있습니다.

이제 두 프로세스 모두 동일한 기본 소켓에 대한 핸들을 보유합니다.


2

원하는 것은 하나의 프로세스가 새 클라이언트를 수신 한 다음 연결이 설정되면 연결을 해제하는 것입니다. 스레드간에이를 수행하는 것은 쉽고 .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 + ".");
}

위의 코드를 각각 실행하는 두 개의 프로세스를 실행하면 작동하고 첫 번째 프로세스가 모든 연결을 얻는 것처럼 보입니다. 첫 번째 프로세스가 종료되면 두 번째 프로세스가 연결됩니다. 이와 같은 소켓 공유를 사용하면 빠른 테스트에서 가장 오래된 프로세스가 먼저 연결되는 것을 가리 키지 만 Windows가 새 연결을 얻는 방법을 결정하는 방법을 정확히 알 수 없습니다. 첫 번째 프로세스가 바쁘거나 내가 모르는 것과 같은 경우 공유하는지 여부.


2

HTTP를 사용하는 경우 Windows에서 여러 복잡한 세부 정보를 피하는 또 다른 접근 방식은 HTTP.SYS 를 사용하는 것 입니다. 이를 통해 여러 프로세스가 동일한 포트에서 서로 다른 URL을 수신 할 수 있습니다. Server 2003 / 2008 / Vista / 7에서는 IIS가 작동하는 방식이므로 포트를 공유 할 수 있습니다. (XP SP2에서는 HTTP.SYS가 지원되지만 IIS5.1에서는이를 사용하지 않습니다.)

다른 상위 수준 API (WCF 포함)는 HTTP.SYS를 사용합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.