cdn线网运营总会遇到有各种各样的奇怪问题,而导致这些问题却对应的各种各样的原因。有些原因查出来却总叫人哭笑不得,比如本案例所说的,服务端的程序挂了导致的connection reset by peer问题。这种最基础的原因却往往最能怀疑到,我个人觉得主要原因有以下两点。
一,定式思维总会让人最先排出掉最接近真相到原因。有点类似高中做题,总容易先入为主,陷入死胡同。
二,没有人对各种原因做归纳总结,不同的原因导致的虽然都是连接失败这一现象,但如果深入研究,一定有独特的特征。需要有人说出“白马”和“黑马”的各自独特性。网上搜索了下connection reset by peer关键字,发现只有应用层原因说明各种原因,并没有这种问题的tcp层的原因介绍。那就我来根据线网运营遇到的案例来分析具体tcp层发生来什么,各种connection reset by peer的原因在tcp层有啥不同的特征来区分。
废话不多扯,直接开始说事。下图是某下载业务在第三方竞速平台的错误点,错误类型是建立连接失败。幸好第三方测试平台自带错误点抓包功能,保留了错误发生的现场。
从图中可以看到,客户端没发送一个syn包都会回复一个reset包。很直观的感觉三次握手阶段,连接还没建立肯定还没有到应用层,必然跟应用层没有关系。并且如果应用层有问题,必然会导致大面积的连接失败,而第三方测试平台显示只是两个错误点。而我首先怀疑的是前一条连接的time_wait一直存在,导致的新链接被reset,因为我们从入门就有意无意的被灌输“前一条流会影响后一条流的建联,尤其是time_wait状态的连接总是充满着各种神秘”。
首先tcp层发送的reset是分为两种——active_reset和非active_reset, active_reset是调用tcp_send_active_reset()进行发送的,而非active_reset是调用tcp_v4_send_reset()进行发送。那从抓包来看怎么确认是哪种类型的reset呢? 关键是要看Win值是否有设置, tcp_send_active_reset()调用的是tcp_transmit_skb()进行发送的,在tcp_transmit_skb()会计算和设置接收窗口。而tcp_v4_send_reset()函数设置的Win是零。
void tcp_send_active_reset(struct sock *sk, gfp_t priority)
{
struct sk_buff *skb;
/* NOTE: No TCP options attached and we never retransmit this. */
skb = alloc_skb(MAX_TCP_HEADER, priority);
if (!skb) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPABORTFAILED);
return;
}
/* Reserve space for headers and prepare contro