【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。喎?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状态有两个存在的理由:
首先看第一个理由:
如果在四次挥手中,最终的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:>上一篇: OAuth协议详细介绍
下一篇: Flutter 淡入淡出效果