【TCP/IP】TIME_WAIT状态及地址reuse问题,SO_REUSEADDR参数详解

TCP/IP四次挥手

  在TCP/IP协议取消连接的时候会进行四次挥手过程:
  
  
  当某个应用进程主动关闭的时候,该端TCP会发送一个FIN分节,表示数据发送完毕。
  接收到这个FIN的对端执行被动关闭,这个FIN由TCP进行确认,他的接受也作为一个文件结束符EOF传递给接收端应用进程,因为FIN的接受意味着接收端应用进程在相应连接上再无额外数据可以接受。
  一段时间后,接受到这个文件结束符的应用进程将调用close关闭他的套接字,这导致他的TCP也发送一个FIN。
  接受这个最终FIN的原发送端TCP,即执行主动关闭的那一端确认这个FIN。
  一个FIN占据一个字节的序列号空间,因此每个FIN的ACK确认好就是这个FIN的序列号加一。
  值得注意的是:
  当套接字被关闭的时候,其所在端TCP各自发送了一个FIN。这是由应用进程调用close而发生的,但是必须知道。
  当一个Unix进程无论是自愿的,即调用exit或从main函数返回,还是非自愿的(终止)时,所有打开的文件描述符都被关闭,这导致仍然打开的任何TCP连接上也发出一个FIN。

TCP四次挥手状态转换

  如果某个应用进程在接收到一个FIN之前调用close,即主动关闭,那就转换到FIN_WAIT _1状态,如果一个进程在close之前收到了一个FIN分节,即表示这个进程是被动关闭的,那就转换到CLOSE_WAIT状态。
  如上面的图所示。
  我们假设在这里主动关闭的进程为A,被动关闭的进程为B。
  那么A首先给B发送一个FIN,然后A状态变为FIN_WAIT_1
  接着B接收到FIN,并给A进程返回一个ACK,然后B状态变为CLOSE_WAIT,如果,此时B进程close关闭进程,会给A进程发一个FIN分节,并且B进程状态变为LAST_ACK
  当A进程收到FIN后,A进程状态变为TIME_WAIT,然后A再给B发送ACK,此时进程B状态转换为CLOSED时。
  此时连接取消成功。

TIME_WAIT

  经过上面对四次挥手过程的分析,我们可以得出结论:
  TCP连接后处于TIME_WAIT状态,一定是发起了主动关闭,并且已经收到了对方的FIN,这时候进入了TIME_WAIT 状态。
  TIME_WAIT状态的时间是2倍的MSL(最大生存时间),在TIME_WAIT状态TCP连接实际上已经断掉,但是该端口又不能被新的连接实例使用。这种情况一般都是程序中建立了大量的短连接,而操作系统中对使用端口数量做了限制,那么非常容易出现连接数占满的异常。
  那么TIME_WAIT存在的意义是什么呢?
  其实TIME_WAIT状态有两个存在的理由:
  

  • 可靠地实现TCP全双工连接的终止
  • 允许老的重复分节在网络中消逝

首先看第一个理由:

  如果在四次挥手中,最终的ACK丢失,那么服务器将重新发送他最终的那个FIN,那么客户必须维护状态信息,以允许它重新发送最终那个ACK,要是客户不维护状态信息,它将相应以一个RST,该分解将被服务器解释成一个错误。
  如果TCP打算执行所有必要的工作以彻底终止某个连接上两个方向的数据流,即全双关关闭,那么它必须正确处理连接终止序列4个分节中任何一个分节丢失的情况。
  这也就说明了,为什么执行主动关闭的那一端是TIME_WAIT状态的那一端。
  因为可能不得不重传最终那个ACK的就是那一端

现在看第二个理由
  假设现在A,B有TCP连接,我们现在关闭这个连接,过一段时间后在相同的IP地址和端口之间建立另一个连接,这个连接成为前一个连接的化身,因为他们的IP地址和端口号都相同。
  TCP必须防止来自某个连接的老的重复分组在次连接已终止后重现,即防止上一个连接的数据发送在当前连接中。
  所以TCP将不给处于TIME_WAIT状态的连接发起新的化身。
  这个时候,MSL的作用就来了
  既然TIME_WAIT状态的持续时间是2MSL,这足以让某个方向上的分组最多存活MSL秒及被丢弃,另一个方向也存活MSL秒丢弃,然后老连接中的分组就会消逝。

地址reuse问题

  在写一个unix server程序时,经常需要在命令行上重启它,绝大多数时候工作正常,但是某些时候会报告”bind: address in use”,于是重启失败。
  上面那个就是地址reuse问题,是就是由于TIME_WAIT状态产生的,那么我们如何解决这个问题呢?
  使用setsockopt函数
  
点击查看setsockopt函数具体用法

int option = 1;

if (setsockopt ( masterSocket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option) ) < 0)
{
   die( "setsockopt" );
}

SO_REUSEADDR

  那么编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思?

   这个套接字选项通知内核,如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息,指明”地址已经使用中”。如果你的服务程序停止后想立即重启,而新套接字依旧使用同一端口,此时SO_REUSEADDR 选项非常有用。必须意识到,此时任何非期望数据到达,都可能导致服务程序反应混乱,不过这只是一种可能,事实上很不可能。

一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端口。SO_REUSEADDR 仅仅表示可以重用本地本地地址、本地端口,整个相关五元组还是唯一确定的。所以,重启后的服务程序有可能收到非期望数据。必须慎重使用SO_REUSEADDR 选项。

1、一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。

SO_REUSEADDR用于对TCP套接字处于TIME_WAIT状态下的socket,才可以重复绑定使用。server程序总是应该在调用bind()之前设置SO_REUSEADDR套接字选项。TCP,先调用close()的一方会进入TIME_WAIT状态

2、SO_REUSEADDR和SO_REUSEPORT

SO_REUSEADDR提供如下四个功能:

  1. SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将此端口用做他们的本地端口的连接仍存在。这通常是重启监听服务器时出现,若不设置此选项,则bind时将出错。
  2. SO_REUSEADDR允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可。对于TCP,我们根本不可能启动捆绑相同IP地址和相同端口号的多个服务器。
  3. SO_REUSEADDR允许单个进程捆绑同一端口到多个套接口上,只要每个捆绑指定不同的本地IP地址即可。这一般不用于TCP服务器。
  4. SO_REUSEADDR允许完全重复的捆绑:当一个IP地址和端口绑定到某个套接口上时,还允许此IP地址和端口捆绑到另一个套接口上。一般来说,这个特性仅在支持多播的系统上才有,而且只对UDP套接口而言(TCP不支持多播)。

你可能感兴趣的:(Linux,计算机网络)