在套接字编程中,您创建一个侦听套接字,然后对于每个连接的客户端,您将获得一个可用于处理客户端请求的普通流套接字.OS管理幕后传入连接的队列.
两个进程无法同时绑定到同一个端口 - 默认情况下,无论如何.
我想知道是否有一种方法(在任何着名的操作系统,特别是Windows上)启动进程的多个实例,这样它们都绑定到套接字,因此它们有效地共享队列.然后每个流程实例可以是单线程的; 它会在接受新连接时阻止.当客户端连接时,其中一个空闲流程实例将接受该客户端.
这将允许每个进程具有非常简单的单线程实现,除非通过显式共享内存,否则不会共享任何内容,并且用户将能够通过启动更多实例来调整处理带宽.
这样的功能是否存在?
编辑:对于那些问"为什么不使用线程?"的人 显然线程是一种选择.但是在单个进程中有多个线程,所有对象都是可共享的,并且必须非常小心以确保对象不是共享的,或者一次只对一个线程可见,或者是绝对不可变的,以及最流行的语言和运行时缺乏对管理这种复杂性的内置支持.
通过启动一些相同的工作进程,您将获得一个默认不共享的并发系统,从而使构建正确且可伸缩的实现变得更加容易.
您可以在Linux甚至Windows中的两个(或更多)进程之间共享套接字.
在Linux(或POSIX类型OS)下,using fork()
将导致分叉子项拥有所有父文件描述符的副本.任何未关闭的内容都将继续共享,并且(例如使用TCP侦听套接字)可accept()
用于客户端的新套接字.这是多少服务器,包括Apache在大多数情况下工作.
在Windows上同样的事情基本上是正确的,除了没有fork()
系统调用,所以父进程将需要使用CreateProcess
或者某些东西来创建子进程(当然可以使用相同的可执行文件)并且需要传递一个可继承的句柄.
使一个监听套接字成为一个可继承的句柄并不是一个完全无关紧要的活动,但也不是太棘手.DuplicateHandle()
需要用来创建一个重复的句柄(但仍然在父进程中),它将在其上设置可继承的标志.然后,你可以给手柄在STARTUPINFO
结构上与子进程的CreateProcess作为STDIN
,OUT
或ERR
处理(假设你不想使用它为别的).
编辑:
阅读MDSN库,看起来这WSADuplicateSocket
是一个更强大或更正确的机制; 它仍然是非常重要的,因为父/子进程需要解决哪些句柄需要被某些IPC机制复制(尽管这可能像文件系统中的文件一样简单)
澄清:
在回答OP的原始问题时,不,多个过程不能bind()
; 只是原来的父进程会叫bind()
,listen()
等,子进程将只是处理请求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的是监听套接字继承模型的例子.
这是一个很好的描述:
http://zimbra.imladris.sk/download/src/GNR-601/ThirdParty/nginx/docs/IMPLEMENTATION
Implementation of HTTP Auth Server Round-Robin and Memory Caching for NGINX Email Proxy June 6, 2007 Md. Mansoor Peerbhoy...
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就绪).通过和,每个工作进程完成处理每个未完成事件的工作队列条目,并返回其事件轮询循环.一旦任何连接建立与客户端,这些事件通常都比较迅速,因为只要连接套接字准备好进行读取,触发读取事件,必须采取相应的行动.
我想补充说,套接字可以通过AF__UNIX套接字(进程间套接字)在Unix/Linux上共享.似乎发生的是创建一个新的套接字描述符,它有点像原始的一个别名.这个新的套接字描述符通过AFUNIX套接字发送到另一个进程.这在进程无法fork()共享文件描述符的情况下尤其有用.例如,使用库时可以防止因线程问题而导致此问题.您应该创建一个Unix域套接字并使用libancillary来发送描述符.
看到:
https://www.linuxquestions.org/questions/programming-9/how-to-share-socket-between-processes-289978/
用于创建AF_UNIX套接字:
http://docs.sun.com/app/docs/doc/817-4415/portmapper-51908?a=view
例如代码:
http://lists.canonical.org/pipermail/kragen-hacks/2002-January/000292.html
http://cpansearch.perl.org/src/SAMPO/Socket-PassAccessRights-0.03/passfd.c
不确定这与原始问题有多相关,但在Linux内核3.9中有一个补丁添加了TCP/UDP功能:对SO_REUSEPORT套接字选项的TCP和UDP支持; 新的套接字选项允许同一主机上的多个套接字绑定到同一端口,旨在提高在多核系统上运行的多线程网络服务器应用程序的性能.更多信息可以在Linux Kernel 3.9中的LWN链接LWN SO_REUSEPORT中找到,如参考链接中所述:
SO_REUSEPORT选项是非标准的,但在许多其他UNIX系统上以类似的形式提供(特别是BSD,其中的想法来自).它似乎提供了一种有用的替代方案,可以在多核系统上运行的网络应用程序中挤出最大性能,而无需使用fork模式.