【问题标题】:Correct order of socket calls for pre-fork serverpre-fork 服务器的套接字调用的正确顺序
【发布时间】:2016-04-02 10:27:00
【问题描述】:

如果我这样做会发生什么(下面的伪代码):

s = socket
bind s 
fork .... 
   (... at child ...)
   listen s 
   conn = accept s

?我应该改用:

s = socket 
bind s 
listen s 
fork .... 
   conn = accept s 

?

哪一个是正确的?另外,我是否需要针对这种特定情况在套接字上设置任何选项?

【问题讨论】:

标签: linux sockets posix


【解决方案1】:

哪个是正确的[,在fork()之前或之后调用listen()]?

调用listen() 之前 fork() 是正确的。 listen() 的作用是将底层套接字标记为准备好连接积压的连接。它只需要调用一次。

从代码质量的角度来看,我会将另一种方法标记为“不正确”,因为它是多余且令人困惑的。

虽然反复拨打listen() 无害,但还是很可疑。规范没有说明进行后续调用时会发生什么,只是说面向连接的套接字应该"maintain a queue of outstanding connection indications",即待处理连接的积压。后续调用是否能够更改积压的大小?你想要吗?事实上,并不是所有的操作系​​统都允许你查询 backlog 的大小(FreeBSD 有,例如,SO_LISTENQLIMIT)。

【讨论】:

    【解决方案2】:

    在 Linux(自 3.9 起)(以及Mac OSFreeBSD 以及可能的其他版本)上,您还可以选择使用SO_REUSEPORT

    // _DEFAULT_SOURCE for htobe16 for the port number, you may need _BSD_SOURCE instead
    #define _DEFAULT_SOURCE
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <endian.h>
    
    int main() {
        struct sockaddr_in6 sa;
        int v = 1;
    
        // prepare ipv6 address [::]:1345 
        memset(&sa, 0, sizeof(sa));
        sa.sin6_family = AF_INET6;
        sa.sin6_port = htobe16(1345);
    
        int s = socket(AF_INET6, SOCK_STREAM, 0);
        perror("socket");
    
        // the key point: enable SO_REUSEPORT
        setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &v, sizeof(v));
        perror("setsockopt");
    
        // from this point on just plain old socket use
        bind(s, (struct sockaddr*)&sa, sizeof(sa));
        perror("bind");
        listen(s, 0);
        perror("listen");
    
        while (1) {
            int conn = accept(s, NULL, NULL);
            perror("accept");
            close(conn);
            perror("close");
        }
    
        return 0;
    }
    

    这种方法的优点是进程之间不需要父/子关系。此外,联机帮助页 (setsockopt(7)) 表明这比传统方法具有性能改进。

    在这种情况下,您可以在 调用socket 之前分叉。唯一的要求是所有涉及的进程在其套接字上设置SO_REUSEPORT 并共享相同的有效UID。

    【讨论】:

    • 这太酷了!它可能无法在 Mac OS X 中运行,所以我不会对重构以启用 pre-fork 的日子感到太难过,但我肯定会寻找在 Linux 中使用它的方法。
    • 请务必检查。 SO_REUSEPORT 落地的时候是一个更大的东西,我可以想象 BSD 和 Mac OS 也实现了这个。
    • 查看编辑,我发现苹果手册页提示 SO_REUSEPORT 可用。
    • 谢谢,我的 pre-fork 不能很好地与 Haskell 的 epoll 事件管理器一起工作,所以我将不得不求助于这些 ;-) 很高兴知道它在 Mac 中!
    猜你喜欢
    • 2014-11-08
    • 1970-01-01
    • 1970-01-01
    • 2012-04-10
    • 2015-06-24
    • 2014-02-21
    • 2015-03-15
    • 2018-05-06
    • 1970-01-01
    相关资源
    最近更新 更多