套接字 API accept() 函数如何工作?

Posted

技术标签:

【中文标题】套接字 API accept() 函数如何工作?【英文标题】:How does the socket API accept() function work? 【发布时间】:2010-10-04 02:14:51 【问题描述】:

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

借用一个半正式的定义:

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

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

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

我猜这类似于客户端地址与套接字描述符相关联,每当数据通过recv 时,它都会被路由到正确的套接字,但我不确定。

如果能对这种机制的内部运作进行彻底的解释,那就太好了。

【问题讨论】:

因此对于每个客户端请求,都会在服务器端打开一个全新的套接字连接。服务器必须始终在 80 点打开以侦听来电。如果它收到一个调用,它会立即使用下面提到的四个元组创建一个新的套接字,这将在客户端和服务器之间建立一个 TCP 连接。我的理解正确吗? 这是一个非常基本的问题,我最近在一次采访中对此进行了测试:***.com/questions/24871827/…如果您对此有任何 cmet,请发布 @brainstorm 仅当您完全忽略 HTTP keep-alive 的存在时。 【参考方案1】:

您的困惑在于认为套接字是由服务器 IP 标识的:服务器端口。实际上,套接字由四组信息唯一标识:

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

【讨论】:

我不知道实现细节(可能因平台而异),我只知道从概念上讲,套接字是由我描述的四重信息标识的。 你有这方面的参考吗? 随机问题:如果正在使用 NAT,并且同一网络上的两个客户端在连接到服务器时尝试使用相同的本地端口,会发生什么情况?例如,如果 10.0.0.1 和 10.0.0.2 都连接到外部 IP 为 192.168.0.1 的路由器,那么 192.168.1.1 的服务器会看到来自 192.168.0.1 的两个连接。如果随机数生成器的一些侥幸使 10.0.0.1 和 10.0.0.2 都选择了相同的本地端口,在这种情况下会发生什么? 路由器中的 NAT 支持负责处理那里的细节。网络流量实际上是通过两个连接 - 客户端到路由器,以及路由器到服务器。路由器在两个不同的端口 192.168.0.1:1234 和 192.168.0.1:5678 上进行传出连接。然后传入的流量由路由器重定向到正确的客户端。 如果一个socket被四方识别,那么监听socket的四方信息是什么?【参考方案2】:

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

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

因此,服务器上的不同应用程序可能必须在完全相同的 4 元组上与同一客户端通信,但协议字段不同。例如

服务器端的 Apache 正在通话(TCP 上的 server1.com:880-client1:1234) 和 魔兽世界正在通话(server1.com:880-client1:1234 on UDP)

客户端和服务器都会处理这个,因为在两种情况下,IP 数据包中的协议字段是不同的,即使其他 4 个字段都相同。

【讨论】:

【参考方案3】:

当我学习这个时,让我感到困惑的是,socketport 这两个术语暗示它们是物理的,而实际上它们只是内核用来抽象网络细节的数据结构。

因此,数据结构被实现为能够将来自不同客户端的连接分开。至于 如何 它们是如何实现的,答案是 a.) 没关系,sockets API 的目的恰恰是实现不重要,或者 b.) 只是有一个看。除了强烈推荐的 Stevens 书籍提供了一种实现的详细描述之外,请查看 Linux 或 Solaris 或 BSD 之一中的源代码。

【讨论】:

是的,大多数网络术语只是为某些位集合和基于它们的值(“协议标识符”、“路由”、“绑定”、“套接字”等.)。您网卡的所有硬件 旨在接收比特流。与计算机上的程序相关的情况由驱动程序和操作系统决定。如果我们愿意,明天我们可以摆脱所有这些术语,但传递比特流的原则似乎很重要......【参考方案4】:

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

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

在 ARPAnet 的早期,某些协议(例如 FTP)会侦听指定端口以获取连接请求,并以切换端口进行回复。该连接的进一步通信将通过切换端口。这样做是为了提高每个数据包的性能:当时计算机的速度要慢几个数量级。

【讨论】:

您能详细说明“切换端口”部分吗? 这要么是一些pre-TCP协议的描述,要么过于简化。尝试连接到侦听套接字的客户端发送一个特殊的数据包来建立连接(SYN 位设置)。创建新套接字的数据包和使用现有套接字的数据包之间有明显的区别。 ...发送一个特殊的数据包来建立连接(SYN位设置)。这(据我所知)导致协议栈将其提供给'the'侦听器(如果有),这就是为什么每个地址/端口/协议组合只能有一个侦听端口的原因。我不确定这是在规范中还是仅仅是实现约定。 第二段没有正确描述 TCP 层或服务器进程中发生的情况。服务器进程不需要维护任何类型的套接字的数据结构,也不需要检查传入的 IP:port 对与任何内容。这就是插座的用途。 FTP 使用单独的数据端口,而不是所有“进一步的通信”,并且是为了简化协议,而不是出于性能原因。使用新端口,但不会以任何方式提高性能。 “维护一个数据库(意思是我不在乎它使用什么样的表/列表/树/数组/魔术数据结构)”:) 我通常称之为“表”(或者也许“图表”或“决策树”)。 “数据库”向我建议了一些实现。

以上是关于套接字 API accept() 函数如何工作?的主要内容,如果未能解决你的问题,请参考以下文章

谁能告诉我accept()函数返回一个新的套接字,所谓新套接字怎么理解?

2018/10/04-网络API-《恶意代码实战分析》

调用 fork() 后 SSL_accept 挂起

connect函数详解

socket 编程中 accept 函数返回

请教套接字编程中的Accept()函数