2007 年 6 月
简介
开发与 Web 服务互动的应用会带来一系列独特的问题。令人沮丧的常见原因是,不知道发送到服务器的确切消息是什么,也不知道收到的响应是什么。最难追踪的一些 bug 是由我们认为自己发送到服务器的内容与实际通过线路传输的内容之间的断开连接造成的。
本文将介绍几种工具,这些工具可帮助您更清晰地了解网络传输的数据,并使其更有用。这些工具通常称为“数据包嗅探器”,可捕获通过网络接口的所有网络数据包。检查这些数据包的内容以及发送和接收顺序可能是一种有用的调试技巧。
示例:检索公开 Feed
我正在组建一支自行车队参加慈善骑行活动,并创建了一个日历来安排信息交流会、团队筹款活动和训练骑行等活动。我已将此日历设为公开,以便团队成员和其他骑手可以查看日历并参与活动。我还想发送包含即将举行的活动的简报,因此我可以使用 Google 日历 Data API 查询此日历并检索活动,而不是从 Google 日历网站复制信息。
Google Calendar API 文档介绍了如何使用 RESTful Google Data API 以编程方式与我的日历互动。(编者注:自 v3 起,Google 日历 API 不再使用 Google 数据格式。)首先,点击日历设置页面上的 按钮,获取日历的活动 Feed 网址:
http://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic
以 Google 日历文档为参考,我可以检索并显示如下日历活动,其中 PUBLIC_FEED_URL
保存了活动 Feed 网址。
CalendarService myService = new CalendarService("exampleCo-fiddlerExample-1"); final String PUBLIC_FEED_URL = "http://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic"; URL feedUrl = new URL(PUBLIC_FEED_URL); CalendarEventFeed resultFeed = myService.getFeed(feedUrl, CalendarEventFeed.class); System.out.println("All events on your calendar:"); for (int i = 0; i < resultFeed.getEntries().size(); i++) { CalendarEventEntry entry = resultFeed.getEntries().get(i); System.out.println("\t" + entry.getTitle().getPlainText()); } System.out.println();
这会生成日历中活动的基本列表:
All events on your calendar: MS150 Training ride Meeting with Nicole MS150 Information session
上面的代码段显示了日历活动的标题,但我们从服务器收到的其余数据呢?Java 客户端库无法轻松地将 Feed 或条目输出为 XML,即使可以,XML 也不是全部内容。那么,随请求一起发送的 HTTP 标头呢?查询是否已通过代理或重定向?随着操作变得越来越复杂,这些问题也变得越来越重要,尤其是在出现问题并收到错误时。数据包嗅探软件可以通过显示网络流量来回答这些问题。
tcpdump
tcpdump 是一种可在类 Unix 平台上运行的命令行工具,但也有一个名为 WinDump 的 Windows 移植版本。与大多数数据包嗅探器一样,tcpdump 会将网卡置于混杂模式,这需要超级用户权限。如需使用 tcpdump,只需指定要监听的网络接口,网络流量就会发送到标准输出:
sudo tcpdump -i eth0
如果您运行此命令,您会看到各种各样的网络流量,其中一些您甚至无法识别。您可以将输出转发到文件,稍后再通过 grep 进行处理,但这可能会导致文件非常大。大多数数据包捕获软件都内置了一些过滤机制,因此您只需捕获所需的数据包。
tcpdump 支持根据网络流量的各种特征进行过滤。例如,您可以在以下表达式中插入服务器的主机名,让 tcpdump 仅捕获端口 80 上进出服务器的流量(HTTP 消息):
dst or src host <hostname> and port 80
对于与过滤表达式匹配的每个数据包,tcpdump 将显示时间戳、数据包的来源和目的地,以及多个 TCP 标志。此信息很有价值,因为它显示了数据包的发送和接收顺序。
查看数据包的内容通常也很有用。“-A”标志会指示 tcpdump 以 ASCII 格式输出每个数据包,从而显示 HTTP 标头和消息正文。“-s”标志用于指定要显示的字节数(其中“-s 0”表示不截断消息正文)。
综合来看,我们得到以下命令:
sudo tcpdump -A -s 0 -i eth0 dst or src host <hostname> and port 80
如果您运行此命令,然后执行上面的简短 .Java 示例,您将看到此操作涉及的所有网络通信。在流量中,您会看到 HTTP GET
请求:
22:22:30.870771 IP dellalicious.mshome.net.4520 > po-in-f99.google.com.80: P 1:360(359) ack 1 win 65535 E.....@....\...eH..c...P.=.....zP......GET /calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic HTTP/1.1 User-Agent: exampleCo-fiddlerExample-1 GCalendar-Java/1.0.6 GData-Java/1.0.10(gzip) Accept-Encoding: gzip Cache-Control: no-cache Pragma: no-cache Host: www.google.com Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 Connection: keep-alive
您还会看到包含 Google 数据 Feed 的 200 OK
响应消息。请注意,Feed 分为四个数据包:
22:22:31.148789 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: . 1:1431(1430) ack 360 win 6432 E...1 ..2.I.H..c...e.P.....z.=.:P..M...HTTP/1.1 200 OK Content-Type: application/atom+xml; charset=UTF-8 Cache-Control: max-age=0, must-revalidate, private Last-Modified: Mon, 11 Jun 2007 15:11:40 GMT Transfer-Encoding: chunked Date: Sun, 24 Jun 2007 02:22:10 GMT Server: GFE/1.3 13da <?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:gCal='http://sc hemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'><id>http ://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.goo gle.com/public/basic</id><updated>2007-06-11T15:11:40.000Z</updated><category sc heme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/g/2 005#event'></category><title type='text'>MS150 Training Schedule</title><subtitl e type='text'>This calendar is public</subtitle><link rel='http://schemas.google .com/g/2005#feed' type='application/atom+xml' href='http://www.google.com/calend ar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic'></ link><link rel='self' type='application/atom+xml' href='http://www.google.com/ca lendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic ?max-results=25'></link><author><name>Lane LiaBraaten</name><email>api.lliabraa@ gmail.com</email></author><generator version='1.0' uri='http://www.google.com/ca lendar'>Google Calendar</generator><openSearch:totalRe 22:22:31.151501 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: . 1431:2861(1430) ack 360 win 6432 E...1!..2.I.H..c...e.P.......=.:P.. 2...sults>3</openSearch:totalResults><openSe arch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch :itemsPerPage><gd:where valueString=''></gd:where><gCal:timezone value='America/ Los_Angeles'></gCal:timezone><entry><id>http://www.google.com/calendar/feeds/24v j3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic/dgt40022cui2k3j 740hnj46744</id><published>2007-06-11T15:11:05.000Z</published><updated>2007-06- 11T15:11:05.000Z</updated><category scheme='http://schemas.google.com/g/2005#kin d' term='http://schemas.google.com/g/2005#event'></category><title type='text'>M S150 Training ride</title><summary type='html'>When: Sat Jun 9, 2007 7am to 10am &nbsp; PDT<br> <br>Event Status: confirmed</summary><conte nt type='text'>When: Sat Jun 9, 2007 7am to 10am&nbsp; PDT<br> <b r>Event Status: confirmed</content><link rel='alternate' type='text/html' href='http://www.google.com/calendar/event?eid=ZGd0NDAwMjJjdWkyazNqNzQwaG5qNDY3 NDQgMjR2ajNtNXBsMTI1YmgyaWpiYm5laDk1M3NAZw' title='alternate'></link><link rel=' self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/24v j3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic/dgt40022cui2k3j 740hnj46744'></link><author><name>MS150 Training Schedule</name></author><gCal:s endEventNotifications value='false'></gCal:sendEventNotifications></entry><entry ><id>http://www.google.com/cal 22:22:31.153097 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: . 2861:4291(1430) ack 360 win 6432 E...1#..2.I.H..c...e.P.......=.:P.. ....endar/feeds/24vj3m5pl125bh2ijbbneh953s%4 0group.calendar.google.com/public/basic/51d8kh4s3bplqnbf1lp6p0kjp8</id><publishe d>2007-06-11T15:08:23.000Z</published><updated>2007-06-11T15:10:39.000Z</updated ><category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.g oogle.com/g/2005#event'></category><title type='text'>Meeting with Nicole</title ><summary type='html'>When: Mon Jun 4, 2007 10am to 11am&nbsp; PDT<br> <br>Where: Conference Room B <br>Event Status: confirmed</summ ary><content type='text'>When: Mon Jun 4, 2007 10am to 11am&nbsp; PDT<br& gt; <br>Where: Conference Room B <br>Event Status: confirmed <br>Event Description: Discuss building cycling team for MS150</content><l ink rel='alternate' type='text/html' href='http://www.google.com/calendar/event? eid=NTFkOGtoNHMzYnBscW5iZjFscDZwMGtqcDggMjR2ajNtNXBsMTI1YmgyaWpiYm5laDk1M3NAZw' title='alternate'></link><link rel='self' type='application/atom+xml' href='http ://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.calendar.goo gle.com/public/basic/51d8kh4s3bplqnbf1lp6p0kjp8'></link><author><name>MS150 Trai ning Schedule</name></author><gCal:sendEventNotifications value='false'></gCal:s endEventNotifications></entry><entry><id>http://www.google.com/calendar/feeds/24 vj3m5pl125bh2ijbbneh953s%40group.calendar.google.com/public/basic/va41amq3r08dhh kpm3lc1abs2o</id><published>20 22:22:31.190244 IP po-in-f99.google.com.80 > dellalicious.mshome.net.4520: P 4291:5346(1055) ack 360 win 6432 E..G1$..2.K.H..c...e.P.....<.=.:P.. ....07-06-11T15:10:08.000Z</published><updat ed>2007-06-11T15:10:08.000Z</updated><category scheme='http://schemas.google.com /g/2005#kind' term='http://schemas.google.com/g/2005#event'></category><title ty pe='text'>MS150 Information session</title><summary type='html'>When: Wed Jun 6, 2007 4pm to Wed Jun 6, 2007 5pm&nbsp; PDT<br> <br>Event Statu s: confirmed</summary><content type='text'>When: Wed Jun 6, 2007 4pm to Wed Jun 6, 2007 5pm&nbsp; PDT<br> <br>Event Status: confirmed< /content><link rel='alternate' type='text/html' href='http://www.google.com/cale ndar/event?eid=dmE0MWFtcTNyMDhkaGhrcG0zbGMxYWJzMm8gMjR2ajNtNXBsMTI1YmgyaWpiYm5la Dk1M3NAZw' title='alternate'></link><link rel='self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/24vj3m5pl125bh2ijbbneh953s%40group.c alendar.google.com/public/basic/va41amq3r08dhhkpm3lc1abs2o'></link><author><name >MS150 Training Schedule</name></author><gCal:sendEventNotifications value='fals e'></gCal:sendEventNotifications></entry></feed>
此输出包含所有 HTTP 标头和内容,以及多个神秘的 TCP 标志。这里显示了所有数据,但难以阅读和理解。有多种图形化工具可让您更轻松地查看这些数据。
WireShark(原称 Ethereal)

