针对 TCP 应该如何 Socket 编程? image.png

  • 服务端和客户端初始化 socket ,得到文件描述符;
  • 服务端调用 bind ,将绑定在 IP 地址和端口;
  • 服务端调用 listen ,进行监听;
  • 服务端调用 accept ,等待客户端连接;
  • 客户端调用 connect ,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close ,那么服务端 read 读取数据的时候,就会读取到了 EOF ,待处理完 数据后,服务端调用 close ,表示连接关闭。

    accept 发生在三次握手的哪一步?

    我们先看看客户端连接服务端时,发送了什么?
    image.png

  • 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 client_isn,客户端进入 SYN_SENT 状态;

  • 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端 进入 SYN_RCVD 状态;
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立 成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1;
  • 应答包到达服务器端后,服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连 接也建立成功,服务器端也进入 ESTABLISHED 状态。

accept函数主要用于服务器端,一般位于listen函数之后,默认会阻塞进程,直到有一个客户请求连接,建立好连接后,它返回的一个新的套接字 socketfd_new ,此后,服务器端即可使用这个新的套接字socketfd_new与该客户端进行通信,而sockfd 则继续用于监听其他客户端的连接请求。

从上面的描述过程,我们可以得知**客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握 手成功之后。

客户端调用 close 了,连接是断开的流程是什么?

我们看看客户端主动调用了 close ,会发生什么?
image.png

  • 客户端调用 close ,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;
  • 服务端接收到了 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序 可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就 意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;
  • 接着,当处理完数据后,自然就会读到 EOF ,于是也调用 close 关闭它的套接字,这会使得服务端会发出一个 FIN 包,之后处于 LAST_ACK 状态;
  • 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
  • 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
  • 客户端经过 2MSL 时间之后,也进入 CLOSE 状态;