套接字 API 是 TCP/IP 和 UDP/IP 通信(即我们所知道的网络代码)的事实标准。但是,它的核心功能之一,accept()
有点神奇。
借用一个半正式的定义:
accept() 在服务器端使用。它接受从远程客户端收到的创建新 TCP 连接的传入尝试,并创建一个与此连接的套接字地址对关联的新套接字。
换句话说,accept
返回一个新的套接字,服务器可以通过该套接字与新连接的客户端进行通信。旧套接字(在其上调用 accept
)保持打开状态,在同一端口上侦听新连接。
accept
是如何工作的?它是如何实施的?关于这个话题有很多困惑。许多人声称接受打开一个新端口,您通过它与客户端通信。但这显然不是真的,因为没有打开新端口。您实际上可以通过同一个端口与不同的客户端进行通信,但是如何?当多个线程在同一个端口上调用 recv
时,数据如何知道去哪里?
我想这与客户端地址与套接字描述符相关联的内容类似,每当数据通过 recv
时,它都会被路由到正确的套接字,但我不确定。
很高兴能彻底解释这种机制的内部工作原理。
您的困惑在于认为套接字是由服务器 IP 标识的:服务器端口。实际上,套接字由四组信息唯一标识:
Client IP : Client Port
和 Server IP : Server Port
因此,虽然服务器 IP 和服务器端口在所有接受的连接中都是不变的,但客户端信息是允许它跟踪所有内容的原因。
澄清事情的例子:
假设我们在 192.168.1.1:80
有一个服务器和两个客户端,10.0.0.1
和 10.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
只是为了添加用户“17 of 26”给出的答案
套接字实际上由 5 个元组组成——(源 ip、源端口、目标 ip、目标端口、协议)。这里的协议可以是 TCP 或 UDP 或任何传输层协议。该协议在数据包中由 IP 数据报中的“协议”字段标识。
因此,服务器上的不同应用程序可能必须在完全相同的 4 元组上与同一客户端通信,但协议字段不同。例如
服务器端的 Apache 正在通话(TCP 上的 server1.com:880-client1:1234)和魔兽世界正在通话(UDP 上的 server1.com:880-client1:1234)
即使所有其他 4 个字段相同,客户端和服务器都会处理此问题,因为在两种情况下 IP 数据包中的协议字段都不同。
当我学习这个时,让我感到困惑的是,术语 socket
和 port
暗示它们是物理的,而实际上它们只是内核用来抽象网络细节的数据结构。
因此,数据结构被实现为能够区分来自不同客户端的连接。至于它们是如何实现的,答案是 a.) 没关系,sockets API 的目的恰恰是实现不重要,或者 b.) 看看。除了强烈推荐的 Stevens 书籍提供了一种实现的详细描述之外,还可以查看 Linux 或 Solaris 或 BSD 中的源代码。
正如其他人所说,套接字由 4 元组(客户端 IP、客户端端口、服务器 IP、服务器端口)唯一标识。
在服务器 IP 上运行的服务器进程维护一个活动套接字的数据库(这意味着我不关心它使用什么样的表/列表/树/数组/魔术数据结构)并侦听服务器端口。当它接收到消息(通过服务器的 TCP/IP 堆栈)时,它会根据数据库检查客户端 IP 和端口。如果在数据库条目中找到客户端 IP 和客户端端口,则将消息传递给现有的处理程序,否则将创建一个新的数据库条目并生成一个新的处理程序来处理该套接字。
在 ARPAnet 的早期,某些协议(一种是 FTP)会监听指定端口的连接请求,并以切换端口进行回复。该连接的进一步通信将通过切换端口。这样做是为了提高每个数据包的性能:当时计算机的速度要慢几个数量级。