Sockets 有没有办法让多个进程共享一个侦听套接字?

Sockets 有没有办法让多个进程共享一个侦听套接字?,sockets,concurrency,Sockets,Concurrency,在套接字编程中,您创建一个侦听套接字,然后为每个连接的客户机获得一个普通流套接字,您可以使用它来处理客户机的请求。操作系统在后台管理传入连接的队列 两个进程不能同时绑定到同一个端口-默认情况下,无论如何 我想知道是否有一种方法(在任何著名的操作系统上,尤其是在Windows上)可以启动一个进程的多个实例,使它们都绑定到套接字,从而有效地共享队列。然后,每个流程实例都可以是单线程的;它在接受新连接时会阻塞。当客户端连接时,其中一个空闲进程实例将接受该客户端 这将允许每个进程有一个非常简单的单线程实

在套接字编程中,您创建一个侦听套接字,然后为每个连接的客户机获得一个普通流套接字,您可以使用它来处理客户机的请求。操作系统在后台管理传入连接的队列

两个进程不能同时绑定到同一个端口-默认情况下,无论如何

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

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

是否存在这样的特征

编辑:对于那些询问“为什么不使用线程?”的人,显然线程是一种选择。但是,由于单个进程中有多个线程,所有对象都是可共享的,因此必须非常小心地确保对象不是共享的,就是一次只能对一个线程可见,或者是绝对不可变的,而且大多数流行的语言和运行时都缺乏管理这种复杂性的内置支持


通过启动几个相同的工作进程,您将获得一个并发系统,其中默认为不共享,从而更容易构建正确且可扩展的实现。

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

在Linux(或POSIX类型操作系统)下,使用
fork()
将导致分叉的子级拥有所有父级文件描述符的副本。它没有关闭的任何套接字将继续被共享,并且(例如使用TCP侦听套接字)可用于
accept()
客户端的新套接字。这是多少台服务器(大多数情况下包括Apache)工作

在Windows上,基本上也是这样,只是没有
fork()
系统调用,因此父进程需要使用
CreateProcess
或其他方法来创建子进程(当然可以使用相同的可执行文件),并需要传递一个可继承的句柄

使侦听套接字成为可继承的句柄并不是一个完全琐碎的活动,但也不太棘手
DuplicateHandle()
需要用于创建重复句柄(但仍在父进程中),该句柄将设置可继承标志。然后,您可以将
STARTUPINFO
结构中的句柄作为
STDIN
OUT
ERR
句柄提供给CreateProcess中的子进程(假设您不想将其用于任何其他用途)

编辑:

通过阅读MDSN库,可以看出,
WSADuplicateSocket
是一种更可靠或正确的机制;这仍然很重要,因为父/子进程需要确定哪个句柄需要由某些IPC机制复制(尽管这可能像文件系统中的文件一样简单)

澄清:


在回答OP最初的问题时,不,多个进程不能
bind()
;仅原始父进程将调用
bind()
listen()
等,子进程将仅通过
accept()
send()
recv()处理请求
等等。

听起来你想要的是一个进程监听新客户机,然后在获得连接后将其断开。要跨线程实现这一点很容易,在.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是如何决定哪个进程获得新连接的,尽管快速测试确实指向最旧的进程首先获得新连接。至于它是否共享第一个进程是否繁忙之类的信息,我不知道。

如果使用HTTP,Windows中的另一种方法(避免了许多复杂的细节)是使用。这允许多个进程侦听同一端口上的不同URL。在Server2003/2008/Vista/7上,这就是IIS的工作方式,因此您可以与它共享端口。(XP SP2支持HTTP.SYS,但IIS5.1不使用它。)


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

只有一个任务,其唯一任务是侦听传入的连接。当接收到连接时,它接受该连接-这将创建一个单独的套接字描述符。接受的套接字被传递给一个可用的辅助任务,主任务返回到侦听

s = socket();
bind(s);
listen(s);
while (1) {
  s2 = accept(s);
  send_to_worker(s2);
}
在Windows(和Linux)下,一个进程可以打开一个套接字,然后将该套接字传递给另一个进程,这样第二个进程也可以使用该套接字(如果它希望这样做,则依次传递)

关键的函数调用是WSADuplicateSocket()

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

然后,接收进程可以调用WSASocket(),传入此informat结构
         Implementation of HTTP Auth Server Round-Robin and
                Memory Caching for NGINX Email Proxy

                            June 6, 2007
             Md. Mansoor Peerbhoy <mansoor@zimbra.com>
$ 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