Xiaoxia[PG]

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

Internet、Linux、我的代码36 条评论

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

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

proc

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

工作原理如下:

proc2

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

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三次,则让三个进程都完全结束。

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

36 条评论

  1. Haproxy的第四层模式可以参考

    1. Haproxy是个负载均衡的软件吧,你玩这个吗???

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

        1. 在哪里编写规则呢?能够写算法如何让不同的进程进行accept?

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

            1. 嗯,谢谢!!!
              我正需要这玩意,学习一下。

  2. 能不能写个C版本的呢?

    1. 已经写过了,详见FCGI进程管理器:
      http://xiaoxia.org/3646.html

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

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

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

    1. 这个处理方式,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. 好像有道理,不过你测试执行exec看看,调用一个不同的程序。

          1. 设置了CLOEXEC才会在exec时关闭。。。

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

                1. Mike~ 原来你早就知道小虾的新博客地址啊,怎么都不告诉我呢。。。

                  1. 不是新旧地址都可以访问的吗?

                    1. 好吧,旧的我也多年没去过了。。。

  5. 這個site每日traffic ip系幾多?有1000沒?

    1. no... 200左右ip,怎么了?

        1. 不知道这个怎么算的。

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

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

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

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

        1. glib有基于UNIX domain socket的send_fd和recv_fd的封装哦~

          1. 容易跨平台不?
            这是我比较关心的问题。

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

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

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

    1. 是哪位?

  7. 看来关键是SO_REUSEADDR 这个参数在起作用啊。。嘿嘿

  8. google搜一个东西,看到头像诧异怎么那么熟悉。。

    1. 世界就这么小..

发表评论

评论会经过自动审核,链接请勿超过 3 个。