WireShark 以多种方式显示网络流量。
WireShark 是一款使用 libpcap(与 tcpdump 使用的库相同)构建的图形化工具,可在 Linux、Mac OS X 和 Windows 上使用。WireShark 的 GUI 实现了多种解读和处理数据包捕获数据的新方式。例如,当从网络接口捕获数据包时,系统会根据数据包使用的协议以不同颜色显示这些数据包。您还可以按时间戳、来源、目的地和协议对流量进行排序。
如果您在数据包列表中选择一行,Wireshark 将以人类可读的树状结构显示数据包标头中的 IP、TCP 和其他协议特定信息。数据还会以十六进制和 ASCII 格式显示在屏幕底部。
虽然 WireShark 的直观特性有助于理解网络流量,但在大多数情况下,您仍然需要过滤网络流量。WireShark 具有强大的过滤功能,包括支持数百种协议。
提示:如需查看可用的协议并构建复杂的过滤条件,请点击 WireShark 窗口顶部附近的 按钮。
如需重现上述 tcpdump 示例中使用的过滤条件,您可以将以下表达式插入 WireShark 过滤条件框中:
ip.addr==<your IP address> && tcp.port==80
或者利用 WireShark 的 HTTP 相关知识:
ip.addr==<your IP address> && http
这样一来,捕获的结果将仅包含与 Google 日历服务器进行此交互时涉及的数据包。您可以点击每个数据包,查看其内容并拼凑出交易。
提示:您可以右键点击其中一个数据包,然后选择“Follow TCP Stream”(跟踪 TCP 流),以便在单个窗口中按顺序显示请求和响应。
WireShark 提供了多种保存捕获信息的方法。您可以保存一个、部分或全部数据包。如果您正在查看 TCP 流,只需点击“另存为”按钮即可仅保存相关数据包。您还可以导入 tcpdump 捕获的输出,并在 WireShark 中查看该输出。
问题:SSL 和加密
数据包捕获工具的一个常见缺点是无法查看通过 SSL 连接加密的数据。上述示例访问的是公开 Feed,因此无需 SSL。不过,如果该示例访问的是私密 Feed,客户端就需要向 Google 身份验证服务进行身份验证,而这需要 SSL 连接。
以下代码段与上一个示例类似,但此处 CalendarService
请求的是用户的日历元 Feed,这是一个需要进行身份验证的私密 Feed。如需进行身份验证,只需调用 setUserCredentials
方法。此方法会触发对 ClientLogin 服务的 HTTPS 请求,并从响应中获取身份验证令牌。然后,CalendarService
对象会在所有后续请求中包含身份验证令牌。
CalendarService myService = new CalendarService("exampleCo-fiddlerSslExample-1"); myService.setUserCredentials(username, userPassword); final String METAFEED_URL = "http://www.google.com/calendar/feeds/default"; URL feedUrl = new URL(METAFEED_URL); CalendarFeed resultFeed = myService.getFeed(feedUrl, CalendarFeed.class); System.out.println("Your calendars:"); for (int i = 0; i < resultFeed.getEntries().size(); i++) { CalendarEntry entry = resultFeed.getEntries().get(i); System.out.println("\t" + entry.getTitle().getPlainText()); } System.out.println();
假设需要进行身份验证并访问私有 Google Data API Feed,那么网络流量如下:
- 向 ClientLogin 服务提交用户凭据
- 向 https://www.google.com/accounts/ClientLogin 发送 HTTP
POST
,并在消息正文中包含以下参数:- 电子邮件地址 - 用户的电子邮件地址。
- Passwd - 用户的密码。
- source - 用于标识您的客户端应用。应采用 companyName-applicationName-versionID 的形式。这些示例使用名称 ExampleCo-FiddlerSSLExample-1。
- 服务 - Google 日历服务名称为“cl”。
- 向 https://www.google.com/accounts/ClientLogin 发送 HTTP
- 接收授权令牌
- 如果身份验证请求失败,您会收到 HTTP 403 禁止访问状态代码。
- 如果成功,则来自服务的响应将是 HTTP 200 OK 状态代码,以及响应正文中的三个长字母数字代码:
SID
、LSID
和Auth
。Auth
值是授权令牌。
- 请求私密日历元 Feed
- 向 http://www.google.com/calendar/feeds/default 发送 HTTP
GET
,并包含以下标头:
Authorization: GoogleLogin auth=<yourAuthToken>
- 向 http://www.google.com/calendar/feeds/default 发送 HTTP
尝试运行此代码段,并在 WireShark 中查看网络流量(使用“http || ssl”作为过滤器)。您会看到交易中涉及的 SSL 和 TLS 数据包,但 ClientLogin 请求和响应数据包已在“应用数据”数据包中加密。别担心,接下来我们将介绍一种可以实际显示这些加密信息的工具。
Fiddler
Fiddler 是另一款图形化数据包嗅探工具,但其行为与目前介绍的工具截然不同。Fiddler 充当应用与您正在互动的远程服务之间的代理,实际上成为中间人。Fiddler 会同时与您的应用和远程 Web 服务建立 SSL 连接,解密来自一个端点的流量,捕获明文,并在发送流量之前重新加密。遗憾的是,Fiddler 仅适用于 Windows,向所有 Mac 和 Linux 用户致歉。
注意:SSL 支持需要 Fiddler 版本 2 和 .NET Framework 版本 2.0。
在 Fiddler 中查看网络流量主要通过“会话检查器”标签页完成。对于调试 Google Data API 问题,最有用的子标签页是:
- 标头 - 以可折叠的树状格式显示 HTTP 标头。
- 身份验证 - 显示身份验证标头。
- 原始 - 以 ASCII 文本显示网络数据包的内容
提示:点击 Fiddler 窗口左下角的 图标可开启和关闭捕获功能。
Fiddler 使用 .NET Framework 配置网络连接,以将 Fiddler 用作代理。这意味着,您使用 Internet Explorer 或 .NET 代码建立的任何连接都会默认显示在 Fiddler 中。不过,上述 Java 示例中的流量不会显示,因为 Java 设置 HTTP 代理的方式不同。
在 Java 中,您可以使用系统属性设置 HTTP 代理。Fiddler 在端口 8888 上运行,因此对于本地安装,您可以通过添加以下代码行,使 Java 代码使用 Fiddler 作为 HTTP 和 HTTPS 的代理:
System.setProperty("http.proxyHost", "localhost"); System.setProperty("http.proxyPort", "8888"); System.setProperty("https.proxyHost", "localhost"); System.setProperty("https.proxyPort", "8888");
如果您运行包含这些行的示例,实际上会从 Java 安全软件包中获得一个令人讨厌的堆栈轨迹:
[java] Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

