上次提到tcp数据流无边界特点 还有一个特点那就是
TCP协议中有长连接和短连接之分
目录结构:
1 socket 正常关闭会发生什么
2 socket 异常关闭会发生什么
存在2个疑问
3 无论是socket正常关闭和异常关闭 socket捕获不到
4 几种心跳包处理方案
tcp建立的终止
这里讨论的是 于一方首先关闭连接的情况
socke正常关闭
流程:
- 被动关闭一方接受完毕数据 然后发送 TCP flag Fin请求
- 主动关闭一方 tcp状态 进入TIME-WAIT
- 主动关闭一方 在在此期间内 该端口不能被任何程序重用 不能建立任何连接。这个期间称为平静时间(quiet time)
Q1 问题来了 TIME-WAIT状态持续大约2分钟内 俗称 平静时间(quiet time) ,存在意义是什么
根据tcp状态含义解释
TIME-WAIT:等待足够的时 ,确保远程TCP收到了终止请求的确认
远程TCP收ack确认这个条件 直接 CLOSE-WAIT状态进入CLOSED状态 最后一步会成功
但是我还是不明白存在意义 最底层接受太官方 不容易理解 有更加合理接受吗麻烦告知 假设网络是好的
我给出答案都被自己排除了
内核为了兼容业务 正常这从业务角度来考虑 在高并发情况下接受为啥对方不能发送Fin请求和接受ack请求
(1)在读取数据之后 业务处于死锁 耗时操作 未能发送closed请求 这个解释不合理 因为讨论是 对方发送clonsed请求 进入time_wait状态
(2)对比TCP连接队列溢出listen 函数 int backlog 全连接队列的大小 默认backlog 值是50
如果同时超过50连接 无法完成三次握手 舍去 但是查到关闭操作是否有关闭队列? 这个解释不很合理
sokcet 异常关闭
- 一方: 立刻发送reset标记,即使缓冲区里有带发送的数据
- 另外一方:此时连接马上断开 中途释放一个连
13:44:40.870817 IP 10.69.61.134.55372 > VM-10-112-178-190.1024: Flags [R.], seq 674, ack 6854, win 0, length 0
- 这个方式叫做 异常终止一个连接 异常释放(abortive release)
优点是什么
异常终止一个连接对应用程序来说有两个优点:
(1)丢弃任何待发数据并立即发送复位报文段;
(2) R S T的接收方会区分另一端执行的是异常关闭还是正常关闭。
应用程序使用的A P I必须提供产生异常关闭而不是正常关闭的手段
快速结束一个连接
Q2 问题来了 如何减少TIME_WAIT时间
通过修改socket选项之SO_LINGER 发送reset标记 打破四次握手,
避免TIME_WAIT状态
讨论一下异常情况下是否捕获正常或者异常终止连接
客户端主机四种:
- 客户端崩溃 异常关闭 server收不到ACK
- 客户端曾经崩溃,但已经重启 响应是一个复位reset
- 客户端主机活跃运行,但从服务器不可到达 T C P连接的双方都没有向对方发送数据
- 服务器主机突然断电 T C P连接的双方都没有向对方发送数据
- 服务器主机网线被拔出 T C P连接的双方都没有向对方发送数据
- 服务器主机正常重启当 系统被操作员关闭时,所有的应用程序进程(也就是客户端进程)都将被终止,客户端TCP会在连接上发送一个FIN。
心跳检查几种方案
KeepAlive通过定时发送探测包来探测连接的对端是否存活
其实keepalive有两种,一种是TCP层的keepalive,另一种是HTTP层的Keep-Alive。
这里说的ttcp层的keepalive
也就是TCP的选项:SO_KEEPALIVE。
int keepAlive = 1;//设定KeepAlive
int keepIdle = 5;//开始首次KeepAlive探测前的TCP空闭时间
int keepInterval = 5;//两次KeepAlive探测间的时间间隔
int keepCount = 3;//判定断开前的KeepAlive探测次数
缺点:
1 有时候检查不到 断电、直接拔掉网线、防火墙这些断线 (呜呜呜)
/* keepalive并不是TCP规范的一部分。在Host Requirements RFC罗列有不使用它的三个理由:
* 但自己的keepalive有这样的一个bug:
* 正常情况下,连接的另一端主动调用colse关闭连接,tcp会通知,我们知道了该连接已经关
* 闭。但是如果tcp连接的另一端突然掉线,或者重启断电,这个时候我们并不知道网络已经关闭。
* 而此时,如果有发送数据失败,tcp会自动进行重传。重传包的优先级高于keepalive,那就意
* 味着,我们的keepalive总是不能发送出去。 而此时,我们也并不知道该连接已经出错而中断。
* 在较长时间的重传失败之后,我们才会知道。即我们在重传超时后才知道连接失败.×
2 不直接通知异常而是 通过其他方式通知业务层连接出现异常
c++:
在程序中表现为,当tcp检测到对端socket不再可用时(不能发出探测包,或探测包没有收到ACK的
* 响应包),select会返回socket可读,并且在recv时返回-1,同时置上errno为ETIMEDOUT.
启动定时器来检查
心跳包一般来说都是在逻辑层发送空的echo包来实现的。
下一个定时器,在一定时间间隔下发送一个空包给客户端,然后客户端反馈一个同样的空包回来,服务器如果在一定时间内收不到客户端发送过来的反馈包,那就只有认定说掉线
缺点:
- 对网络闪断情况处理不好
func (pthis *SocketClient) RecHeartBeating() {
log.Println("RecHeartBeating begin ..")
var ticker *time.Timer
ticker = time.NewTimer(time.Second * 10)
defer ticker.Stop()
for {
select {
case _ = <-pthis.heartChannel:
log.Println(pthis.conn.RemoteAddr().String(), "get message, keeping heartbeating...")
//conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
ticker.Reset(time.Second * 10)
break
case <-ticker.C:
log.Println(" RecHeartBeating timeout!!!")
pthis.userLeaveOp()
pthis.conn.Close()
}
}
log.Println("RecHeartBeating end ..")
}
// 心跳计时,判断Client是否在设定时间内发来信息
func (pthis *SocketClient) SendHeartBeating() {
log.Println("SendHeartBeating begin ..")
for {
select {
case <-time.After(time.Second * 5):
log.Println(" SendHeartBeating!!!")
data := "HeartBeating"
pthis.send(pthis.conn, heartReq, data)
}
}
log.Println("SendHeartBeating end ..")
}
3 循环处理+sleep方式()
本章节内容:
心跳包实现考虑问题
1 是否能及时发现异常
2 明确通知业务层出现异常
下章预告:
TCP的交互数据流
Nagle算法
TCP的超时与重传
拥塞避免算法
TCP的成块数据流
滑动窗口
计划:
参考:
- 用TCP/IP详解,卷1:协议
- rfc793(tcp协议)