现在的位置: 首页 > 自动控制 > 工业·编程 > 正文

TCP通信流程解析

2012-08-06 22:30 工业·编程 ⁄ 共 8983字 ⁄ 字号 暂无评论

B/S 通信简述

整个计算机网络的实现体现为协议的实现, TCP/IP 协议是 Internet 的核心协议, HTTP 协议是比 TCP 更高层次的应用层协议。

HTTP ( HyperText Transfer Protocol ,超文本传输协议)是互联网上应用最为广泛的一种网络协议。所有的 WWW 文件都必须遵守这个标准。设计 HTTP 的初衷是为了提供一种发布和接收 HTML 页面的方法。

浏览器( Web Browser )负责与服务器建立连接,下载网页(包括资源文件及 JS 脚本文件)到本地,并最终渲染出页面。 JS 脚本文件运行在客户端,负责客户端一些行为响应或预处理,例如提交表单前的数据校验、鼠标事件处理等交互。由此可见,浏览器( Browser )一方面充当了 C/S 通信架构中 C 角色 ,另一方面它是 HTML/JavaScript 的解析渲染引擎( Analyze Render Engine )。

在浏览器地址栏敲入 “http://www.baidu.com/ ” ,按下回车键,浏览器中呈现出百度首页。这样一种情景我们再熟悉不过,本文通过 wireshark 抓取这一过程的 TCP/IP数据包,结合 TCP 协议分析 HTTP 通信的基本流程。

MTU MSS

本文用到的抓包工具为 wireshark ,它的前身是赫赫有名的 Ethereal 。 wireshark 以太网帧的封包格式为:

Frame = Ethernet Header + IP Header + TCP Header + TCP Segment Data

( 1 ) Ethernet Header = 14 Byte = Dst Physical Address ( 6 Byte ) +  Src Physical Address ( 6 Byte ) + Type ( 2 Byte ),以太网帧头以下称之为数据帧

( 2 ) IP Header = 20 Byte ( without options field ),数据在 IP 层称为 Datagram ,分片称为 Fragment

( 3 ) TCP Header = 20 Byte ( without options field ),数据在 TCP 层称为 Stream ,分段称为 Segment ( UDP 中称为 Message ) 。

( 4 ) 54 个字节后为 TCP 数据负载部分( Data Portion ),即应用层用户数据。

Ethernet Header 以下的 IP 数据报最大传输单位为 MTU ( Maximum Transmission Unit , Effect of short board ),对于大多数使用以太网的局域网来说,MTU=1500 。

TCP 数据包每次能够传输的最大数据分段为 MSS ,为了达到最佳的传输效能,在建立 TCP 连接时双方协商 MSS 值,双方提供的 MSS 值的最小值为这次连接的最大 MSS 值。MSS 往往基于 MTU 计算出来,通常 MSS=MTU-sizeof(IP Header)-sizeof(TCP Header)=1500-20-20=1460 。

这样,数据经过本地 TCP 层分段后,交给本地 IP 层,在本地 IP 层就不需要分片了。但是在下一跳路由( Next Hop )的邻居路由器上可能发生 IP 分片!因为路由器的网卡的MTU 可能小于需要转发的 IP 数据报的大小。这时候,在路由器上可能发生两种情况:

( 1 ) . 如果源发送端设置了这个 IP 数据包可以分片( May Fragment , DF=0 ),路由器将 IP 数据报分片后转发。

( 2 ) . 如果源发送端设置了这个 IP 数据报不可以分片( Don’t Fragment , DF=1 ),路由器将 IP 数据报丢弃,并发送 ICMP 分片错误消息给源发送端。

关于 MTU 的探测,参考《 Path MTU discovery 》。我们 可以通过基于 ICMP 协议的 ping 命令来探测从本机出发到目标机器上路由上的 MTU ,详见下文。

TCP UDP

在基于传输层( TCP/UDP )的应用开发中,为了最后的程序优化,应避免端到端的任何一个节点上出现 IP 分片。 TCP 的 MSS 协商机制加上序列号确认机制,基本上能够保证数据的可靠传输。

