如何让多个进程监听同一个TCP端口

通常我的服务器上都会启用多个php-cgi程序,有时候这样做是很必要的

1. 可以同时让多个进程处理请求,负载均衡。
2. 避免一个进程崩溃时,在重新启动前,无法及时提供服务。

启用多个相同服务进程必然需要考虑共用一个socket。对于php-cgi程序来说,是通过stdin文件描述符来传递这个socket。

工作原理如下:

下面是一个示例代码,演示如何启动多个子进程监听同一个端口。

Parent Proecess:

import os, socket, sys

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("", 8000))
s.listen(10)

for i in xrange(3):
    pid = os.fork()
    if pid == 0:
        os.dup2(s.fileno(), sys.stdin.fileno())
        os.execv("/usr/bin/python", ("python", "child.py"))
    else:
        print "fork", i, "process id =", pid
        
print "Parent exited"
sys.exit()

Child Process:

import socket, os, sys

print "Child", os.getpid(), "started"
s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
fd, address = s.accept()

print "Accept from", address

sys.exit()

执行parent.py后,启动了三个child.py的进程,这三个进程都等待来自8000端口的连接,当有连接时打印对方地址并结束进程。通过终端运行

telnet localhost 8000

可以测试child.py进程的响应。连续telnet三次,则让三个进程都完全结束。

若有更好地方法或者建议,请与我交流 🙂

如何让多个进程监听同一个TCP端口》有36个想法

      1. Creke

        可以写规则让相同端口分配负载到不同进程。其实多个进程监听一个端口本质来说也是负载均衡嘛。我用作53端口\(^o^)/~

        回复
          1. Creke

            在HAPROXY中可以写规则,感兴趣的话可以参考一下HAPROXY的文档。如果要实现自己的算法,恐怕要修改一下源代码了

            回复
  1. pzmj

    提一点小意见:加强算法和设计模式,并将其付诸实践,这才是王道。其他一切都是浮云,,,

    回复
    1. Xiaoxia 文章作者

      嗯,这个可能是每个软件工程师的基本要求吧!
      不过说实话呢,我以后的方向不一定是做软件开发的,所以我自己对算法和模式的要求并不高,只要大部分人能看懂我的代码,能从我的代码学到东西,就很不错啦!

      回复
  2. 清风剑

    三个进程同时listen同一个端口,那么遇到连接的时候是怎么决定由哪一个进程accept呢?求解
    另外,在Linux上的子进程不是可以共享父进程的fd么?为什么要通过stdin去传递呢?

    回复
    1. Xiaoxia 文章作者

      这个处理方式,Linux和Windows好像不同的吧。Linux好像是随机寻找空闲的分配。
      为什么要通过stdin去传递呢?因为调用了exec系统调用,已经装载了新的程序,应该只有标准文件描述符被保留下来吧,你觉得呢?

      回复
      1. 清风剑

        似乎是所有fd都会保留的啊
        ##########file parent.py ########
        from multiprocessing import Process
        import os

        def info(content):
        print content
        with fh as f:
        print f.readlines()
        print ‘process id:’, os.getpid()

        def main():
        print ‘main line:%s’ %os.getpid()
        global fh
        fh = open(‘./readme’)
        p = Process(target=info, args=(‘child’,))
        p.start()
        p.join()

        if __name__ == ‘__main__’:
        main()

        ###############
        output:
        main line:4156
        child
        [‘hello world\n’]
        process id: 4157

        回复
            1. Xiaoxia 文章作者

              也就是说,默认不设置CLOEXEC,在子进程里调用了exec,父进程的文件描述符还是会被保留下来的?

              回复
  3. Mike

    用stdin发送fd?!?把stdin覆盖了呀……

    话说,UNIX域套接字发送fd正统一点……

    —删— 我以为你是自己写的,原来是fork exec到php进程的。。怪不得。。

    回复
    1. Mike

      把我的想法贴出来(貌似乃用graphviz啊)
      digraph G
      {
      “Request” -> “Daemon”
      “Daemon” -> “Service 1″ [label=”fork”]
      “Daemon” -> “Service 2″ [label=”fork”]
      “Service 1” -> “Subprocess #1″ [label=”same fd”]
      “Service 1” -> “Subprocess #2″ [label=”same fd”]
      “Service 1” -> “Subprocess #3″ [label=”same fd”]

      “Service 2″ -> ” Subprocess #1″ [label=”same fd”]
      “Service 2″ -> ” Subprocess #2″ [label=”same fd”]
      “Service 2″ -> ” Subprocess #3″ [label=”same fd”]
      }

      回复
      1. Xiaoxia 文章作者

        嗯,原来你也用graphviz哦!
        这个想法跟我想的差不多啊,我也是有计划写一个Daemon,用它来运行和守护我用python写的一堆服务程序。
        unix domain socket还没真正使用过,所以通常都用stdin来传递,没有觉得正统不正统的,只想简简单单点解决问题。

        回复
            1. Mike

              不要问GLib,你应该问M$,因为POSIX煞费苦心还把AF_UNIX更名为AF_LOCAL了,M$支持不支持?只有它自己知道。

              (PS. Windows 7有一个隐藏得很深的POSIX子系统,不过我没测试过。)

              回复
    2. Xiaoxia 文章作者

      恩,这个是学习了fcgi进程管理器的做法,可以spawn php-cgi的进程,也可以让自己写的服务进程也使用这种模式进行工作 🙂

      回复

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据