TCP状态

CLOSED:表示初始状态。

LISTEN:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接。

SYN_SENT:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,随即进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。

SYN_RCVD: 该状态表示接收到SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂。此种状态时,当收到客户端的ACK报文后,会进入到ESTABLISHED状态。

ESTABLISHED:表示连接已经建立。

FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。区别是:

FIN_WAIT_1状态是当socket在ESTABLISHED状态时,想主动关闭连接,向对方发送了FIN报文,此时该socket进入到FIN_WAIT_1状态。

FIN_WAIT_2状态是当对方回应ACK后,该socket进入到FIN_WAIT_2状态,正常情况下,对方应马上回应ACK报文,所以FIN_WAIT_1状态一般较难见到,而FIN_WAIT_2状态可用netstat看到。

FIN_WAIT_2:主动关闭链接的一方,发出FIN收到ACK以后进入该状态。称之为半连接或半关闭状态。该状态下的socket只能接收数据,不能发。

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,等2MSL后即可回到CLOSED可用状态。如果FIN_WAIT_1状态下,收到对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

CLOSING: 这种状态较特殊,属于一种较罕见的状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。

CLOSE_WAIT: 此种状态表示在等待关闭。当对方关闭一个SOCKET后发送FIN报文给自己,系统会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对方,如果没有可以 close这个SOCKET,发送FIN报文给对方,即关闭连接。所以在CLOSE_WAIT状态下,需要关闭连接。

LAST_ACK: 该状态是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,即可以进入到CLOSED可用状态。

这个图很重要,它对于排除和定位网络或系统故障时大有帮助,但是怎样牢牢地将这张图刻在脑中呢?一定要对这张图的每一个状态,及转换的过程有深刻的认识,不能只停留在一知半解之中.

半关闭

当TCP连接中A发送FIN请求关闭,B端回应ACK后(A端进入FIN_WAIT_2状态),B没有立即发送FIN给A时,A方处在半关闭状态,此时A可以接收B发送的数据,但是A已不能再向B发送数据。

从程序的角度,可以使用API来控制实现半连接状态:

#include <sys/socket.h>

int shutdown(int sockfd, int how);

sockfd: 需要关闭的socket的描述符

how:    允许为shutdown操作选择以下几种方式:

    SHUT_RD(0):    关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。

    SHUT_WR(1):        关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。

    SHUT_RDWR(2):    关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。

为什么不用close?因为close一次只中止一个连接,它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0时才关闭连接。

shutdown不考虑描述符的引用计数,直接关闭所有的描述符。也可选择中止一个方向的连接,只中止读或只中止写。

注意:

  1. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
  2. 在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其它进程。

2MSL

2MSL (Maximum Segment Lifetime) TIME_WAIT状态的存在有两个理由:

(1)让4次握手关闭流程更加可靠;4次握手的最后一个ACK是是由主动关闭方发送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次发送出去。

(2)防止lost duplicate对后续新建正常链接的传输造成破坏。lost uplicate在实际的网络中非常常见,经常是由于路由器产生故障,路径无法收敛,导致一个packet在路由器A,B,C之间做类似死循环的跳转。IP头部有个TTL,限制了一个包在网络中的最大跳数,因此这个包有两种命运,要么最后TTL变为0,在网络中消失;要么TTL在变为0之前路由器路径收敛,它凭借剩余的TTL跳数终于到达目的地。但非常可惜的是TCP通过超时重传机制在早些时候发送了一个跟它一模一样的包,并先于它达到了目的地,因此它的命运也就注定被TCP协议栈抛弃。

另外一个概念叫做incarnation connection,指跟上次的socket pair一摸一样的新连接,叫做incarnation of previous connection。lost uplicate加上incarnation connection,则会对我们的传输造成致命的错误。

TCP是流式的,所有包到达的顺序是不一致的,依靠序列号由TCP协议栈做顺序的拼接;假设一个incarnation connection这时收到的seq=1000, 来了一个lost duplicate为seq=1000,len=1000, 则TCP认为这个lost duplicate合法,并存放入了receive buffer,导致传输出现错误。通过一个2MSL TIME_WAIT状态,确保所有的lost duplicate都会消失掉,避免对新连接造成错误。

