>

在进行网络编程时,服务端的过程通常是:socket()->bind()->listen(),当有客户端程序 connect() 时,服务端调用 accept() 来获得一个连接。下面是几个常见的问题:

问题1:客户端和服务端的 socket 是怎么通信的?

我们知道,socket 有一个四元组:(目的 ip, 目的 port、源 ip,源 port)。这个四元组就可以保证客户端和服务端之间可以通信。
比如服务端是个80的 web 服务,ip是1.1.1.1。服务端启动时会生成一个 socket 绑定到 80 端口监听。这时一个 ip 为 2.2.2.2的客户端要去访问这个服务端时,先要生成一个 socket 。系统会为这个 socket 选择一个端口,比如 65535,那么客户端通个 (1.1.1.1, 80, 2.2.2.2, 65535) 通过 connect() 访问服务端。

问题2:accept 中的 socket 和 listen 监听的 socket 的端口相同吗?

客户端通过 connect() 访问服务端80,通过3次握手后,进入 accept 队列。服务端调用 accept() 时,会生成一个新的 socket 和客户端通信,之前的 socket 仍然继续监听 80 端口。那么这个新生成的端口还是 80 吗?答案是肯定的,否则客户端那边的 socket 就无法和新生成的 socket 通信。看 accept 源码,会发现新生成的 socket 会拷贝监听 socket 的信息。因此两者的端口号相同。

问题3:服务端是怎么区分客户端的信息是给 listen 的 socket 还是 accept 的 socket 的?

如果监听的 socket 和新生成的 socket 都是使用 80 端口,那么客户端发给 80 端口的信息,怎么区分是给哪个 socket ?是通过客户端 port 做路由的。listen 使用的 socket 是没有客户端信息的,它的客户端端口为*;而 accept 时新生成的 socket 是有客户端端口号。如下图,第一行是监听的 socket ,第二行是 accept 后生成的 socket 。因此通过四元组中的客户端 port 就可将客户端信息路由到了正确的 socket 上。