TCP通信流程解析

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 ,它的前身是赫赫有名的 Etherealwireshark 以太网帧的封包格式为:

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

1Ethernet Header = 14 Byte = Dst Physical Address6 Byte+ Src Physical Address6 Byte+ Type2 Byte ),以太网帧头以下称之为数据帧

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

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

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

Ethernet Header 以下的 IP 数据报最大传输单位为 MTU Maximum Transmission UnitEffect 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 FragmentDF=0 ),路由器将 IP 数据报分片后转发。

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

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

TCP UDP

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

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

向外 NAT 在内网和公网之间提供了一个 不对称 桥的映射。 向外 NAT 在默认情况下只允许向外的 session 穿越 NAT :从外向内的的数据包都会被丢弃掉,除非 NAT 设备事先已经定义了这些从外向内的数据包是已存在的内网 session 的一部分。对于一方在 LAN ,一方在 WANUDP 通信,鉴于 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 Filterhost www.baidu.com ;对于通过 PPP over EthernetPPPoE )联网的机器, Wireshark Capture Filterpppoes and host www.baidu.com 。以下抓包示例 直接通过 Ethernet 联网访问百度的过程。可点击图片超链接下载pcap文件,使用wireshark软件查看。

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

1 TCP 三次握手建立连接

“http://” 标识 WWW 访问协议为 HTTP ,根据规则,只有底层协议建立连接之后才能进行更高层协议的连接。在浏览器地址栏输入地址后按下回车键的瞬间, C 建立与 S (机器名为 www.baidu.com DNS 解析出来的 IP220.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 调用 socketlisten 函数进入监听状态; C 调用 connect 函数连接 S[SYN]S 调用 accept 函数接受 C 的连接并发起与 C 方向上的连接: [SYN,ACK]C 发送 [ACK] 完成三次握手, connect 函数返回; S 收到 C 发送的 [ACK] 后, accept 函数返回。

关于 Seq Ack

SeqSequence Number , 为源端 ( source ) 的发送序列号 ; AckAcknowledgment Number , 为目的端 ( destination ) 的接收确认序列号 。在 Wireshark Display Filter 中,可使用 tcp.seqtcp.ack 过滤。

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

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

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

2 TCP 获取网站数据流程

连接建立后,下一步发送( “GET / HTTP/1.1” )请求( RequestHTML 页面,这里 “/” 表示 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 可靠性传输的保障。

Shttp )收到 HTTP GET 报文(共 363 个字节),向 Camqp )发送 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,SISN ,意为已发送过 SYNPacket2 中, Ack=1SIAN 。这里的 Ack-IAN=364-1=363 表示 S 已经从 C 接收到 363 个字节,即 HTTP GET 报文。同时,Ack=364也是S期待C发送的下一个TCP报文序列号(上面分析的 Next sequence number)

接下来, SC 发送 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 HeaderPacket 6 )。

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

尽管握手协商的 MSS1460 ,但服务器或者代理平衡服务器,每次发送过来的 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 调用 socketsend 函数发送 2139 个字节的 Http Response ContentPacket 7Packet 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)

Packet6Packet7Packet9ACK 都是 364 ,这是因为这三个segment都是针对 Packet4TCP 响应。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)]

Camqp )接收到百度首页的 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

与此同时, Cjms )新建一个连接( 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

Packet12Packet13Packet14Packet16Packet17 为对 Packet10TCP 响应(它们的 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 完成新连接的三次握手。然后, Cjms )发送 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

Packet22Packet23Packet24Packet26Packet27 为对 Packet21TCP 响应(它们的 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 这个文件,就把它下载下来作为网站地址栏图标。于是, Camqp )还将发起 GET /favicon.ico HTTP/1.1 请求 网站地址栏图标,见报文 Packet29

3 TCP 四次挥手关闭连接

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

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

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

参考:

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

HTTP通信过程分析

究竟什么是 HTTP连接

一次完整的 HTTP通信步骤

SOCKET TCP/IP HTTP的关系

TCP连接、 Http连接与 Socket连接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值