该状态为什么设计在主动关闭这一方

(1)发最后ACK的是主动关闭一方。

(2)只要有一方保持TIME_WAIT状态,就能起到避免incarnation connection在2MSL内的重新建立,不需要两方都有。

如何正确对待2MSL TIME_WAIT?

RFC要求socket pair在处于TIME_WAIT时,不能再起一个incarnation connection。但绝大部分TCP实现,强加了更为严格的限制。在2MSL等待期间,socket中使用的本地端口在默认情况下不能再被使用。

若A 10.234.5.5 : 1234和B 10.55.55.60 : 6666建立了连接,A主动关闭,那么在A端只要port为1234,无论对方的port和ip是什么,都不允许再起服务。这甚至比RFC限制更为严格,RFC仅仅是要求socket pair不一致,而实现当中只要这个port处于TIME_WAIT,就不允许起连接。这个限制对主动打开方来说是无所谓的,因为一般用的是临时端口;但对于被动打开方,一般是server,就悲剧了,因为server一般是熟知端口。比如http,一般端口是80,不可能允许这个服务在2MSL内不能起来。

解决方案是给服务器的socket设置SO_REUSEADDR选项,这样的话就算熟知端口处于TIME_WAIT状态,在这个端口上依旧可以将服务启动。当然,虽然有了SO_REUSEADDR选项,但sockt pair这个限制依旧存在。比如上面的例子,A通过SO_REUSEADDR选项依旧在1234端口上起了监听,但这时我们若是从B通过6666端口去连它,TCP协议会告诉我们连接失败,原因为Address already in use.

RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。

RFC (Request For Comments),是一系列以编号排定的文件。收集了有关因特网相关资讯,以及UNIX和因特网社群的软件文件。

 

 

这里我要解决自己的一个疑问,主动关闭一方半关闭状态之后还是能发送数据:半关闭状态是指主动关闭一方接收到了对方的ACK但是还没有接收到对方的FIN,这个时间段才叫半关闭,这个时间段主动方的确不能发送数据给对方.这是对的.但是当本方接收到了对方的FIN之后,本方又可以发送数据了.在这个时间段中不是半关闭状态了.为什么?因为本方要回复对方啊,要发送ACK给对方回复.所以我猜测之前的半关闭并不是真的不能发送数据了,而是被协议通过软件手段屏蔽掉了.毕竟要传输数据socket就要打开啊.半关闭是不能发送数据,但是接收到了FIN之后就不是半关闭了,所以就可以发送数据了.

端口复用

做一个测试,首先启动server,然后启动client,用Ctrl-C终止server,马上再运行server,运行结果:

itcast$ ./server

bind error: Address already in use

这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口。我们用netstat命令查看一下:

itcast$ netstat -apn |grep 6666

tcp 1 0 192.168.1.11:38103 192.168.1.11:6666 CLOSE_WAIT 3525/client

tcp 0 0 192.168.1.11:6666 192.168.1.11:38103 FIN_WAIT2 -

server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态。

现在用Ctrl-C把client也终止掉,再观察现象:

itcast$ netstat -apn |grep 6666

tcp 0 0 192.168.1.11:6666 192.168.1.11:38104 TIME_WAIT -

itcast$ ./server

bind error: Address already in use

client终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。

MSL在RFC 1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后就可以再次启动server了。至于为什么要规定TIME_WAIT的时间,可参考UNP 2.7节.

在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为,TCP连接没有完全断开指的是connfd(127.0.0.1:6666)没有完全断开,而我们重新监听的是lis-tenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。

在server代码的socket()和bind()调用之间插入如下代码:

    int opt = 1;

    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

就可以解决问题了.

有关setsockopt可以设置的其它选项请参考UNP第7章。

