关于重连测试的一点研究

在最近的异常测试中,发现长连接协议的客户端存在较多的坑点,除了需要关注一般的网络错误、超时之外,长连接本身就具有无连接时创建连接,连接异常时重连这样的特性,是额外需要关注的地方。如果处理不好,往往会造成无限重连socket占满,或者是网络断开没有触发重连导致后续请求全都发不出去这样的大问题

然而我在做这类测试的时候也是一头雾水,尝试用iptables reject或者drop了连接,触发了几次报错或者重连是否就真的对这样的场景验证完全了呢?到底应该iptables掉的是客户端发送的包还是客户端接收的包,这2种姿势哪种才是对的呢?我陷入了深深的迷茫之中

周末埋头研究了一下,尝试理了一下思路,整理如下

从关闭连接说起

谈到重连,那就必须从关闭连接说起

TCP状态转移图大家都很熟悉,看到有关闭连接那部分, 如图所示关闭连接是由FIN发起的

简单的从wireshark抓包看建立连接->发请求->断开连接的过程也能看到FIN

然而除了正常的FIN的方式断开连接,还有一种RST的方式用来异常的关闭连接,比如telnet了一个本地一个不存在的端口返回了RST:

需要注意的是RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止该连接,并通知应用层连接复位。出现RST的一般都是异常情况:

  1. 网络问题:端口未打开,请求超时等
  2. 服务端问题:程序不接收或接收请求不完整,客户端向已经关闭的socket发数据等

这时候对于短连接来讲本次连接失败了抛异常连接关闭,但对于长连接来讲,需要客户端保持和服务器的连接以便能够继续发消息,往往需要触发重连的操作

重连本身没有太多好讲的,即关闭原有连接再重新连接,我们更关心的是对于FIN的情况和RST的情况,客户端能否关闭旧连接创建新连接,这构成了我们接下去的测试场景的设计

心跳检测

建立完连接,然后和服务器不断收发请求的过程中,客户端能够实时的感知到网络状态。但如果连接建立但长时间没有发送过请求,会一直保持着连接。这其中如果有一方异常掉线,另一端永远也不可能知道,那么客户端如何才能知道这个情况并进行重连呢,可以采用一种称为心跳检测的方式

有些客户端会使用自己的心跳检测方法,原理和TCP协议自带的类似,所以这里拿TCP自带的keepalive来模拟说明。当建立一个TCP连接时,发送端就会创建一些计时器,其中一些计时器就是处理keeplaive相关问题的。当keepalive的计时器计数到0时,发送端就会向对端发送一些不含数据的keepalive数据包并开启ACK标志。如果得到keepalive探测包的回复,就可以认为当前的TCP连接正常。如果响应失败的次数超过探测包设定的次数,则认为连接失败,关闭连接,

我们可以用cat命令查看到系统中TCP keeplaive相关配置项的值

#cat /proc/sys/net/ipv4/tcp_keepalive_time  7200  #cat /proc/sys/net/ipv4/tcp_keepalive_intvl  75  #cat /proc/sys/net/ipv4/tcp_keepalive_probes  9

含义分别为tcp_keepalive_time(开启keepalive的闲置时长)tcp_keepalive_intvl(keepalive探测包的发送间隔)和tcp_keepalive_probes (如果对方不予应答,探测包的发送次数)。修改配置文件, 对整个系统所有的socket有效.:

#echo 60 > /proc/sys/net/ipv4/tcp_keepalive_time  #echo 75 > /proc/sys/net/ipv4/tcp_keepalive_intvl  #echo 2 > /proc/sys/net/ipv4/tcp_keepalive_probes

写一个长连接的socket和client,诀窍就是用while以后不要退出,不要退出,不要退出
服务端

import timeHOST_LOCAL = ‘‘                 # Symbolic name meaning all available interfacesPORT_LOCAL = 50007              # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind((HOST_LOCAL, PORT_LOCAL))s.listen(1)conn, addr = s.accept()print ‘Connected by‘, addrwhile 1:    data = conn.recv(2048)    if not data: continue    conn.send("hello")s.close()

客户端

#!/usr/bin/env pythonimport socketimport timeHOST = ‘‘    # The remote hostPORT =  50007             # The same port as used by the servers = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((HOST, PORT))while True:    s.send(‘hello world\r\n‘.encode())    time.sleep(10)s.close()

