accept()被定义为始终创建另一个文件描述符以接受来自客户端的新连接,但如果事先知道我们只接受一个客户端和一个连接,为什么还要创建一个新的文件描述符呢? ?在任何定义的标准中是否有这种情况的描述?
答案 0 :(得分:3)
在设计API时,我认为通用是有价值的。为什么有2个API,一个用于接受可能的多个连接,另一个用于使用较少的文件描述符?后一种情况似乎不足以证明当我们今天使用的API会对全新的系统调用进行证明,并且您可以使用它来实现您想要的行为。
另一方面,Windows有AcceptEx
,它允许您重复使用先前表示其他不相关的先前连接的套接字的先前套接字句柄。我相信这是为了避免在断开连接后再次进入内核以关闭套接字的性能损失。不完全是你所描述的,但模糊地相似。 (虽然意味着扩大规模而不是缩小规模。)
更新:一个月后,我觉得你为此创造了一笔赏金,这有点奇怪。我认为答案很清楚 - 当前的接口可以做你要求的就好了,并且没有动力为你的边缘情况添加,更不用说标准化了一个新界面。使用当前接口,close
成功后您可以accept
原始套接字,并且不会伤害任何人。
答案 1 :(得分:1)
RFC 793中描述的TCP协议描述了术语 socket 和 connection 。 套接字是IP地址和端口号对。 连接是一对套接字。从这个意义上说,相同的套接字可以用于多个连接。从这个意义上说,正在使用传递给socket
的{{1}}。由于套接字可用于多个连接,并且传递给accept()
的{{1}}代表套接字,因此API会创建一个新的{{1}代表连接。
如果您只是想要一种简单的方法来确保socket
为您创建的那个accept()
与您用于执行socket
调用的套接字相同,那么请使用包装器FTW:
socket
如果您想要一种让客户端和服务器相互连接的方式,而不是每边只创建一个accept()
,那么这样的API确实存在。 API为accept()
,当您获得simultaneous open时,它会成功。
int accept_one (int accept_sock, struct sockaddr *addr, socklen_t *addrlen) {
int sock = accept(accept_sock, addr, addrlen);
if (sock >= 0) {
dup2(sock, accept_sock);
close(sock);
sock = accept_sock;
}
return sock;
}
如果您担心性能问题,我相信这些担忧是错误的。使用TCP协议,您必须在客户端和服务器之间的网络上至少等待一次完整的往返,因此处理另一个套接字的额外开销可以忽略不计。如果客户端和服务器位于同一台机器上,那么您可能会关心这种开销的可能情况,但即便如此,如果连接非常短暂,这只是一个问题。如果连接如此短暂,那么重新设计解决方案以使用更便宜的通信介质(例如,共享内存),或对数据应用框架并使用持久连接可能会更好。
答案 2 :(得分:0)
因为不需要。如果您只有一个客户端,则只执行一次操作;你有足够的文件描述符;与网络开销相比,“开销”非常小。您希望“优化”作为API设计者的情况是您拥有数千个客户端。
答案 3 :(得分:0)
在listen返回的套接字和accept返回的套接字描述符之间唯一的变化是新套接字处于ESTABILISHED状态而不是LISTEN状态。所以你可以重新使用调用后创建的套接字听功能接受其他联系。
答案 4 :(得分:0)
由于accept()旨在接受新客户。
它需要三件事,一般套接字描述符必须绑定到特定端口号以便在该端口号上提供服务,一个结构用于存储客户端信息,另一个int值用于存储客户端的大小。
它返回一个new_socket_descriptor,用于服务于服务器接受的特定客户端。
第一个参数是用于接受客户端的套接字描述符。对于并发服务器,它总是用于接受客户端连接。因此它不应该通过任何accept()调用来修改。
所以accept()返回新的套接字描述符来为新连接的客户端提供服务。
服务器套接字描述符(第一个参数)绑定到服务器property.server属性总是设计为固定类型,即其端口号,连接类型,协议族都是固定的。因此,一次又一次使用相同的文件描述符。
另一点是,这些属性用于过滤为特定服务器建立的客户端连接。
对于客户端,每个客户端的信息不同,每个客户端使用的最小IP地址都是唯一的,并且这些属性绑定到新文件描述符,因此始终是accept()函数成功返回的新文件描述符。
注: -
您需要一个文件描述符,以便客户端接受,并且根据您要接受/服务的最大客户端数量,使用那么多文件描述符来服务客户端。
答案 5 :(得分:0)
答案是,您在当前API中只处理了一个连接 的具体示例,并从一开始就将其设计到API的用例中。关于如何处理单个套接字案例的解释在于套接字程序在最初发明BSD套接字接口时的工作方式。
套接字API旨在始终能够接受连接。基本原则是当连接到达时,程序应该最终决定是否接受连接。但是,在做出此决定时,应用程序也必须永远不会错过连接。因此,API仅设计为并行,并且accept()
被指定为从listen()
返回不同的套接字,以便listen()
可以在应用程序做出决定时继续侦听进一步的连接请求关于刚刚收到的连接请求。这是一项基本的设计决策,并未在任何地方记录;我们只是假设套接字程序必须以这种方式工作才能发挥作用。
在线程发明之前的过去,在类Unix系统上实现套接字服务器所需的并行性依赖于fork()
。接受了新连接,程序将使用fork()
将自身分成两个相同的副本,然后一个副本将处理新连接,而原始副本继续侦听传入连接尝试。在fork()
模型中,即使accept()
返回一个新的文件句柄,也只支持处理一个连接的用例,只需让" listen"程序退出的副本,而第二个"接受" copy处理单个连接。
以下伪代码显示了这一点:
fd = socket();
listen(fd, 1); /* allow 1 unanswered connection in the backlog */
switch (fork())
{
case 0: break; /* child process; handle connection */
case -1: exit (1); /* error. exit anyway. */
default: exit (0); /* parent process; exit as only one connection needed */
}
/* if we get here our single connection can be accepted and handled.
*/
accept_fd = accept(fd);
这种编程范例意味着无论服务器是接受单个连接还是处理多个连接的循环,这两种情况下的代码几乎完全相同。现在我们有线程而不是fork()
。但是,由于范例仍然存在于今天,因此从来没有必要更改或升级套接字API。