Linux:TCP状态/半关闭/2MSL/端口复用的更多相关文章

  1. 深入浅出TCP之半关闭与CLOSE_WAIT

    转自:https://www.2cto.com/net/201309/243585.html(相关链接) 深入浅出TCP之半关闭与CLOSE_WAIT 终止一个连接要经过4次握手.这由TCP的半关闭( ...

  2. TCP之半关闭与CLOSE_WAIT

    终止一个连接要经过4次握手.这由TCP的半关闭(half-close)造成的.既然一个TCP连接是全双工(即数据在两个方向上能同时传递,可理解为两个方向相反的独立通道),因此每个方向必须单独地进行关闭 ...

  3. TCP/IP编程——基于TCP的半关闭

    在TCP服务端和客户端建立连接之后服务端和客户端会分别有两个独立的输入流和输出流,而且相互对应.服务端的输出流对应于客户端的输入流,服务端的输入流对应于客户端的输出流.这是在建立连接之后的状态. 当我 ...

  4. 理解tcp顺序释放操作和tcp的半关闭

    Shutdown的调用        在关闭socket的时候,可以有两种方式closesocket和shutdown,这两个函数的区别在什么地方呢? #include <sys/socket. ...

  5. linux系统下怎么关闭一个端口

    netstat -an | grep 22查看22端口 netstat -ntulp |grep 80   //查看所有80端口使用情况

  6. linux tcp状态学习

    参考: http://huoding.com/2013/12/31/316 http://www.cnblogs.com/sunxucool/p/3449068.html http://maoyida ...

  7. 我为 Netty 贡献源码 | 且看 Netty 如何应对 TCP 连接的正常关闭,异常关闭,半关闭场景

    欢迎关注公众号:bin的技术小屋,本文图片加载不出来的话可查看公众号原文 本系列Netty源码解析文章基于 4.1.56.Final版本 写在前面..... 本文是笔者肉眼盯 Bug 系列的第三弹,前 ...

  8. TCP状态转换图解析

    本文参考Unix网络编程卷1,对TCP状态转换进行总结,方便掌握TCP链接中各个状态及故障分析. 1.Linux下TCP相关工具 基于Linux系统查看网络状态,首先了解几个基本查看指令. Linux ...

  9. TCP 状态详解 -转载

    TCP 是一个面向连接的协议,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接.本节将详细讨论一个TCP 连接是如何建立的以及通信结束后是如何终止的. 建立一个 TCP 连接 TCP使用 ...

随机推荐

  1. shell命令输出

    在shell脚本中的打印输出通常会有echo和printf两种,前者会自动换行. 一.echo Shell 的 echo 指令与 PHP 的 echo 指令类似,都是用于字符串的输出.您可以使用ech ...

  2. centos7部署openvpn-2.4.6

    一.环境说明 返回主机的IP地址 # ip a | grep "scope global" | awk -F'[ /]+' '{print $3}' | head -1 [root ...

  3. [UE4]GameUserSettings

  4. plsql怎么执行sql脚本

    首先,我们需要登录需要执行sql文件的用户,在我们确保sql文件无误的情况下,进入plsqldeveloper: 1,找到tools--->import tables --->选择sql ...

  5. MySQL 之 MySQL数据库的优化

    服务器物理硬件的优化 在挑选硬件服务器时,我们应该从下面几个方面着重对MySQL服务器的硬件配置进行优化,也就是说将项目中的资金着重投入到如下几处: 1.磁盘寻道能力(磁盘I/O),我们现在用的都是S ...

  6. L1正则化

    正则化项本质上是一种先验信息,整个最优化问题从贝叶斯观点来看是一种贝叶斯最大后验估计,其中正则化项对应后验估计中的先验信息,损失函数对应后验估计中的似然函数,两者的乘积即对应贝叶斯最大后验估计的形式, ...

  7. Vue百度搜索

    <!DOCTYPE html><html lang="en"><head> <meta charset="GBK"&g ...

  8. C# 线程 在 sleep,suspend 之后 Abort 的方法

    1) 线程在sleep时的Abort     方法:对线程函数用 catch ThreadAbortException ,并return.   示例: [csharp] view plaincopy ...

  9. Linux下用node-inspector实现NodeJS远程调试开发

    1.首先安装 node-inspector npm install -g node-inspector -g表示全局安装,如果像我一样安装失败,再试几次,npm偶尔就会这样抽风... 这一步是关键的, ...

  10. C语言:传值,传地址

    形参:形式参数实参:实际参数 传值: 把实参的值复制给形参, 修改函数内的形参,不会影响实参. 传地址: 指针传值,形参为指向实参地址的指针 当对形参的指向操作时,相当于对实参本身进行的操作 #inc ...