套接字 API 接受()函数是如何工作的?

套接字 API 是 TCP/IP 和 UDP/IP 通信(即我们所知的网络代码)的事实标准。然而,它的核心功能之一 accept()有点神奇。

借用一个半正式的定义:

在服务器端使用接受()。 它接受接收到的传入尝试 创建新的 TCP 连接 远程客户端,并创建一个新的 与该套接字关联的套接字 这个连接的地址对。

换句话说,accept返回一个新的套接字,服务器可以通过该套接字与新连接的客户端通信。旧的套接字(在其上调用 accept)在同一端口上保持打开状态,侦听新的连接。

accept是如何工作的?如何实施?关于这个话题有很多混淆。许多人声称接受打开一个新的端口,并通过它与客户端进行通信。但这显然不是真的,因为没有新的港口开放。您实际上可以通过相同的端口与不同的客户端进行通信,但是如何进行通信呢?当多个线程在同一端口上调用 recv时,数据如何知道去哪里?

我猜它是沿着客户机地址的线路与套接字描述符相关联的东西,每当数据通过 recv传输到正确的套接字时,它就被路由到正确的套接字,但我不确定。

如果能对这种机制的内部工作原理有一个彻底的解释就太好了。

53485 次浏览

您的困惑在于认为套接字是由 ServerIP: ServerPort 标识的。实际上,套接字是由四组信息唯一识别的:

Client IP : Client PortServer IP : Server Port

因此,虽然在所有接受的连接中,服务器 IP 和服务器端口都是常量,但是客户端信息允许它跟踪所有事情的进展情况。

举例说明:

假设我们在 192.168.1.1:80有一个服务器和两个客户机,10.0.0.110.0.0.2

10.0.0.1在本地端口 1234上打开一个连接并连接到服务器。现在,服务器有一个套接字标识如下:

10.0.0.1:1234 - 192.168.1.1:80

现在,10.0.0.2在本地端口 5678上打开一个连接并连接到服务器。现在,服务器有两个套接字,标识如下:

10.0.0.1:1234 - 192.168.1.1:80
10.0.0.2:5678 - 192.168.1.1:80

正如另一个人所说,套接字是由4个元组(客户端 IP、客户端端口、服务器 IP、服务器端口)唯一标识的。

在 Server IP 上运行的服务器进程维护一个活动套接字的数据库(这意味着我不关心它使用的是什么样的表/列表/树/数组/魔法数据结构) ,并监听 Server Port。当它接收到消息(通过服务器的 TCP/IP 堆栈)时,它将根据数据库检查客户端 IP 和端口。如果在数据库条目中找到了 Client IP 和 Client Port,则将消息传递给现有的处理程序,否则将创建新的数据库条目并生成新的处理程序来处理该套接字。

在 ARPAnet 的早期,某些协议(FTP 就是其中之一)会监听指定端口的连接请求,并使用切换端口进行响应。这个连接的进一步通信将通过切换端口进行。这样做是为了提高每个数据包的性能,因为当时的计算机数量级比现在慢了好几倍。

当我学习这个的时候,令我困惑的是,术语 socketport表明它们是物理的东西,而实际上它们只是内核用来抽象网络细节的数据结构。

因此,实现数据结构是为了能够区分来自不同客户端的连接。至于实现的 怎么做,答案要么是 a)没关系,套接字 API 的目的恰恰是实现不应该有关系,要么是 b)看看就知道了。除了强烈推荐的 Stevens 书籍提供了一个实现的详细描述之外,还可以查看 Linux、 Solaris 或 BSD 的源代码。

只是为了补充用户“17 of 26”给出的答案

套接字实际上包含5个元组(源 ip、源端口、目标 ip、目标端口、协议)。这里的协议可以是 TCP 或 UDP 或任何传输层协议。该协议在 IP 数据报中的“ protocol”字段的数据包中进行标识。

因此,可能有不同的应用程序在服务器上通信到同一个客户机完全相同的4元组,但不同的协议领域。比如说

服务器端的 Apache 通话(server1.com: 880-client1:1234 on TCP) 还有 魔兽世界对话(server1.com: 880-client1:1234 on UDP)

在这两种情况下,客户机和服务器都将处理这个问题,因为 IP 包中的协议字段是不同的,即使所有其他4个字段都是相同的。