UDP 协议在 IP 协议的基础上,只增加了传输层的端口( Source Port+Destination Port )、 UDP 数据包长( Length = Header+Data )以及检验和( Checksum)。因此,基于 UDP 开发应用程序时,数据包需要结合 IP 分片情况考虑。对于以太局域网,往往取 UDP 数据包长 Length<=MTU-sizeof(IP Header)=1480 ,故 UDP 数据负载量小于或等于 1472 ( Length-UDP Header );对于公网, ipv4 最小 MTU 为 576 , UDP 数据负载量小于或等于 536 。

“ 向外” NAT 在内网和公网之间提供了一个“ 不对称” 桥的映射。“ 向外” NAT 在默认情况下只允许向外的 session 穿越 NAT :从外向内的的数据包都会被丢弃掉,除非 NAT 设备事先已经定义了这些从外向内的数据包是已存在的内网 session 的一部分。对于一方在 LAN ,一方在 WAN 的 UDP 通信,鉴于 UDP 通信不事先建立虚拟链路, NAT 后面的 LAN 通信方需先发送消息给 WAN 通信方以洞穿 NAT ,然后才可以进行双向通信,这即是常提到的 “UDP 打洞( Hole Punching ) ” 问题。

TCP 连接百度过程解析

下文对百度的完整抓包建立在不使用 缓存的基础上。如若主机存有百度站点的 cookie 和脱机缓存( Offline Cache ),则不会再请求地址栏图标 favicon.ico ;请求/js/bdsug.js?v=1.0.3.0 可能回应 “HTTP/1.1 304 Not Modified” 。可在浏览器打开百度首页后,Ctrl+F5强制刷新,不使用缓存,也可参考《 浏览器清除缓存方法 》。

以下为访问百度过程, wireshark 抓包数据。对于直接通过 Ethernet 联网的机器, Wireshark Capture Filter 为 host www.baidu.com ;对于通过 PPP over Ethernet ( PPPoE )联网的机器, Wireshark Capture Filter 为 pppoes and host www.baidu.com 。以下抓包示例 直接通过 Ethernet 联网访问百度的过程。可点击图片超链接下载pcap文件,使用wireshark软件查看。

为方便起见,以下将客户端(浏览器)简称为 C ,将服务器(百度)简称为 S 。

clip_image002

1 TCP 三次握手建立连接

“http://” 标识 WWW 访问协议为 HTTP ,根据规则,只有底层协议建立连接之后才能进行更高层协议的连接。在浏览器地址栏输入地址后按下回车键的瞬间, C 建立与 S (机器名为 www.baidu.com , DNS 解析出来的 IP 为 220.181.6.175 )的 TCP 80 连接( HTTP 默认使用 TCP 80 端口)。

以下为三次握手建立 TCP 连接的数据包( Packet1-Packet3 )。

1   192.168.89.125:5672 → 220.181.6.175:80    TCP( 协议 ) 62( 以太网 帧长 )

amqp > http [SYN] Seq=0 Win=65535 Len=0 MSS=1460 SACK_PERM =1

2   220.181.6.175:80 → 192.168.89.125:5672 TCP 62

