欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

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

程序员文章站 2022-06-27 22:50:34
TCP/IP四次挥手 在TCP/IP协议取消连接的时候会进行四次挥手过程:         当某个应用进程主动关闭的时候,该端TCP会发送一个FIN分节,表示数据发送完毕。   接收...

TCP/IP四次挥手

在TCP/IP协议取消连接的时候会进行四次挥手过程:
  【TCP/IP】TIME_WAIT状态及地址reuse问题,SO_REUSEADDR参数详解
  
  当某个应用进程主动关闭的时候,该端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。喎?http: www.2cto.com/kf/ware/vc/"="" target="_blank" class="keylink">vcD4NCjxoMSBpZD0="tcp四次挥手状态转换">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提供如下四个功能:

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