有多个进程共享侦听套接字的方法吗?

在套接字编程中,您将创build一个监听套接字,然后为每个连接的客户端获得一个普通的stream套接字,您可以使用它来处理客户端的请求。 OS在幕后pipe理传入连接的队列。

两个进程无法同时绑定到相同的端口 – 默认情况下,无论如何。

我想知道是否有一种方法(在任何知名的操作系统,尤其是Windows上)启动一个进程的多个实例,以便它们都绑定到套接字上,因此它们有效地共享队列。 每个stream程实例可以是单线程的; 它只会在接受新的连接时被阻塞。 当一个客户端连接时,其中一个空闲进程实例将接受该客户端。

这将允许每个进程都有一个非常简单的单线程实现,除非通过显式的共享内存,否则就不会共享,用户可以通过启动更多的实例来调整处理带宽。

这样的function是否存在?

编辑:对于那些问“为什么不使用线程?” 显然线程是一个选项。 但是在一个进程中有多个线程,所有对象都是可共享的,而且必须非常小心,以确保对象不是共享的,或者一次只能看到一个线程,或者是绝对不变的,最stream行的语言和运行时缺乏pipe理这种复杂性的内置支持。

通过启动一些相同的工作进程,您将得到一个默认不共享的并发系统,使构build正确和可扩展的实现变得容易得多。

您可以在Linux甚至Windows中的两个(或更多)进程之间共享套接字。

在Linux(或POSIXtypes的操作系统)下,使用fork()将导致分叉的孩子拥有所有父母的文件描述符的副本。 任何它不closures的将继续被共享,并且(例如用一个TCP监听套接字)可以被用来accept()客户端的新套接字。 这是多less个服务器,包括Apache在大多数情况下,工作。

在Windows上,同样的事情基本上是正确的,除了没有fork()系统调用,所以父进程将需要使用CreateProcess或其他东西来创build一个subprocess(当然可以使用相同的可执行文件),并需要传递它可inheritance的句柄。

使一个监听套接字是一个可inheritance的句柄不是一个完全平凡的活动,但也不是太棘手。 需要使用DuplicateHandle()来创build一个重复的句柄(但是仍然在父进程中),它将会设置可inheritance的标志。 然后,您可以将STARTUPINFO结构中的句柄作为STDINOUTERR句柄(假设您不想将其用于其他任何内容)放在CreateProcess中的子stream程中。

编辑:

读取MDSN库,看起来WSADuplicateSocket是一个更健壮或更正确的机制。 因为父/subprocess需要计算出某个IPC机制需要复制哪个句柄(尽pipe这可能与文件系统中的文件一样简单)

澄清:

回答OP的原始问题,不,多个进程不能bind() ; 只是原来的父进程会调用bind()listen()等,subprocess只会通过accept()send()recv()等来处理请求。

大多数人提供了这个工程的技术原因。 这里有一些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和程序的结果:

 $ 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 

看起来这个问题已经被MarkR和zackthehack完全回答了,但是我想补充一点,Nginx是监听套接字inheritance模型的一个例子。

这是一个很好的描述:

  Implementation of HTTP Auth Server Round-Robin and Memory Caching for NGINX Email Proxy June 6, 2007 Md. Mansoor Peerbhoy <mansoor@zimbra.com> 

NGINX工作stream程的stream程

主NGINX进程读取configuration文件并分配到configuration的工作进程数后,每个工作进程进入一个循环,在其中等待其相应套接字上的任何事件。

每个工作进程只从侦听套接字开始,因为没有可用的连接。 因此,为每个工作进程设置的事件描述符从只有侦听套接字开始。

(注)NGINX可以configuration为使用以下几种事件轮询机制中的任何一种:aio / devpoll / epoll / eventpoll / kqueue / poll / rtsig / select

当连接到达任何监听套接字(POP3 / IMAP / SMTP)时,每个工作进程都从其事件轮询中出现,因为每个NGINX工作进程都inheritance了监听套接字。 然后,每个NGINX工作进程将尝试获取全局互斥。 其中一个工作进程将获得该锁,而其他工作进程将返回到其各自的事件轮询循环。

同时,获取全局互斥量的工作进程将检查触发的事件,并为每个触发的事件创build必要的工作队列请求。 一个事件对应于工作人员正在监视事件的一组描述符中的一个套接字描述符。