Fiddler 可以解密并显示 SSL 流量。
当无法验证 SSL 连接中从服务器返回的证书时,会发生此错误。在这种情况下,错误证书来自充当中间人的 Fiddler。Fiddler 会动态生成证书,但由于 Fiddler 不是受信任的签发者,这些证书会导致 Java 在设置 SSL 连接时失败。
注意:当 Fiddler 运行时,您在 Internet Explorer 中建立的任何 SSL 连接都会触发“安全警报”,询问您是否要在证书可疑的情况下继续操作。您可以点击“查看证书”,查看 Fiddler 生成的证书。
那么,如何绕过此安全例外情况呢?基本上,您需要重新配置 Java 的安全框架,以信任所有证书。幸运的是,您无需在此处重新发明轮子 - 请查看 Francis Labrie 的解决方案,并将 SSLUtilities.trustAllHttpsCertificates()
方法添加到上面的示例中。
将 Java 配置为使用 Fiddler 作为代理并停用默认证书验证后,您就可以运行该示例,并以纯文本形式查看通过网络发送的所有流量。别盗取我的密码!
请注意,此身份验证交易只是 SSL 流量的一个小示例。有些 Web 应用完全使用 SSL 连接,因此如果不解密数据,就无法调试 HTTP 流量。
总结
tcpdump 可在 Linux、Mac OS X 和 Windows 上使用,如果您知道自己要查找的内容,并且只需要快速捕获,那么它是一个非常棒的工具。不过,也有一些图形化工具可以以更易于理解的格式呈现网络流量。与本文介绍的工具相比,tcpdump 具有更多选项和过滤功能。如需全面了解 tcpdump 的功能,请键入“man tcpdump”,或访问 tcpdump 手册页在线版。
WireShark 还可在 Linux、Mac OS X 和 Windows 上使用。Wireshark 内置了对数百种协议的支持,因此它不仅是 HTTP 调试的实用工具,还可用于许多其他应用。本简介仅介绍了 WireShark 的部分功能。如需了解详情,请键入“man wireshark”或访问 WireShark 网站。
Fiddler 还具有许多出色的功能,但其独特之处在于能够解密 SSL 流量。如需了解详情,请访问 Fiddler2 网站。
这些数据包嗅探应用是您工具箱中的实用工具,细心的读者会发现它们都是免费的!下次您在使用 Google API 时发现异常情况,不妨拿出其中一款网络分析器,仔细查看网络传输的内容。如果您找不到问题,可以随时向我们的论坛发帖提问。提供相关的网络消息有助于他人了解和诊断您的具体问题。
祝您好运,闻香愉快!