TCP(2)再探TCP
现在来看学的真有点浅了,所以今天再来看看TCP。当然不排除以后还会写TCP/UDP(3),TCP/UDP(4),还有以前很想学的QUIC。
还是三次握手和四次挥手
MTU和MSS
- MTU:一个网络包的最大长度。
- MSS:出去IP和TCP头部之后,一个网络包容纳的TCP数据的最大长度。
三次握手
上图展示三次握手每一次握手会给出的信息。我们可以看到TCP还会给出窗口大小、报文长度、MSS等重要信息。
需要第三次握手最主要的原因是解决历史连接问题,这一点不再详细说明。
四次挥手
关闭的过程分为客户端关闭和服务端关闭两部分。
- 客户端发送FIN到服务端,客户端进入Fin_Wait_1状态
- 服务端发送ACK到客户端,服务端进入Closed_Wait状态
- 客户端收到服务端的 ACK 应答报文后,进入 Fin_Wait_2 状态。
现在客户端关闭结束,服务端可能还有信息没传 - 服务端客户端发送 FIN 报文,服务端进入 Last_Ack 状态。
- 客户端回复ACK 应答报文,之后进入 Time_Wait状态
- 服务器收到了 ACK 应答报文后,进入了 Closed 状态,服务端完成连接的关闭。
- 客户端在经过 2MSL 一段时间后,自动进入 Closed 状态,至此客户端也完成连接的关闭。
连接关闭的发起不一定是客户端也可以是服务端。这里默认客户端发起连接关闭。
为什么有Time_wait状态?
- 避免这次连接数据影响下一次连接。自己上一次发送的数据包可能还残留在网络中,等待2MSL时间可以保证所有残留的网络报在自己关闭前都已经超时。
- 保证TCP连接的正确关闭。客户端发出ACK可能会失败,服务器收不到ACK就会再发FIN报文,客户端需要等一会确定这件事。
但Time_Wait是必须的吗?不是的。这一系列状态的设计,是因为TCP是可靠的协议,针对第二个问题,ACK报文是否传达真的重要吗?我的数据都已经传完了啊。所以TCP有优雅关闭和非优雅关闭两种。
为什么Time_wait是2MSL?
MSL 报文最大生存时间。MSL的大小和TTL有关。而TTL指的是IP数据报可以经过的最大路由器数。MSL的大小是略大于TTL消耗时间的。我倾向于把MSL理解为TTL在时间上的表达。
而2MSL是因为一个来回需要两次。针对第一个问题,2MSL这一段时间足以让所有停留在网络中的数据报走一个来回,保证所有的数据自然消失。针对第二个问题,2MSL可以让ACK报文抵达,然后等待服务端FIN报文,如果没有收到就再发一个ACK。
TIME_WAIT 过多
这是我的服务器。我用webbench在15秒之类发起了10000次请求。
在最后时刻有4063个TIME_WAIT状态,他们已经没有用了。但他们却占据大量资源。这显然是不好的。
出现了问题我们就需要解决。还记得我们之前说过TCP的关闭分为优雅关闭和非优雅关闭吗?
在网络编程中我们可以改变setsockopt的SO_LINGER来设置close的关闭方式。
struct linger{
int l_onoff;
int l_linger;
};
一共三种情况:
l_onoff | l_linger | 行为 |
---|---|---|
0 | 忽略 | 选项关闭,内核缺省情况,是优雅关闭 |
非0 | 0 | 立即关闭连接,通过发送RST报文,而不是正常的FIN|ACK报文,不管缓冲区中未发送的数据,直接跳过TIME_WAIT,这是强制关闭 |
非0 | 非0 | 设置一个超时,如果socket发送缓冲区中有残留数据,进程睡眠,内核进入定时状态来处理数据。超时之前可以处理完所有数据,用FIN|ACK报文来关闭连接,否则使用RST报文关闭连接。 |