当请求以10s的间隔发送时,由于小于超时检查时间窗,无需进行心跳检查

所以修改tcp_client.py设置发送请求间隔为120

 time.sleep (120)

服务端加上keepalive

s.setsockopt(socket.SOL_SOCKET,socket.SO_KEEPALIVE,1)

由于超时时间窗为60s,请求间隔为120s,则每次请求1min后会触发一次Keep-alive

现在加上DROP使客户端发往服务端的包被丢弃

# iptables -I OUTPUT -p tcp --dport 50007 -j DROP

在75s的2次探查失败后(第一列time 840->915->990),服务端向客户端发送了RST,然后查看了下两边连接都已经关闭

为什么DROP连接后,最后两边连接都能够正常释放?

  1. 服务器配置了心跳检测,检测到了连接不通,从而关闭了连接
  2. 只DROP了发往服务器端的包而没有DROP接受的包,从而客户端收到了服务端的RST请求也关闭了连接

如果把接受服务端的包DROP掉,客户端可能就无法觉察到连接的异常

  • DROP掉服务端发往客户端的包,在不断有请求发送的情况下,客户端不断重传请求,最终超时退出,返回socket.error: [Errno 110] Connection timed out
  • DROP掉服务端发往客户端的包,如果两边没有数据发送,, 就算服务端关闭连接了,客户端也没感知到网络断了,如下面那所示,服务器因为心跳检测关闭已关闭连接,而客户端还没有,连接是半打开(Half-Open)的了
# netstat -lan|grep 50007 tcp        0      0 0.0.0.0:50007           0.0.0.0:*               LISTEN     tcp        0      0 127.0.0.1:50007         127.0.0.1:55321         ESTABLISHEDtcp        5      0 127.0.0.1:55321         127.0.0.1:50007         ESTABLISHED

# iptables -I OUTPUT -p tcp --dport 55321 -j DROP  # netstat -lan|grep 50007                        tcp        5      0 127.0.0.1:55321         127.0.0.1:50007         ESTABLISHED

所以不仅服务端需要有心跳检查,客户端也需要有自己的心跳,在客户端加上心跳检测后,客户端能检测到连接异常,而能够关掉当前连接

有心跳检测但没有重连处理造成的异常,常常发生在测试环境,比如过了一个周末回来发现有服务连不上,重启后生活才能自理,所以对于有心跳检查机制的客户端,需要观察下空闲情况下的网络异常是否会触发重连

几种异常场景的模拟

通过上面2个部分的描述,我们大概能够规划出如何有哪些场景是用来测试重连的

  • 从来源区分:服务器告诉客户端网络挂了,客户端feel到网络挂了
  • 从时机上划分:建立连接时的网络异常,连接建立后的网络异常
  • 从触发方式区分:服务器返回FIN, 服务器返回RST,客户端请求超时,客户端心跳检查失败

建立连接阶段

主要观察本次连接失败,连接是否释放,下次连接是否能成功

建立连接返回错误

用心跳检测时写的那个python客户端访问一个不存在的端口,也可认为是原本存在,但服务挂了就不存在了

socket.error: [Errno 111] Connection refused

wireshark抓包看有收到了RST包

我们通常用iptables REJECT掉发往目标地址的请求,同样能返回[Errno 111] Connection refused
但是这2个真的是一回事么,尝试下,可以发现其实返回的是icmp报错

iptables -I INPUT -p tcp --dport 50007 -j REJECT

而更接近的方式应该是iptables -I INPUT -p tcp –dport 50007 -j REJECT –reject-with tcp-reset

reject-with还有其他的返回错误可以选择,简单列举一下

  • icmp-host-prohibited
iptables -I OUTPUT -p tcp --dport 50007 -j REJECT --reject-with icmp-host-prohibited
[Errno 113] No route to host

  • icmp-net-unreachable
[Errno 101] Network is unreachable

  • icmp-host-unreachable
[Errno 113] No route to host

  • icmp-port-unreachable
[Errno 111] Connection refused

  • icmp-proto-unreachable
[Errno 92] Protocol not available

  • icmp-net-prohibited
[Errno 101] Network is unreachable

建立连接超时

