A:我发了。
B:没收到。
A:我发了!
B:没收到!
A:我发了!发了!发了!
B:没收到!没收到!没收到!
B:计时器到了,看我kill you!
A:你,你,你,啊!...
几秒钟后,
A:一个崭新的我又来了!
B:再不按时发心跳,我还杀了你!
A:我不怕,我是九头怪,我是九尾猫。
B:计时器到了,看我再kill you!
A:你,你,你,啊!...
又过了几秒,
A:一个崭新的我又来了!
B:真讨厌,还能不能一起愉快滴干活了???
......
列位看官,上面这段不是小品,不是相声,而是一台主机上运行的A、B俩进程间的隔空喊话。为啥是隔空涅?因为原本他们之间正常的通信出了问题。
按照执行逻辑,A、B在主机中通过UDP协议进行通信,A不定时发送业务数据,定时发送心跳,B接收并计数。当B的接收计时器超时,认为A故障,B要先杀掉A,几秒钟后再重启A。如果重启A后,双方的通信还是不能恢复,就会出现这样循环往复的重启、等待、杀掉...
查看B的运行日志,终止A的原因确实是B的信息接收计时器超时,而A的运行日志则显示已经按时向B发送心跳信息。在排除了B接收计时出错的可能性之后,问题聚焦在A与B两进程之间的UDP通信上。
UDP是一种无连接的传输层协议,重效率轻可靠性,不提供数据包分组、组装,不能对数据包进行排序,因此当报文发送之后,是无法得知其是否安全完整到达的。如此一来,我们就难以断定到底是A进程的发送出了问题,还是B进程的接收出了问题。
这明面上是A、B两个进程间的较劲,实际上是两个软件的开发者在顶牛,谁都认为自己的软件没问题,bug当然都是别人的,如果纵容这么无休止的扯下去,必是一个无头葫芦案呀!既然当事双方互不认输,自然需要用其它方法来获得A、B之间实际的数据发送与接收情况,也就是要对网络通信数据包进行截获与分析。
一说到网络分析,当然要祭出Wireshark了!
那位看官问了:Wireshark何方神圣?可否担当此任?它比《石头记》里那个葫芦僧强在哪儿?
您且往下看:
Wireshark能够利用网卡的混杂模式(Promiscuous Mode),通过WinPcap接口直接获取网络数据包,不影响正常通信,端的是网络海洋中浑水摸鱼的神器。
它既支持TCP、UDP、HTTP等通用协议,也支持AppleTalk这样的高级协议,绝对的生冷不忌,荤素通吃。
它能够获取网络上的实时数据流,并快速生成与数据相关的协议、传输介质、通信通道等信息,好一个庖丁解牛、快字当头。
……
鉴于Wireshark拥有的强大而可靠的功能,利用它来分析A、B进程间的实际通信情况,化解顶牛扯皮,消除网络迷雾,明断是非,当是一个绝佳的选择。
抽丝剥茧之前,大伙先来看看A和B的开发与运行环境:
操作系统 | Windows Server 2008 R2 Standard |
运行平台 | .NET v2.0 |
开发环境 | Visual Studio 2005 |
编程语言 | C# |
通信方式 | UDP协议,通过主机的IP地址192.168.1.16。 |
端口 | A的发送端口由操作系统随机确定,B的接收端口为1024。 |
下面就让Wireshark出来走几步,勘察罪案现场,抓抓数据,录录口供:
这第一步叫“选择目标接口”:Wireshark具备同时对一台主机上的多个不同类型网络接口进行数据捕获的能力,为了避免有用的信息被淹没在海量的数据中,需要将目标接口限定在本案所关注的网卡上,也就是具有192.168.1.16地址的那个网卡。
这第二步是“设置捕获过滤器(Capture Filter)”:通过捕获过滤器可以让Wireshark只捕获满足条件的数据包。
本案使用的捕获过滤器表达式为:udp && src host 192.168.1.16。其含义是:只捕获UDP数据包,并且数据包的源地址是192.168.1.16。
这第三步称“设置显示过滤器(Display Filter)”:通过显示过滤器可以让Wireshark对已捕获的数据进一步筛选,并显示在用户界面上。
本案使用的显示过滤器表达式为:eth.dst == 40:f2:e9:9d:b9:d4 && udp.dstport eq 1024。其含义是:显示目标网卡的MAC地址是“40:f2:e9:9d:b9:d4”(也就是192.168.1.16对应的MAC地址),并且UDP目标端口是1024的数据包。
这第四步乃“捕获数据包”:Wireshark在执行实时数据捕获时,会将有效数据显示在界面的“数据包列表”、“数据包详情”、“数据包字节”三个区域中。数据包列表区中列出了所有符合过滤条件的数据包,内容为序号、时间、源地址、目标地址、协议类型等;数据包详情区会将用户在数据包列表区选中的某数据包的详细信息展示出来,包括各层协议的字段内容;数据包字节区以16进制形式显示出选中数据包的全部字节内容。
这第四步曰“创建图表”:本案关心的是数据发送与时间的关系,因此与数据包列表相比,数据统计图能更加直观的反映通信变化的趋势。Wireshark的兵器谱里有一款“IO Graph”正适合绘制这样的统计图。IO Graph的参数是可以定制的,例如将X轴定义为实际时间轴,1秒/刻度,刻度间隔10像素;Y轴定义为数据包数量轴,1包/刻度,最大刻度为10;数据过滤器表达式与显示过滤器表达式一致。
这最后一步便是“完成捕获”:如果捕获到了足够多的数据包,可以停止捕获,并将全部的捕获数据或者仅数据包列表区的数据保存成捕获文件,以便于进行后续的离线数据分析。
该查的查了,该问的问了,接下来咱们就要分析案情,升堂审理了。
通过查看B进程的运行日志,得知A进程在19:06:35出现过心跳发送超时现象。Wireshark绘制的相应时段IO Graph如下:
图中先出现尖峰,随后进入波谷并探底,最后又恢复正常。再查看对应时间段“数据包列表区”和“数据包详情区”显示的信息,A进程在19:06:27—19:06:30发送大量的业务信息,1秒钟后停止发送信息,在大约4秒后的19:06:35恢复正常,继续发送心跳信息。上述情况在后续长时间的捕获与分析中也多次反复出现,因此初步的结论是:A进程中业务信息的发送影响了心跳信息的按时发送。
随后通过复查A的源代码,印证了上述结论。A采用两个独立的线程分别负责发送业务信息和心跳信息,线程间共用一个UdpClient通信对象。虽然开发者在两个线程间设置了同步锁,但业务信息发送线程在完成发送任务后没有及时释放通信对象和同步锁,进而造成心跳发送线程等待时间过长,后续心跳发送超时。
问题的根源找到了,解决的方法也就显而易见了,就是在业务信息发送线程完成信息发送后迅速释放通信对象,解除同步锁。
至此,审案结束。Wireshark不是葫芦僧,更不是那“假语村言”。原告、被告心服口服。退堂!
Wireshark看似一把瑞士军刀,短小精悍,实乃网络分析的重武器。这里只是献个丑,让它牛刀小试。如果哪天真正在某个大事件里派上了用场,它的好处,你懂的!