如果触发的事件对应于新的传入连接,则NGINX接受来自监听套接字的连接。 然后,它将上下文数据结构与文件描述符相关联。 此上下文保存有关连接的信息(无论POP3 / IMAP / SMTP,用户是否已通过身份validation等)。 然后,这个新构造的套接字被添加到为该工作进程设置的事件描述符中。

工作人员现在放弃互斥(这意味着任何到达其他员工的事件都可以进行),并开始处理之前排队的每个请求。 每个请求都对应于发送信号的事件。 从发送的每个套接字描述符开始,工作进程检索先前与该描述符相关联的对应的上下文数据结构,然后根据该连接的状态调用执行动作的对应的callback函数。 例如,对于新build立的IMAP连接,NGINX首先要做的就是将标准IMAP欢迎消息写入
连接套接字(*确定IMAP4准备好)。

由此,每个工作进程完成处理每个未完成事件的工作队列条目,并返回到其事件轮询循环。 一旦与客户端build立连接,事件通常会更快,因为只要连接的套接字准备好读取,就会触发读取事件,并且必须采取相应的行动。

我想补充一点,套接字可以通过AF__UNIX套接字(进程间套接字)在Unix / Linux上共享。 看起来会发生的是一个新的套接字描述符被创build,这个描述符有点像原来的别名。 这个新的套接字描述符通过AFUNIX套接字发送到另一个进程。 这在fork()不能共享文件描述符的情况下特别有用。 例如,使用防止由于线程问题造成的库。 您应该创build一个Unix域套接字并使用libancillary发送描述符。

看到:

为了创buildAF_UNIX套接字:

例如代码:

不知道这与原来的问题有多相关,但是在Linux内核3.9中有一个增加TCP / UDPfunction的补丁:TCP和UDP支持SO_REUSEPORT套接字选项; 新的套接字选项允许同一主机上的多个套接字绑定到相同的端口,旨在提高在多核系统之上运行的multithreadingnetworking服务器应用程序的性能。 更多信息可以在Linux Kernel 3.9的LWN链接LWN SO_REUSEPORT中find,如参考链接中所述:

SO_REUSEPORT选项是非标准的,但是在许多其他UNIX系统(特别是思想起源的BSD)上以类似的forms可用。 它似乎为在多核系统上运行的networking应用程序挤出最高性能提供了一个有用的select,而不必使用fork模式。

如果您使用HTTP,另一种方法(避免许多复杂的细节)是使用HTTP.SYS 。 这允许多个进程监听同一端口上的不同URL。 在Server 2003/2008 / Vista / 7上,这是IIS的工作原理,所以你可以共享端口。 (在XP SP2上支持HTTP.SYS,但是IIS5.1不使用它。)

其他高级API(包括WCF)使用HTTP.SYS。

有一个单一的任务,其唯一的工作是监听传入的连接。 当接收到连接时,它接受连接 – 这将创build一个单独的套接字描述符。 接受的套接字被传递给你的一个可用的工作任务,主任务返回到监听。

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

在Windows(和Linux)下,一个进程可能会打开一个套接字,然后将该套接字传递给另一个进程,以便第二个进程也可以使用该套接字(并且如果希望这样做, 。

关键的函数调用是WSADuplicateSocket()。

这将使用有关现有套接字的信息填充结构。 然后,通过你select的IPC机制,这个结构被传递给另一个现有的进程(注意,我说的是现有的 – 当你调用WSADuplicateSocket()时,你必须指明将接收发送信息的目标进程)。

然后接收进程可以调用WSASocket(),传入这个信息结构,并接收底层套接字的句柄。

两个进程现在都拥有相同底层套接字的句柄。

从Linux 3.9开始,您可以在套接字上设置SO_REUSEPORT,然后让多个不相关的进程共享该套接字。 这比preforkscheme更简单,没有更多的信号问题,fd泄露给subprocess等等。

Linux 3.9引入了写入套接字服务器的新方法

SO_REUSEPORT套接字选项

这听起来像你想要的是一个进程监听新的客户端,然后一旦你获得连接就切断连接。 为了在线程间做到这一点很简单,在.NET中你甚至可以使用BeginAccept等方法来为你处理大量的pipe道工作。 跨越stream程边界的连接将是复杂的,不具有任何性能优势。

或者,你可以在同一个套接字上绑定和监听多个进程。

 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究竟是如何决定哪个进程获得新的连接,尽pipe快速testing确实指向了最早的进程。 至于是否共享,如果第一个进程是忙碌或类似的东西,我不知道。