http > amqp [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 SACK_PERM=1

3   192.168.89.125:5672 → 220.181.6.175:80    TCP 54

amqp > http [ACK] Seq=1 Ack=1 Win=65535 Len=0

三次握手建立 TCP 连接的流程如下:

     C(Browser)                                     S(www.baidu.com)

  1.  CLOSED                                              LISTEN

  2.  SYN-SENT      → <SEQ=0><CTL=SYN>               → SYN-RECEIVED

  3.  ESTABLISHED ← <SEQ=0><ACK=1><CTL=SYN,ACK>  ← SYN-RECEIVED

  4.  ESTABLISHED → <SEQ=1><ACK=1><CTL=ACK>         → ESTABLISHED

3-Way Handshake for Connection Synchronization

三次握手的 socket 层执行逻辑

S 调用 socket 的 listen 函数进入监听状态; C 调用 connect 函数连接 S : [SYN] , S 调用 accept 函数接受 C 的连接并发起与 C 方向上的连接: [SYN,ACK] 。 C 发送 [ACK] 完成三次握手, connect 函数返回; S 收到 C 发送的 [ACK] 后, accept 函数返回。

关于 Seq Ack

Seq 即 Sequence Number , 为源端 ( source ) 的发送序列号 ; Ack 即 Acknowledgment Number , 为目的端 ( destination ) 的接收确认序列号 。在Wireshark Display Filter 中,可使用 tcp.seq 或 tcp.ack 过滤。

在 Packet1 中, C:5672 向 S:80 发送 SYN 握手包, Seq=0(relative sequence number) ;在 Packet2 中 , S:80 向 C:5672 发送 ACK 握手回应包,Ack=1(relative sequence number) ,同时发送 SYN 握手包, Seq=0(relative sequence number) ;在 Packet3 中, C:5672 向 S:80 发送 ACK 握手回应包,Seq=1 , Ack=1 。

至此, Seq=1 为 C 的 Initial Sequence Number ( ISN ),后期某一时刻的 Seq=ISN+ 累计发送量 (cumulative sent) ; Ack=1 为 C 的 Initial Acknowledge Number ( IAN ),后期某一时刻的 Ack=IAN+ 累计接收量 (cumulative received) 。对于 S 而言, Seq 和 Ack 情同此理。

参考 :《TCP Analyze Sequence Numbers 》、《Understanding TCP Sequence and Acknowledgement Numbers

2 TCP 获取网站数据流程

连接建立后,下一步发送( “GET / HTTP/1.1” )请求( Request ) HTML 页面,这里 “/” 表示 S 的默认首页, “GET” 为 HTTP Request Method ; “/” 为 Request-URI ,这里为相对地址; HTTP/1.1 表示使用的 HTTP 协议版本号为 1.1 。

以下为 HTTP GET 请求数据包( Packet4 )。

4   192.168.89.125:5672 → 220.181.6.175:80 HTTP 417

GET / HTTP/1.1

HTTP GET 报文长 =417-54=363 个字节,其中 Next sequence number: 364(relative sequence number) 表示,若 在规定的时间内收到S 响应 Ack=364 ,表明该报文发送成功,可以发送下一个报文( Seq=364 );否则重传(TCP Retransmitssion )。序列号确认机制是 TCP 可靠性传输的保障。

S ( http )收到 HTTP GET 报文(共 363 个字节),向 C ( amqp )发送 TCP 确认报文 ( Packet5 )。

5   220.181.6.175:80 →   192.168.89.125:5672  TCP 60

http > amqp [ACK] Seq=1 Ack=364 Win=6432 Len=0

这里 Seq=1, 为 S 的 ISN ,意为已发送过 SYN 。 Packet2 中, Ack=1 为 S 的 IAN 。这里的 Ack-IAN=364-1=363 表示 S 已经从 C 接收到 363 个字节,即 HTTP GET 报文。同时,Ack=364也是S期待C发送的下一个TCP报文序列号(上面分析的 Next sequence number) 。

接下来, S 向 C 发送 Http Response ,根据 HTTP 协议,先发响应头( Response Header ),再发百度首页 HTML 文件。

Http Response Header 报文 ( Packet6 ) 如下 。

6   220.181.6.175:80 →   192.168.89.125:5672  TCP 465

[ TCP segment of a reassembled PDU ]

其部分内容如下:

======================================

HTTP/1.1 200 OK

……

Content-Length: 2139

Content-Type: text/html;charset=gb2312

Content-Encoding: gzip

======================================

S 响应 C 的 “GET / HTTP/1.1” 请求,先发送带 [PSH ] 标识的 411 个字节的 Http Response Header ( Packet 6 )。

TCP 头部 [PSH] 标识置位,敦促 C 将缓存的数据推送给应用程序,即先处理 Http Response Header ,实际上是一种 “ 截流 ” 通知。相应 C 的 socket 调用 send 时 在IPPROTO_TCP 选项级别设置 TCP_NODELAY 为 TRUE 禁用 Nagle 算法可以 “ 保留发送边界 ” ,以防粘连。

尽管握手协商的 MSS 为 1460 ,但服务器或者代理平衡服务器,每次发送过来的 TCP 数据最多只有 1420 个字节 。 可以使用 ping -f -l size target_name 命令向指定目标 target_name 发送指定字节量的 ICMP 报文,其中 -l size 指定发送缓冲区的大小; -f 则表示在 IP 数据报中设置不分片( Don’t Fragment ),这样便可探测出到目标路径上的 MTU 。

执行“ ping -f -l 1452 www.baidu.com ”的结果如下:

220.181.6.18 的 Ping 统计信息 :

数据包 : 已发送 = 4 ,已接收 = 4 ,丢失 = 0 (0% 丢失 )

执行“ ping -f -l 1453 www.baidu.com ”的结果如下:

需要拆分数据包但是设置 DF 。

220.181.6.18 的 Ping 统计信息 :

数据包 : 已发送 = 4 ,已接收 = 0 ,丢失 = 4 (100% 丢失 )

从以上 ping 结果可知,在不分片时,从本机出发到百度的路由上能通过的最大数据量为 1452 ,由此推算出 MTU{local,baidu}=sizeof(IP Header)+ sizeof(ICMP Header)+sizeof(ICMP Data Portion)=20+8+1452=1480 。

S 调用 socket 的 send 函数发送 2139 个字节的 Http Response Content ( Packet 7 、 Packet 9 ),在 TCP 层将分解为两段( segment )后再发出去。

7   220.181.6.175:80 →   192.168.89.125:5672  TCP 1474

[TCP segment of a reassembled PDU]

由 “Content-Length: 2139” 可知, HTML 文件还有 2139-(1474-54)=719 个字节。但此时, C 已经发送了确认报文 ( Packet8 ) 。

8   192.168.89.125:5672 →    220.181.6.175:80  TCP 54

amqp > http [ACK] Seq=364 Ack=1832 Win=65535 Len=0

Seq-ISN=364-1=363 ,表示 C 已经发出了 363 个字节,上边已经收到了 S 的确认。 Ack-IAN=1832-1=(465-54)+(1474-54) ,表示 C 至此已经接收到 S 发来的1831 个字节。

接下来, C 收到 HTML 文件剩余的 719 个字节,报文 ( Packet9 )如下。

9   220.181.6.175:80 →   192.168.89.125:5672  HTTP   773

HTTP/1.1 200 OK

至此, C 收到 S 发送过来的全部 HTTP 响应报文,即百度首页 HTML 内容 (text/html) 。

Packet6 、 Packet7 和 Packet9 的 ACK 都是 364 ,这是因为这三个segment都是针对 Packet4 的 TCP 响应。S将百度首页HTML文件(一个完整的HTTP报文)按照MSS分段提交给TCP层。 在 Wireshark 中可以看到 Packet9 的报文中有以下 reassemble 信息:

[Reassembled TCP segments (2555 bytes): #6(411),#7(1420),#9(719)]

[Frame: 6, payload: 0-410(411 bytes)]

[Frame: 7, payload: 411-1830(1420 bytes)]

[Frame: 9, payload: 1831-2549(719 bytes)]

C ( amqp )接收到百度首页的 HTML 文件后,开始解析渲染。在解析过程中,发现页面中含有百度的 logo 资源 baidu_logo.gif ,并且需要 bdsug.js 脚本。

<img src=" http://www.baidu.com/img/baidu_logo.gif " width="270" height="129" usemap="#mp">

{d.write('<script src=http://www.baidu.com/js/bdsug.js?v=1.0.3.0><//script>')}

于是上面那个连接( C:5672 )继续向 S 请求 logo 图标资源,报文( Packet10 )如下。

10  192.168.89.125:5672 →    220.181.6.175:80  HTTP 492

GET /img/baidu_logo.gif HTTP/1.1

与此同时, C ( jms )新建一个连接( TCP 5 673 )向 S 请求 js 脚本文件。 报文( Packet11 )如下。

11  192.168.89.125:5673 →    220.181.6.175:80  TCP 62

jms > http [SYN] Seq=0 Win=65535 Len=0 MSS=1460 SACK_PERM=1

( Packet12 ) Packet13 、 Packet14 、 Packet16 和 Packet17 为对 Packet10 的 TCP 响应(它们的 Ack=802 ), 在逻辑上它们是一个完整的 TCP 报文。其 Http Response Content 为图片文件 baidu_logo.gif 。我们在 Wireshark 中可以看到 Packet17 的报文中有以下 reassemble 信息:

[Reassembled TCP segments (1801 bytes): #13(312),#14(1420),#16(28) ,#17(41)]

[Frame: 13, payload: 0-311(312 bytes)]

[Frame: 14, payload: 312-1731(1420 bytes)]

[Frame: 16, payload: 1732-1759(28 bytes)]

[Frame: 17, payload: 1760-1800(41 bytes)]

Packet11-Packet19-Packet20 完成新连接的三次握手。然后, C ( jms )发送 “ GET /js/bdsug.js?v=1.0.3.0 HTTP/1.1 ” 报文( Packet21 ),以获取bdsug.js 脚本文件。

21  192.168.89.125:5673 →    220.181.6.175:80  HTTP 465

GET /js/bdsug.js?v=1.0.3.0 HTTP/1.1

( Packet22 ) Packet23 、 Packet24 、 Packet26 和 Packet27 为对 Packet21 的 TCP 响应(它们的 Ack=412 ), 在逻辑上它们是一个完整的 TCP 报文。其 Http Response Content 为脚本文件 bdsug.js 。我们在 Wireshark 中可以看到 Packet27 的报文中有以下 reassemble 信息:

[Reassembled TCP segments (3897 bytes): #23(310),#24(1420),#26(1420) ,#27(747)]

[Frame: 23, payload: 0-309(310 bytes)]

[Frame: 24, payload: 310-1729(1420 bytes)]

[Frame: 26, payload: 1730-3149(1420 bytes)]

[Frame: 27, payload: 3150-3896(747 bytes)]

通常,浏览器会自动的搜索网站的根目录,只要它发现了 favicon.ico 这个文件,就把它下载下来作为网站地址栏图标。于是, C ( amqp )还将发起 “ GET /favicon.ico HTTP/1.1 ” 请求 网站地址栏图标,见报文 Packet29 。

3 TCP 四次挥手关闭连接

经 Packet28 确认收到了完整的 japplication/javascript 文件后,链路 1 (本地端口 5673 )使命结束, S 关闭该链路,进入四次挥手关闭双向连接。

( Packet30 ) Packet31 和 Packet32 为对 Packet29 的 TCP 响应(它们的 Ack=1201 )。 经 Packet33 确认收到了完整的 image/x-icon 文件后,链路 2 (本地端口 5672 )使命结束, S 关闭该链路,进入四次挥手关闭双向连接。

为什么握手是三次,而挥手是四次呢?这是因为握手时,服务器往往在答应建立连接时,也建立与客户端的连接,即所谓的双向连接。所以,在 Packet2 中,服务器将 ACK 和SYN 打包发出。挥手,即关闭连接,往往只是表明挥手方不再发送数据(无数据可发),而接收通道依然有效(依然可以接受数据)。当对方也挥手时,则表明对方也无数据可发了,此时双向连接真正关闭。

参考:

浏览器 /网页工作原理 》《 What really happens when you navigate to a URL

HTTP通信过程分析

究竟什么是 HTTP连接

一次完整的 HTTP通信步骤

SOCKET与 TCP/IP与 HTTP的关系

TCP连接、 Http连接与 Socket连接

【上篇】
【下篇】

给我留言

留言无头像?