本帖最后由 lzwml 于 2016-1-14 22:20 编辑
服务器和两个客户端连接,采用epoll非阻塞方式,客户端分别发送心跳包(
6s一次),
服务器发送心跳回应,接收在一个线程,人为操作服务器,每隔
1S查询一次客户端业务情况,
在另一个线程发送,此线程与接收线程未作
互斥同步(Socket可能同时被两线程“写”)
所以服务器的两个线程大约每隔6一次数据冲突。(
这是诱因)
抓包发现的确出现有一定概率的数据冲突。
冲突后,接着服务器关闭1S的查询业务,网络上只心跳包和心跳回应包。
代码上看:
- 每次客户端发心跳包后,服务器返回心跳回应都成功,这两种数据包都不大,都是40byte,不应该导致协议栈满
- 问题是客户端收不到任何数据(这是我关注的问题)
抓包显示此时只有服务器端的TCP协议栈返回受到数据的纯ACK应答, ACK应答里并不包含心跳应答数据内容???
这种包好奇怪,命名负载在TCP头尾部,但wiresharp解析却放在Ethernet头尾部,对于padding第一次听说。
整个包长度60byte,其中6byte全0填在末端,54byte是Ethernet+IP+TCP头
抓包显示10次供400byte的心跳应答一次性发完
同事对最后一次发送的分析是:
TCP断开会有半连接状态,主动断开放问对方是否有数据要发,有就一次性发送。
大致的网络数据流程是这样
此帖出自
Linux与安卓论坛
下面是我对问题排查的过程,作为个人记录,也供后人查阅
1. TCP_QUIKACK
TCP有个延迟确认机制设置TCP_QUIKACK,每次收到数据后分别测试打开和关闭这个值是否有效果
http://www.360doc.com/content/13/0913/15/13047933_314201280.shtml
问题依旧
2. TCP_CORK和Nagle,采用CORK机制立即发送小包
搜索linux头文件里TCP_QUIKACK的具体位置include/linux/tcp.h,include/netinet/tcp.h两个头文件有定义所有setsockopt支持的选项,网上一个个搜索具体作用。
/* TCP socket options */
#define TCP_NODELAY 1 /* Turn off Nagle's algorithm. */
#define TCP_MAXSEG 2 /* Limit MSS */
#define TCP_CORK 3 /* Never send partially complete segments */
#define TCP_KEEPIDLE 4 /* Start keeplives after this period */ 保活机制
#define TCP_KEEPINTVL 5 /* Interval between keepalives */ 保活机制
#define TCP_KEEPCNT 6 /* Number of keepalives before death */ 保活机制
#define TCP_SYNCNT 7 /* Number of SYN retransmits */
#define TCP_LINGER2 8 /* Life time of orphaned FIN-WAIT-2 state */
#define TCP_DEFER_ACCEPT 9 /* Wake up listener only when data arrive */
#define TCP_WINDOW_CLAMP 10 /* Bound advertised window */
#define TCP_INFO 11 /* Information about this connection. */
#define TCP_QUICKACK 12 /* Block/reenable quick acks */
#define TCP_CONGESTION 13 /* Congestion control algorithm */
#define TCP_MD5SIG 14 /* TCP MD5 Signature (RFC2385) */
#define TCP_COOKIE_TRANSACTIONS 15 /* TCP Cookie Transactions */
#define TCP_THIN_LINEAR_TIMEOUTS 16 /* Use linear timeouts for thin streams*/
#define TCP_THIN_DUPACK 17 /* Fast retrans. after 1 dupack */
保活机制我用过
看到有个TCP_CORK,于是百度,后来搜索到一个叫Nagle的算法,默认linux采用该算法,来提高吞吐率,当发送小数据时(几byte),协议栈把他缓存下来,直到达到某些触发条件才发送(比如超过MTU),这种方式对于文件传输服务器特别有效
关于Nagle
http://baike.baidu.com/link?url= ... Ub_Z1-SSJ5BenlVzUsK
TCP_CORK可以说与他对着干,它是个塞子,发送前拧上塞子,把数据存入协议栈,之后打开塞子,塞子里的数据就无论多少一起发送。大概代码
int val;
val = 1;//拧上塞子
setopt(fd, IPPROTO_TCP, TCP_CORK, &val, sizeof(int));
send(...)
val = 0;//打开塞子
setopt(fd, IPPROTO_TCP, TCP_CORK, &val, sizeof(int));
按照上面的代码逻辑,每次发送前后都拧上、打开塞子,希望能强制发送。测试问题依旧,
自己写另一个测试代码,将所有的数据都放入塞子,不打开,并且数据只有2byte,但接收方还是能收到,暂时不去管它。
3. 强制发送1500Byte(MTU)以上数据跳出Nagle机制
如果说数据发送不出去是linux为了提高网络吞吐量,对小数据延迟发送,那么我故意连续发送10次心跳回应,每个心跳回应40byte,client发起心跳间隔6s,所以24S以后必定能操果1500byte,导致服务器强制发送。
测试问题依旧
4. 写个模拟client的应用程序
client应用程序制作两件事情,
发送心跳,
接收服务器的业务查询指令并返回信息
网络拓扑同上,一个server,另个client,其中一个client是真的电路板,另一个是用其他应用程序模拟的。
模拟结果无任何问题。
5. Wireshark抓取与三层交换机端口镜像功能结合抓包
网络拓扑同上,不过这里的client全是真的电路板。
至于什么是端口镜像读者自行百度
当初自掏腰包900多的H3C最便宜的网关交换机,就是为了解决这种问题。
具体的包分析在http://bbs.csdn.net/topics/391890983依旧说清楚,不再阐述。
其中有一个现象没在CSDN里说,端口镜像我拦截流过 【A端口】 的所有数据,正常情况只有server与其中一台client的数据(192.168.0.254 <--> 192.168.0.20)但是我居然发现偶尔(大约1%的概率)有另一他client(192.168.0.5)的数据,数据方向是从server到client。当时没在意就过去了。
当初怀疑是交换机的ARP表过期了(理应不应该),又或者交换机故障(公司自己抄别人交换机的电路板,估计芯片设计有缺陷)将数据发错端口。
于是我弄了市场买的交换机TP-Link微调网络拓扑做测试。
问题依旧掉线。
但这一次我也抓到192.168.0.5的流量,发现192.168.0.5与192.168.0.20的MAC是一样的。
恍然大悟,这样一切都可以解释成功了,所以server不是没有回应client心跳,而是被转发错了,也导致Wireshark弹出TCP Previous segment not capture(包错位)错误。
26124、27337数据包显示:两者的MAC地址都是00:80:e1:33:38:33
server将192.168.0.5非法连接复位RST,同时序列号是个随机数,窗口Win、长度Len都设置成0
有趣的是server下查看arp,cat /proc/net/arp看到的确arp是冲突的,但多次执行该命令,偶尔会不冲突(还好是偶尔不冲突,要是偶尔冲突就麻烦了),现象表明client的MAC地址有跳动的可能,并且跳动导致冲突。
接着将两块client的电路板分别接入电脑查ARP,不错的确冲突,曾经项目刚开始初期我们是对client的芯片STM32 + RT-Thread 读取STM32的某几位96bit世界唯一ID(虽然网上流传该ID是ST公司出厂人为写入的,但我相信出厂已经是唯一的,我自己做产品也验证过),MAC地址是否冲突讨论过的,【当初我也是发现MAC地址在跳动】,后来也是同样的接入电脑查看ARP表,很长一段时间都没冲突。
只能说这次运气好,碰到一个大多数时候(一上电)就冲突的芯片。
以前运气差,MAC地址只是偶尔冲突,找不到证据说服同事检查代码。
现在叫同事手动写死两套程序分配不同的MAC地址
再次联调一切正常
虽然问题解决了,但我还有疑问:
只有当server以1s为间隔查询client业务时才出现断网,当空闲状态两个client都会发送心跳,为什么不以为MAC冲突导致断开连接?
下面用Wireshark的分析Server与Client通信异常前后数据包状况
找到点线索,可能是TCP包错位【没有重传】导致
下面的图片是两组数据包处理重传的情况,
其中第一种处理了重传,另一个没有
54号数据(第14秒):序列号是117,下一个期望序列号(121)
66号数据(第22秒):错误(TCP Previous segment not capture)表示这个包(273)之前的包(121)没有收到,序列号错误。
121的数据包没有发送成功(该包可能路由走错了),序列号变成【273】不是(121)
同时67号数据(第22秒),纠正错误(重传TCP Retransmission)
这样,通信一切正常
紧接着7S后又出现一次(TCP Previous segment not capture)错误
93号数据(第21秒):序列号421,下一个期望序列号(也是421,因为这个包是个【纯ACK】包)
104号数据(第27秒):(TCP Previous segment not capture)错误,序列号473,不是431
次后从115号数据(第33秒)到185号数据(第69秒)就是该帖子最开始描述的现象,“server能收到client的心跳数据(整个帧长94byte,心跳内容40byte),server只返回纯ACK应答(帧长60byte),无心跳应答,持续30秒”
下面是拓扑图
我怀疑上面的两次数据错误是因为交换机数据转发错误,
因为在抓包端【偶尔】能收到192.168.1.254(服务器)发往192.168.0.6(另一台客户端)的流量
那么同样两次错误包也有可能将本该发往192.168.0.20(关注的客户端)的数据发往192.168.0.6(另一台客户端),
【并且】这之后还发生了什么????导致服务器不重发该数据包!!!!
一周热门 更多>