刚才采用的方式是REJECT客户端发出的包,但如果尝试REJECT从服务端返回的包呢?那实际上是模拟网络超时

# iptables -I INPUT -p tcp --sport 50007 -j REJECT

服务端收到了请求但客户端的请求被REJECT掉了,导致客户端会重新尝试建立连接,wireshark抓包的结果如下

三次握手是需要一些时间的,内核中对connect的超时限制大概是75秒,就是说如果超过75秒则connect会由于超时而返回失败,最后连接建立失败,对客户端来说是socket.error: [Errno 110] Connection timed out

对客户端来说DROP或者REJECT掉从服务端返回的包效果是基本一样的,只是对服务端来说REJECT返回了错误信息,处理上会有些区别

另一种超时是DROP发给服务端的包,由于没有返回错误信息,所以会重传SYN直到超时,返回[Errno 110] Connection timed out

# iptables -I INPUT -p tcp --dport 50007 -j DROP

保持连接阶段

主要观察网络断开或不可用时:

  • 是否能够识别并重连:不能重连就只能重启了~~
  • socket数量是否正常:重连不释放连接或者创建的连接过多,把自己或者服务器的连接数占满了
  • 重连的时间间隔是多少:无间隔重试对系统和程序压力太大,而且日志刷到无法自理

服务端关闭连接

如果在服务端改成返回响应后就关闭连接,这是短连接的处理方式,返回正常的FIN

直接kill服务来模拟服务重启、连接关闭,可以发现返回的是RST

在测试过程中的话,不能直接上别人家拆别人家的房子时,可以采用端口转发的方式模拟连接关闭,好处就是不需要kill别人的服务,另外连接配置起来比较方便,不用反复改服务的配置文件,推荐下Linux下简单好用的工具rinetd,能实现端口映射/转发/重定向。

rinetd软件下载
wget http://www.boutell.com/rinetd/http/rinetd.tar.gz
解压安装

tar zxvf rinetd.tar.gzmakemake install

加入一行本地地址 本地端口 远端地址 远端端口,然后运行rinetd

# vim /etc/rinetd.conf0.0.0.0 1111 192.168.231.130 50007# rinetd# ./tcp_client.py

可以看到实际上通过rinetd在客户端和服务端分别建立了连接,可以像直连那样正常访问别人的服务

# netstat -lan|grep 1111tcp        0      0 0.0.0.0:1111            0.0.0.0:*               LISTEN     tcp        0      0 127.0.0.1:1111          127.0.0.1:39233         ESTABLISHEDtcp       20      0 127.0.0.1:39233         127.0.0.1:1111          ESTABLISHED# netstat -lan|grep 50007tcp        0      0 192.168.231.131:38295   192.168.231.130:50007   ESTABLISHE

模拟连接中断,只要用pkill rinetd杀掉rinetd进就好了,效果同kill服务

连接返回网络错误

Reject客户端发出的请求,非tcp-reset的返回错误,由于代码里没有处理错误,连接还保持着。一般客户端可以自行判断错误决定保持连接重试或者关掉原有连接重连,实际表现以代码为准

若设置的是tcp-reset,返回RST,重置连接

[Errno 104] Connection reset by peer

收发请求超时

这类异常的模拟最好DROP掉的是服务器返回客户端的包,原因是排除掉服务器的FIN和RST, 客户端的重连不要强依赖服务器的关闭连接请求。
Drop包可以模拟发送请求超时或者连接已建立但是已不可用的情况,方法和心跳检测那节说的类似,主要需要观察断开连接的时机和触发重连的时机,有心跳检测的观察下心跳是否成功,如果长时间Drop请求再恢复后无法重连成功,那就有问题了

时间: 2024-10-10 10:09:16

关于重连测试的一点研究的相关文章

discuz 7.2 faq.php sql注入的一点研究

6.2号(可能更早)看到网上这个exp,是一个discuz 7.2的sql注射漏洞 经过多番考证,网上多数exp中都存在这些或者那些的问题,我自己利用和修改后总结,利用方法如下: Discuz 7.2 /faq.php SQL注入漏洞 1.获取数据库版本信息 faq.php?action=grouppermission&gids[99]='&gids[100][0]=) and (select 1 from (select count(*),concat(version(),floor(r

对RLC重排序窗口大小的一点讨论

