在一段时间内,如果TCP连接两方都没有数据交互,TCP的保活机制**(TCP keepalive)**会起作用,每隔一个时间段会发送一个探测报文,如果连着好几个探测报文都没有得到相应,则会认为当前TCP连接已经GG,系统内核会将错误信息通知给上层应用程序。
在没有开启TCP keepalive的情况下
客户端主机宕机,又立即重启
客户端宕机,没有重启
服务端会发送发送的探测报文超时次数达到一定阈值,就会认为该TCP连接有问题。
就是当主动关闭方收到FIN报文时是怎样处理的。根据源码分析,在FIN_WAIT2状态时会检查FIN报文的序列号,如果前面还有报文还未到达,所以此FIN是乱序的,因此会加入乱序队列。等到有新的报文到达时,会判断乱序报文队列里面有无可用数据,会在乱序队列中找与新到报文顺序的报文,查看此报文是否是FIN报文,如果是,则会进入TIME_WAIT状态。
在三次握手时初始的序列号是随机的,所以一般情况下,客户端重新发送的SYN请求中的初始化序列号不是当前服务端期望收到序列号,服务端会回复RST。
伪造一个相同的四元组,发送的的RST报文序列号落在对端可接收的滑动窗口序列范围内。
伪造相同的四元组容易,如何获取到对端期望的下一个报文序列号呢?
根据问题四,可以先给对端发送一个SYN请求,服务端会回复challenge ACK,这个ACK的确认号,便是服务端期望接收的下一个报文序列号,然后使用此序列号作为发送RST的序列号,服务端收到后会释放连接。
在 Linux 上有个叫 killcx 的工具,就是基于上面这样的方式实现的,它会主动发送 SYN 包获取 SEQ/ACK 号,然后利用 SEQ/ACK 号伪造两个 RST 报文分别发给客户端和服务端,这样双方的 TCP 连接都会被释放,这种方式活跃和非活跃的 TCP 连接都可以杀掉。
在tcp连接中有两个参数:
要使得这两个选项生效,有一个前提条件,就是要打开 TCP 时间戳,即 net.ipv4.tcp_timestamps=1(默认即为 1)。
**PAWS机制:**收发两方维护最近一次收到数据的时间戳,要求收到的数据包时间戳是递增的,如果不是递增,说明此数据包过期。
当开启net,ipv4.tcp_tw_recycle和时间戳时会开启一种叫per-host的PWAS机制:
对IP做PAWS检查,不是对IP+端口四元组做PAWS检查。因此客户端环境使用了NAT网关,客户端所有机器对外的IP地址都是相同,就会导致客户端A和客户端B的连接A比B的时间戳大,便会丢弃B的SYN包。
如果处于 TIME_WAIT 状态的连接收到「合法的 SYN 」后,就会重用此四元组连接,跳过 2MSL 而转变为 SYN_RECV 状态,接着就能进行建立连接过程。
如果处于 TIME_WAIT 状态的连接收到「非法的 SYN 」后,就会再回复一个第四次挥手的 ACK 报文,客户端收到后,发现并不是自己期望收到确认号(ack num),就回 RST 报文给服务端。
基于UDP实现的可靠传输QUIC协议。
要给予UDP实现可靠传输就要在应用层上下功夫。首先在建立连接阶段确定连接ID,使用此ID可以实现迁移功能。在传输数据时Packet Number每个报文是独一无二的,严格递增,传输中有数据包丢失也不影响后续数据包的传输,从而解决了TCP接收窗口队头阻塞问题,后续重传丢失的数据包的Packet Number值也是一个比之前丢失的包id值大。TCP重传丢失报文的序列号是一样的会导致计算RTT时间不太准确。
所以,Packet Number 单调递增的两个好处:
重传的数据包通过Offset保证数据的顺序性和可靠性。
可以的。
多个TCP服务进程可以绑定同一个端口号吗?
如果两个 TCP 服务进程同时绑定的 IP 地址和端口都相同,那么执行 bind() 时候就会出错,错误是“Address already in use”。
客户端的端口号可以重复使用吗?
TCP 连接是由四元组(源IP地址,源端口,目的IP地址,目的端口)唯一确认的,那么只要四元组中其中一个元素发生了变化,那么就表示不同的 TCP 连接的。所以如果客户端已使用端口 64992 与服务端 A 建立了连接,那么客户端要与服务端 B 建立连接,还是可以使用端口 64992 的,因为内核是通过四元祖信息来定位一个 TCP 连接的,并不会因为客户端的端口号相同,而导致连接冲突的问题。
多个客户端可以 bind 同一个端口吗?
要看多个客户端绑定的 IP + PORT 是否都相同,如果都是相同的,那么在执行 bind() 时候就会出错,错误是“Address already in use”。
如果一个绑定在 192.168.1.100:6666,一个绑定在 192.168.1.200:6666,因为 IP 不相同,所以执行 bind() 的时候,能正常绑定。
所以, 如果多个客户端同时绑定的 IP 地址和端口都是相同的,那么执行 bind() 时候就会出错,错误是“Address already in use”。
一般而言,客户端不建议使用 bind 函数,应该交由 connect 函数来选择端口会比较好,因为客户端的端口通常都没什么意义。
服务端如果只 bind 了 IP 地址和端口,而没有调用 listen 的话,然后客户端对服务端发起了连接建立,服务端会回 RST 报文。
参照源码TCP报文入口函数tcp_v4_rcv():
1)调用__inet_lookup_skb函数查找此报文的的socket;
int tcp_v4_rcv(struct sk_buff *skb)
{
...
sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
if (!sk)
goto no_tcp_socket;
...
}
2)__inet_lookup_skb函数首先查找连接建立状态socket,找不到的话查找监听套接字接口;
3)找不到监听改端口的socket,会RST;
可以。
延迟确认机制。
最长/最短延时确认时间,开启/关闭TCP快速确认参数TCP_QUICKACK;
// 1 表示开启 TCP_QUICKACK,即关闭 TCP 延迟确认机制
int value = 1;
setsockopt(socketfd, IPPROTO_TCP, TCP_QUICKACK, (char*)& value, sizeof(int));
会回复RST。
三次握手是为了避免旧的历史连接被初始化。
就是这事,散会!