TCP三次握手/四次挥手,TCP连接异常中断后,服务器的Socket应什么时候关闭
一、TCP三次握手/四次挥手
1. 三次握手
第一次: 主机A 发送位码为syn=1,随机产生seq number=1234567 的数据包到服务器,
主机B由SYN=1 知道,A 要求建立联机;
第二次: 主机B 收到请求后要确认联机信息, 向A 发送
ack number=( 主机A 的seq+1),syn=1,ack=1,随机产生seq=7654321 的包
第三次:主机A 收到后检查ack number 是否正确,即第一次发送的seq number+1,以及位码 ack 是否为1,
若正确,主机A 会再发送ack number=(主机B 的seq+1),ack=1,主机B 收到后确认seq 值与ack=1 则连接建立成功。
2. 四次挥手
TCP 建立连接要进行三次握手,而断开连接要进行四次。这是由于TCP 的半关闭造成的。因为TCP 连
接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭。这个单
方向的关闭就叫半关闭。当一方完成它的数据发送任务,就发送一个FIN 来向另一方通告将要终止这个
方向的连接。
1) 关闭客户端到服务器的连接:首先客户端A 发送一个FIN,用来关闭客户到服务器的数据传送,
然后等待服务器的确认。其中终止标志位FIN=1,***seq=u
2) 服务器收到这个FIN,它发回一个ACK,确认号ack 为收到的序号加1。
3) 关闭服务器到客户端的连接:也是发送一个FIN 给客户端。
4) 客户段收到FIN 后,并发回一个ACK 报文确认,并将确认序号seq 设置为收到序号加1。
首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
主机A 发送FIN 后,进入终止等待状态, 服务器B 收到主机A 连接释放报文段后,就立即
给主机A 发送确认,然后服务器B 就进入close-wait 状态,此时TCP 服务器进程就通知高
层应用进程,因而从A 到B 的连接就释放了。此时是“半关闭”状态。即A 不可以发送给
B,但是B 可以发送给A。此时,若B 没有数据报要发送给A 了,其应用进程就通知TCP 释
放连接,然后发送给A 连接释放报文段,并等待确认。A 发送确认后,进入time-wait,注
意,此时TCP 连接还没有释放掉,然后经过时间等待计时器设置的2MSL 后,A 才进入到
close 状态。
3.为什么会挥手次数比握手次数多?
这是由于TCP 的半关闭造成的。
因为TCP 连接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭。这个单方向的关闭就叫半关闭。
当一方完成它的数据发送任务,就发送一个FIN 来向另一方通告将要终止这个方向的连接。
二、TCP连接异常中断后,服务器的Socket应什么时候关闭
1. TCP/IP可选特性Keepalive(默认关闭)
Keepalive特性需要在 setsockopt 里面把 SO_KEEPALIVE 标志设置为1,才会打开。
有3个参数,以linux为例,搜索keepalive相关的系统参数(sysctl -A |grep keepalive),通常会得到如下3个参数:
net.ipv4.tcp_keepalive_intvl = 75 -- 间隔多少秒发一次嗅探包
net.ipv4.tcp_keepalive_probes = 9 -- 嗅探包一共发几次
net.ipv4.tcp_keepalive_time = 7200 -- TCP连接空闲多少秒后发嗅探包
3个参数组合起来表示:
如果TCP连接2小时(7200秒)内没有数据传输,则发送嗅探包,每隔75秒发送一次,共重试9次。
9次对方都没响应,则表明此连接已死。
一般服务器程序不会用到这个机制,而是把Keepalive放到业务层,自己实现Keepalive机制。这样做的好处是可以减少程序对特定协议的依赖,且发送给客户端的请求可以组合起来,在心跳包到来的时候一并返回给客户端。还有,开启Keepalive特性还有有一些其他方面的风险。
比如:
(1) cause perfectly good connections to break during transient Internet failures;
(2)consume unnecessary bandwidth ("if no one is using the connection, who cares if it is still good?");
and (3) cost money for an Internet path that charges for packets. (RFC1122)
因此,如果没打开TCP/IP的Keepalive特性,业务层又没有自己实现Keepalive,且未调用send/recv,那么连接会一直存在。
2. 不依赖 Keepalive 如何关闭
正常情况下,如果一端关闭了socket,那么另一端会受到FIN包,因此是可以知道对端关闭的。
异常情况下(如拔网线,用iptables封了端口),一端是不能收到对端的关闭通知的。可以通过send和recv检测,
以linux为例:
recv的man对返回值的介绍如下:
RETURN VALUE
These calls return the number of bytes received, or -1 if an error occurred. The return value will be 0 when the peer
has performed an orderly shutdown.
即,recv返回0表示对端关闭。对于send,如果返回值为-1,且errno为ECONNRESET,表示对端关闭。
因此,只要你在业务层有心跳机制,且正确处理了send/recv,那么对端断开你一定能得到通知。