在LTE协议栈的PDCP层和RLC层,都有一个重排序窗口(reordering window),主要用来保证数据的可靠传输,PDCP层的重排序窗口主要用于handover时保证数据的可靠传输,这里暂且不表,只讨论RLC层的重排序窗口. 对RLC层,在AM接收模式和UM接收模式下,UM接收实体/AM实体接收端有一个重排序窗口,当接收到的RLC PDU位于重排序窗口内,且之前没有被接收过时,接收端才会对该RLC PDU进行处理,重排序窗口大小无论是在UM模式还是AM模块下都是序列号(SN)取值范围的

关于MultiDex方案的一点研究与思考

背景 产生65535问题的原因 LinearAlloc问题的原因 Google提出的MultiDex方案 MultiDex实现原理 缺点 美团的多Dex分包动态异步加载方案 多Dex分包 异步加载方案 参考资料 关于我 背景 目前来说,对于使用Android Studio的朋友来说,MultiDex应该不陌生,就是Google为了解决『65535天花板』问题而给出的官方解决方案,但是这个方案并不完美,所以美团又给出了异步加载Dex文件的方案.今天这篇文章是我最近研究MultiDex方案的一点收获

转 个人对kobject的一点研究

在LINUX中最让人不解的大概就是/sys下面的内容了 下面首先让我们来创建一个简单的platform设备,并从这个设备的视角进行深入,在此篇文章的深入过程中,我们只看kobeject的模型我所使用的内核版本号为2.6.26,操作系统的内核版本号为2.6.27-7,暂未发现2.6.27-7与2.6.26的重大不同 首先写一个简单的模块 #include <linux/platform_device.h>#include <linux/init.h>#include <linu

关于测试的一点思考

从2010年开始做测试至今也有六个年头了.从最开始的跟着别人设计好的测试用例一步步的测试执行到现在独立完成一个项目的测试计划.设计.执行.自动化交付等,期间做过大大小小的项目不知道有多少.六年来,其中的酸甜苦辣也只有自己知道.虽已不是测试小白,但距测试专家的程度还有很远,在此记录下一些关于测试的想法,梳理下以后的努力方向. 首先关于软件测试职业以后的发展情况.当前有人或公司提出全栈工程师的概念,即从最开始的需求到最后的交付都由开发人员独立完成,其中包括软件测试,所以以后专职的软件测试人员将不再需

关于指针作为函数参数的一点研究

事情大概起源于这样一个问题: #include<stdio.h> void Try_change(int *p) { int b=7; p=&b; } int main() { int *p=NULL; int a=5; p=&a; Try_change(p); printf("%d\n",*p); return 0; } 当我第一次看到这个题目的时候我觉得答案是7,但是又好像是5,模模糊糊,傻傻分不清楚,这也是我想深入探究下这个问题的原因. 如果你一眼知道

关于卷积神经网络旋转不变性的一点研究

今天一直在思考CNN的旋转不变性,众所周知,CNN具有平移不变性,但是是否具有旋转不变性呢.我们来研究下吧. 1 查阅资料 查阅了许多国内外资料,在解释旋转不变性的时候,普遍得出来,CNN具有一定的旋转不变性,但是这个旋转不变性是有一定的角度控制的,当然起作用的是maxpooling 层,当我们正面拍一些照片的时候,在某些地方会得到activation.然后旋转一定的角度之后,这个依然在相同的点得到activation区域.当然决定这个区域的是maxpooling 层,所以说maxpooling

【原创】Android selector选择器无效或无法正常显示的一点研究

想将LinearLayout作为一个按钮,加上一个动态背景,按下的时候,背景变色,这个理所当然应该使用selector背景选择器来做: <LinearLayout android:id="@+id/btn_user_profit_record" android:layout_width="0dp" android:layout_height="130dp" android:layout_weight="1" androi

关于rem自适应的一点研究

参考地址:http://m.ctrip.com/html5/ https://www.amazon.cn/ rem是相对于html根元素的一个单位.rem是px的16倍,即1rem = 16px;除了IE低版本浏览器不支持它以外,其他都支持,看下图: 现在大部分浏览器IE9+,Firefox.Chrome.Safari.Opera ,如果我们不修改相关的字体配置,都是默认显示font-size是16px即 html { font-size:16px; } 那么如果我们想给一个P标签设置12px的