由于TCP是双工的, 需要两端各调用一次close, 将自己到另一端发送数据的通道关闭, 整个TCP通道才会被完全关闭.

socket_20150712_151938.png

1 主动关闭端

  1. 客户端主动关闭, 向服务端发送FIN, 自己进入FIN_WAIT_1
  2. 客户端收到服务端发来的ACK信号, 进入FIN_WAIT_2
  3. 客户端收到服务端发来的FIN信号, 发回ACK信号, 自己进入TIME_WAIT
  4. 在TIME_WAIT状态等待了两倍MSL时间后, 进入CLOSED状态, 端口被释放, 连接被正式断开.
  5. 最后等待两倍MSL时间是为了让这个连接的所有数据包都寿终正寝, 这样下一个在这个端口上起的连接不会收到上次连接的数据.

当一个TCP连接的某个端口处于TIME_WAIT状态时, 无法再在这两个端口上建立一个新的连接. 但是在linux中, 即便新连接只用到其中一个端口, 也无法建立, 除非在建立socket的时候指定SO_REUSEADDR选项, 这个选项使得多个socket可以绑定到同一个端口. 由于主动关闭方最终会出现一个TIME_WAIT状态, 在一段时间内无法重用端口, 因此最好由客户端来主动关闭, 服务端的话会在一定时间内无法bind服务端口, 显示错误端口正在被使用.

2 被动关闭端

  1. 服务端收到FIN信号, 进入CLOSE_WAIT状态.
  2. 服务端调用close, 发送FIN信号, 进入LAST_ACK状态.
  3. 服务端收到ACK信号. 进入CLOSED状态, 至此整个TCP连接被断开.

3 实验

使用一个典型的C/S架构做实验.

当server段被强制关闭, 系统自动关闭server端的socket, 但是客户端并不会被关闭, 此时server端socket状态为FIN_WATI_2, 因为没有收到客户端关闭socket 而发来的FIN信号. 客户端socket状态为CLOSE_WAIT, 因为并没有调用CLOSE. 如果一直不操作, 一段时间后FIN_WAIT_2超时, 服务端socket进入closed状态. 而此时客户端强制关闭, 导致系统自动关闭客户端的socket, 客户端socket进入LAST_ACK状态, 但是即使没接收到ACK信号也会直接进入CLOSED状态.

当server端先关闭(即主动关闭TCP连接), 再关闭client端, 连接被正常断开, 但是server端进入TIME_WAIT状态, 短时间内无法再次启动(因为端口被占用了).

4 连接断开检测

由于整个TCP连接的断开需要双方都调用close, 这就需要一个协议. 例如客户端发送exit, 然后自己close, 服务端收到exit之后也close. 还有一种方法是服务端在读取客户端发来的数据时, 调用read函数, 根据read函数的返回值有三种情况:

  1. >0: 表示读到的数据字节数, 说明连接正常
  2. =0: 表示客户端已经调用了close, 并且客户端的数据已经全部接收完毕, 此时服务端可以调用close来关闭整个TCP连接.
  3. <0: 读取错误, 可能原因是在read的过程中被中断信号中断, 导致中断处理完毕时read函数返回了负值, 可以通过检查errno, 如果errno==EINTR, 说明是中断导致的错误, 此时可以重新read一遍. 否则的话就不知道是什么错误了, 也可以调用close.

还有一种更简单粗暴的方法是心跳机制, 即每一段时间发送一个信号, 如果长时间没收到信号, 可以认为已经断开了, 此时可以close或者重连.

Leave a Reply

电子邮件地址不会被公开。 必填项已用*标注

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>