pre-fork 服务器套接字调用的正确顺序
Correct order of socket calls for pre-fork server
如果我这样做会发生什么(下面的伪代码):
s = socket
bind s
fork ....
(... at child ...)
listen s
conn = accept s
?我应该改用:
s = socket
bind s
listen s
fork ....
conn = accept s
?
哪个是正确的?另外,我是否需要为这个特定场景在套接字上设置任何选项?
Which one is correct [, calling listen()
before or after fork()
]?
在 fork()
之前调用 listen()
是正确的。 listen()
的作用是将底层套接字标记为已准备好连接,但存在连接积压。只需调用一次。
我将从代码质量的角度来标记另一种方法 "incorrect",因为它是多余的且令人困惑。
虽然重复调用 listen()
没有害处,但它很可疑。该规范没有说明进行后续调用时会发生什么,只是 connection-oriented 套接字应 "maintain a queue of outstanding connection indications", that is, a backlog of pending connections. Would a subsequent call be able to change the size of the backlog? Would you want it to? Indeed, not all operating systems even let you query the size of the backlog (FreeBSD has, e.g., SO_LISTENQLIMIT).
在 Linux(自 3.9 起)(以及 Mac OS and FreeBSD 和可能的其他版本),您还可以选择使用 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;
}
这种方法的优点是进程之间不需要 parent/child 关系。此外,联机帮助页 (setsockopt(7)
) 表明这比传统方法有性能改进。
在这种情况下,您可以在 调用 socket
之前分叉 。唯一的要求是 all 涉及的进程在其套接字上设置 SO_REUSEPORT
并共享相同的有效 UID。
如果我这样做会发生什么(下面的伪代码):
s = socket
bind s
fork ....
(... at child ...)
listen s
conn = accept s
?我应该改用:
s = socket
bind s
listen s
fork ....
conn = accept s
?
哪个是正确的?另外,我是否需要为这个特定场景在套接字上设置任何选项?
Which one is correct [, calling
listen()
before or afterfork()
]?
在 fork()
之前调用 listen()
是正确的。 listen()
的作用是将底层套接字标记为已准备好连接,但存在连接积压。只需调用一次。
我将从代码质量的角度来标记另一种方法 "incorrect",因为它是多余的且令人困惑。
虽然重复调用 listen()
没有害处,但它很可疑。该规范没有说明进行后续调用时会发生什么,只是 connection-oriented 套接字应 "maintain a queue of outstanding connection indications", that is, a backlog of pending connections. Would a subsequent call be able to change the size of the backlog? Would you want it to? Indeed, not all operating systems even let you query the size of the backlog (FreeBSD has, e.g., SO_LISTENQLIMIT).
在 Linux(自 3.9 起)(以及 Mac OS and FreeBSD 和可能的其他版本),您还可以选择使用 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;
}
这种方法的优点是进程之间不需要 parent/child 关系。此外,联机帮助页 (setsockopt(7)
) 表明这比传统方法有性能改进。
在这种情况下,您可以在 调用 socket
之前分叉 。唯一的要求是 all 涉及的进程在其套接字上设置 SO_REUSEPORT
并共享相同的有效 UID。