上篇我们介绍了三次握手四次挥手,这次继续来进行tcp的介绍。
1 TINE_WAIT与CLOSE_WAIT
我们先使用客户端作为左端,服务端作为右方
当我们客户端close时,就会发起两次挥手,此时服务端就会进入CLOSE_WAIT状态,只要服务端不进行close那么就会一直处于CLSOE_WAIT状态,因此我们也就可以观察到现象了(使用netstat -talp即可)
所以我们可以进行telnet+服务端进行本地测试
注意:
若是服务器出现了卡顿就要观察是否存在大量的CLOSE_WAIT状态导致的
随后我们在进行ctrl+c(OS会自动释放尚未关闭的fd,进而发起两次挥手)即可看到TIME_WAIT。
但是我们也会发现为什么TIME_WAIT为什么持续了很久?不会进入CLOSED状态?
这是因为TIME_WAIT一般会持续2*MSL时间,这个我们稍后说
而我们的bind也恰好在TIME_WAIT结束后可以进行bind,这是因为端口号还在一直被占用。
做完这个实验我们可以明显的感受到四次挥手是明显滞后于进程的结束的,OS要继续维护知道连接彻底关闭。
2*msl
那么为什么要持续2*MSL时间?
MSL是报文最大生存时长。
保证已经重传过得数据因为某些原因还在网络中没有被收到,等待报文消散,所以当报文被收到时要进行丢掉。
因为MSL >> 超时重传的时间,所以不用害怕是未被接受到的报文。
那么为什么要直接丢弃呢?
因为当你的报文重新连接,旧的报文可能就被送到服务器,老旧数据干扰了正常服务。
同时也是在理论上保证最后一个报文可靠到达(假设最后一个 ACK 丢失, 那么服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然可以重发 LAST_ACK)
那么我们如何解决这种情况?
比如双十一时我们肯定不能再等待
使用 setsockopt()设置 socket 描述符的 选项 SO_REUSEADDR 为 1, 表示允许创建端口号相同但 IP 地址不同的多个 socket 描述符
2 流量控制
我们观察下图,发现若是对方接受缓冲区满了,那我们发送的报文只能丢弃了,可是这并不合理,即便可以超时重传。
因为传输的过程中浪费了大量的资源CPU资源,路由器…
所以此时我们就需要慢一些发送数据,根据对方的接受能力去决定我们的发送速度,而所谓的接受能力就是对方接收缓冲区的剩余大小。
那我们怎么知道对方的接受能力?
就需要用到报文中16位接受窗口大小了!
细节:
- 是双方互发消息,窗口写的是自己的剩余接受缓冲区大小,也就是流量控制是双方的
- 控制不单单可以快,还可以慢
- 第一次发消息时怎么发?发一个很小的报文?实际上在三次握手时就已经交换双方报文,进而得知对方的接受能力了。
- 窗口大小为16位岂不是意味着窗口大小是有限制的?确实如此,但是我们可以进行使用报文中的
选项
设置窗口大小 - 当B窗口为0时,若是B有了空间,B会发送报文通知A,同时A也会有窗口探测报文进行探测
若是一直为0怎么办?
就需要PSH(PUSH标志位置1),催促OS快点处理(需要多=多路转接的知识点,暂时不管)。
但即使是非0时也可以加入PSH标志位,比如我们使用Xshell时每次输入指令可能都使用了PSH选项。
这里也就顺便说一下URG标志位(URGENT),当为0时16位紧急指针无意义,否则反之。
16位紧急指针的意义是标识那一部分是紧急数据,是一个偏移量。
但是他只能标记一个位置。
这里使用一个例子进行解释:当我们在百度网盘下载东西时,下到一半想暂停,于是就点击暂停,此时报文中就包含一个紧急指针,其中这个被标识字节我们可以自己规定含义。
tcp按需到达,前边还有很多数据,所以此时会优先读取紧急指针内容并进行响应操作,避免了资源的浪费。
但是我们很少用,完全可以在关联一个端口,一个用来通信,一个用来控制,保证了更好的独立性与拓展性。
3 滑动窗口
首先滑动窗口与超时重传和流量控制有关。
也就是如上的两个话题。
持续更新