监听套接字的套接字选项是否会在 accept() 中继承?

33
假设传递给 accept 的监听套接字在 setsockopt 中设置了非默认选项。这些选项(部分或全部?)是否会被接受连接的结果文件描述符继承?

3
如果有疑问,就进行测试。我知道这不是一个确定的答案,但从其他答案来看,可能不存在这样的答案(而且实现也会发生变化,有时是意外的,有时则是不能阻挡的进步之路)。 - Gil
4
如果您正在编写针对完全受您控制的特定目标的程序(例如嵌入式系统,您可以选择其运行的硬件、内核/库/软件版本等),那么"测试"是一个很好的答案。但如果您的目标是编写可移植应用程序,则它不是非常有用的。即使在嵌入式系统上,使用"测试"来回答这类问题也会产生重大成本:当您发现需要升级软件时,您必须担心之前得到的结果是否仍然正确,如果不正确,您可能会被困在旧软件中... - R.. GitHub STOP HELPING ICE
5
@Gil:这就是为什么你要写明确定义的行为,而不是通过测试当前实现找到的随机东西。 - R.. GitHub STOP HELPING ICE
2
有几个选项不能像这样进行测试,例如SO_BINDTODEVICE套接字选项仅适用于setsockopt而不适用于getsockopt。因此,您只能尝试设置此选项,并希望您的套接字绑定到该接口。只有在实际收到数据包并深入使用IP_PACKETINFO的rcvmsg来确定哪个接口接收了数据包后,您才会收到确认。我已经在一个真实产品中完成了这个练习。 - fkl
2
这个小片段“This option, like many others, will be inherited by the socket returned by accept(2), if it was set on the listening socket.”在TCP_USER_TIMEOUT下的http://man7.org/linux/man-pages/man7/tcp.7.html似乎表明有些是继承的,有些则不是。 - rmanna
显示剩余4条评论
4个回答

14

其中一些套接字选项在系统的较低层处理。虽然大多数套接字选项都可使用setsockopt进行设置。参考文献:man setsockopt由于您提到了在任何Linux上一般只使用POSIX,因此accept()(参考:man accept)确实有一定的自主权,可以继承哪些套接字选项并拒绝从侦听fd继承哪些选项。

accept()不会修改作为其参数传递的原始套接字。 accept()返回的新套接字不会继承文件状态标志,例如O_NONBLOCK、O_ASYNC等来自监听套接字的标志。

因此,与其依赖于继承或不继承侦听套接字属性(这将因实现和许可证而异),不如显式设置所需的套接字选项以供接受的套接字使用。(最佳实践)

man页面和机器中的实现代码是accept()行为最相关的规范。在多个Linux变体之间不存在通用或标准规范。


O_选项与setsockopt选项不同。 - R.. GitHub STOP HELPING ICE
虽然它们不能通过setsockopt()设置,但文件状态标志或O_选项可以通过fcntl()系统调用应用于套接字fd。然后可以将套接字传递给accept()。参考:man socket - askmish
我的观点是,这个问题涉及套接字选项,而不是打开文件描述符标志。 - R.. GitHub STOP HELPING ICE
1
由于文件状态标志也是套接字fd可能包含的内容(虽然与套接字选项无关),因此我在这里添加了一个关于accept()行为的观点。 - askmish
你说的是错误的。O_NONBLOCK 可能会被继承。 - Bob

5

不,它们并不一定是继承的。尝试这个示例,它将接收缓冲区大小(SO_RCVBUF)设置为非默认值,并将结果与继承的套接字进行比较。运行此代码,它会监听TCP端口12345,然后从任何其他程序连接到它。

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>

void die(const char *f)
{
  printf("%s: %s\n", f, strerror(errno));
  exit(1);
}

int main(void)
{
  int s = socket(AF_INET, SOCK_STREAM, 0);
  if(s < 0)
    die("socket");

  int rcvbuf;
  socklen_t optlen = sizeof(rcvbuf);
  if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) < 0)
    die("getsockopt (1)");
  printf("initial rcvbuf: %d\n", rcvbuf);
  rcvbuf *= 2;
  if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0)
    die("setsockopt");
  printf("set rcvbuf to %d\n", rcvbuf);

  struct sockaddr_in sin;
  memset(&sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;
  sin.sin_port = htons(12345);
  sin.sin_addr.s_addr = INADDR_ANY;
  if(bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    die("bind");

  if(listen(s, 10) < 0)
    die("listen");

  struct sockaddr_in client_addr;
  socklen_t addr_len = sizeof(client_addr);
  int s2 = accept(s, (struct sockaddr *)&client_addr, &addr_len);
  if(s2 < 0)
    die("accept");
  printf("accepted connection\n");
  optlen = sizeof(rcvbuf);
  if(getsockopt(s2, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &optlen) < 0)
    die("getsockopt (2)");

  printf("new rcvbuf: %d\n", rcvbuf);

  return 0;
}

一台运行Linux 3.0.0-21-generic操作系统的机器所得到的结果:

initial rcvbuf: 87380
set rcvbuf to 174760
accepted connection
new rcvbuf: 262142

2
我不认为这是这种情况(但我可能错了,这里是参考文献)。“SO_RCVBUF设置或获取以字节为单位的最大套接字接收缓冲区。内核在使用setsockopt(2)进行设置时将此值加倍(以允许空间用于簿记开销),并且通过getsockopt(2)返回此加倍值。默认值由/ proc / sys / net / core / rmem_default文件设置,最大允许值由/ proc / sys / net / core / rmem_max文件设置。此选项的最小(加倍)值为256。” http://linux.die.net/man/7/socket. - fkl
@fayyazkl:哦,有趣,我猜这就解释了为什么新的rcvbuf与原始默认值和修改后的值都不同。我修改了它,在原始套接字上执行setsockopt之后立即执行另一个getsockopt,令人惊讶的是,它返回了一个不同的值,然后被继承到了新的套接字中。然而,仅仅因为在这种情况下它被继承了,并不意味着所有选项都必然被继承,或者该行为可以被依赖。 - Adam Rosenfield
当然同意。例如,在问题评论中,我指出了SO_BINDTODEVICE,它只允许设置。因此,您甚至不能使用getsock进行测试以了解是否已设置,或者在接受后绑定仍保持相同的接口。我确信即使许多选项可以安全地继承,但其他选项可能具有不同的行为。 - fkl

3

套接字选项是放置那些不适合其他地方的东西的地方。因此,不同的套接字选项具有不同的继承行为是可以预期的。是否继承套接字选项取决于具体情况。


1
根据我的阅读,对于符合POSIX的实现来说,答案是否定的。从POSIX-2017规范中可以看到,accept()函数会提取挂起连接队列中的第一个连接,使用与指定套接字相同的套接字类型、协议和地址族创建一个新套接字,并为该套接字分配一个新文件描述符。需要注意的是,它明确是一个“新套接字”,而不是“被出队套接字的完整或部分副本”,因此不应具有与该套接字类型和地址族的默认选项不同的选项。虽然复制行为可能是可取的,但这留作平台可能拥有的扩展接口。然而,我没有看到任何平台实现了这个接口,所以它可以被添加到标准中。因此,在使用返回的套接字发送或接收数据之前,由应用程序使用getsockopt()/setsockopt()复制与默认值不同的任何属性,而不是接口的责任。

额外说明,我认为这是更可靠的行为,应该成为标准。用于监听的连接所适用的内容通常与默认值和优化建立数据会话的内容不同,因此这允许应用程序仅设置对于会话最优的选项,而无需担心撤消设置为监听的任何选项。 - M. Ziegast

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接