报文段
报文段是指TCP/IP协议网络传输过程中,起着路由导航作用
用以查询各个网络路由网段,ip地址,交换协议等ip数据包
报文段充当整个tcp/ip协议数据包的导航路由功能
报文在传输过程中会不断的封装成组、包、帧来传输的
传输协议
协议顾名思义,一种规定,约束
约定大于配置,在网络传输中依然适用;网络的传输流程是健壮的稳定的,得益于基础的协议构成
简单来说:A->B的传输数据,B能识别,繁殖B->A的传输数据A也能识别,这就是协议
IP地址
"1.1.1.1": 为直接广播地址,会向全网广播UDP消息,但是一般会被路由器防火墙拦截
"255.255.255.255": 为受限广播地址,会在局域网络内发送UDP消息
UDP
用户数据报协议,基于报文的协议,而不是基于连接的协议
应用场景:DNS TFTP SNMP
UDP包的最大长度
16位-> 2字节 存储长度信息;2^16-1=65535;自身协议占用:32+32位=64位=8字节
所以最大数据长度:65535-8=65507 byte字节
UDP单播,广播,多播
单播:点对点
广播:给所有设备发送数据(例如:受限广播地址,255.255.255.255)
- C网广播地址一般为:xxx.xxx.xxx.255(192.168.1.255)
多播(组播):给一组发送数据
- D类IP地址为多播预留
广播地址运算
IP;192.168.124.7
子网掩码: 255.255.255.0
网络地址: 192.168.124.0
广播地址: 192.168.124.255
但是运算结果不是绝对的,如下:
如果子网掩码是:255.255.255.192-> 11111111.11111111.11111111.11000000
则可划分网段就是最后的一个字节的1数据,即2*2=4个
四个网段为:0-63 64-127 128-191 n 192-255
IP:192.168.124.7归属于第一个网段,所以广播地址就是该网段的
最后一个即192.168.124.63 ,网络地址(是该网段第一个地址)
仍是192.168.124.0
广播通信问题
如上图,主机一位于第一个网段,主机二位于第二个网段,所以二者无法通信
协议
什么是协议
从应⽤的⻆度出发,协议可理解为“规则”,是数据传输和数据的解释的规则。
假设,A、B双⽅欲传输⽂件。规定:
- 第⼀次,传输⽂件名,接收⽅接收到⽂件名,应答OK给传输⽅;
- 第⼆次,发送⽂件的尺⼨,接收⽅接收到该数据再次应答⼀个OK;
- 第三次,传输⽂件内容。同样,接收⽅接收数据完成后应答OK表示⽂件内容接收成功
由此,⽆论A、B之间传递何种⽂件,都是通过三次数据传输来完成。A、B之间形成了⼀个最简单的数据传输规则。双⽅都按此规则发送、接收数据。A、B之间达成的这个相互遵守的规则即为协议。
这种仅在A、B之间被遵守的协议称之为原始协议。当此协议被更多的⼈采⽤,不断的增加、改进、维护、完善。最终形成⼀个稳定的、完整的⽂件传输协议,被⼴泛应⽤于各种⽂件传输过程中。该协议就成为⼀个标准协议。最早的ftp协议就是由此衍⽣⽽来。
TCP协议注重数据的传输。http协议着重于数据的解释。
典型协议
传输层 常⻅协议有TCP/UDP协议。
应⽤层 常⻅的协议有HTTP协议,FTP协议。
⽹络层 常⻅协议有IP协议、ICMP协议、IGMP协议。
⽹络接⼝层 常⻅协议有ARP协议、RARP协议。
TCP传输控制协议(Transmission Control Protocol)是⼀种⾯向连接的、可靠的、基于字节流的
传输层通信协议。
UDP⽤户数据报协议(User Datagram Protocol)是OSI参考模型中⼀种⽆连接的传输层协议,
提供⾯向事务的简单不可靠信息传送服务。
HTTP超⽂本传输协议(Hyper Text Transfer Protocol)是互联⽹上应⽤最为⼴泛的⼀种⽹络协
议。
FTP⽂件传输协议(File Transfer Protocol)
IP协议是因特⽹互联协议(Internet Protocol)
ICMP协议是Internet控制报⽂协议(Internet Control Message Protocol)它是TCP/IP协议族的⼀个⼦协议,⽤于在IP主机、路由器之间传递控制消息。
IGMP协议是 Internet 组管理协议(Internet Group Management Protocol),是因特⽹协议家
族中的⼀个组播协议。该协议运⾏在主机和组播路由器之间。
ARP协议是正向地址解析协议(Address Resolution Protocol),通过已知的IP,寻找对应主机
的MAC地址。
RARP是反向地址转换协议,通过MAC地址确定IP地址。
分层模型
- 物理层:
主要定义物理设备标准,如⽹线的接⼝类型、光纤的接⼝类型、各种传输介质的传输速率等。它的主要作⽤是传输⽐特流(就是由1、0转化为电流强弱来进⾏传输,到达⽬的地后再转化为1、0,也就是我们常说的数模转换与模数转换)。这⼀层的数据叫做⽐特。
- 数据链路层:
定义了如何让格式化数据以帧为单位进⾏传输,以及如何让控制对物理介质的访问。这⼀层通常还提供错误检测和纠正,以确保数据的可靠传输。如:串⼝通信中使⽤到的115200、 8、N、1
- ⽹络层:
在位于不同地理位置的⽹络中的两个主机系统之间提供连接和路径选择。Internet的发展使得从世界各站点访问信息的⽤户数⼤⼤增加,⽽⽹络层正是管理这种连接的层。
- 传输层:
定义了⼀些传输数据的协议和端⼝号(WWW端⼝80等),如:TCP(传输控制协议,传输效率低,可靠性强,⽤于传输可靠性要求⾼,数据量⼤的数据),UDP(⽤户数据报协议,与TCP特性恰恰相反,⽤于传输可靠性要求不⾼,数据量⼩的数据,如QQ聊天数据就是通过这种⽅式传输的)。主要是将从下层接收的数据进⾏分段和传输,到达⽬的地址后再进⾏重组。常常把这⼀层数据叫做段。
- 会话层:
通过传输层(端⼝号:传输端⼝与接收端⼝)建⽴数据传输的通路。主要在你的系统之间发起会话或者接受会话请求(设备之间需要互相认识可以是IP也可以是MAC或者是主机名)。
- 表示层:
可确保⼀个系统的应⽤层所发送的信息可以被另⼀个系统的应⽤层读取。例如,PC程序与另⼀台计算机进⾏通信,其中⼀台计算机使⽤扩展⼆⼀⼗进制交换码(EBCDIC),⽽另⼀台则使⽤美国信息交换标准码(ASCII)来表示相同的字符。如有必要,表示层会通过使⽤⼀种通格式来实现多种数据格式之间的转换。
- 应⽤层
是最靠近⽤户的OSI层。这⼀层为⽤户的应⽤程序(例如电⼦邮件、⽂件传输和终端仿真)提供⽹络服务。
下图模拟一个数据发送到接收一层层封包和解包的过程
通信过程
TCP/IP通信过程
上图对应两台计算机在同⼀⽹段中的情况,如果两台计算机在不同的⽹段中,那么数据从⼀台计算机到另⼀台计算机传输过程中要经过⼀个或多个路由器,如下图所示:
跨路由通信
链路层有以太⽹、令牌环⽹等标准,链路层负责⽹卡设备的驱动、帧同步(即从⽹线上检测到什么信号算作新帧的开始)、冲突检测(如果检测到冲突就⾃动重发)、数据差错校验等⼯作。交换机是⼯作在链路层的⽹络设备,可以在不同的链路层⽹络之间转发数据帧(⽐如⼗兆以太⽹和百兆以太⽹之间、以太⽹和令牌环⽹之间),由于不同链路层的帧格式不同,交换机要将进来的数据包拆掉链路层⾸部重新封装之后再转发。
⽹络层的IP协议是构成Internet的基础。Internet上的主机通过IP地址来标识,Inter-net上有⼤量路由器负责根据IP地址选择合适的路径转发数据包,数据包从Internet上的源主机到⽬的主机往往要经过⼗多个路由器。路由器是⼯作在第三层的⽹络设备,同时兼有交换机的功能,可以在不同的链路层接⼝之间转发数据包,因此路由器需要将进来的数据包拆掉⽹络层和链路层两层⾸部并重新封装。IP协议不保证传输的可靠性,数据包在传输过程中可能丢失,可靠性可以在上层协议或应⽤程序中提供⽀持。
⽹络层负责点到点(ptop,point-to-point)的传输(这⾥的“点”指主机或路由器),⽽传输层负责端到端(etoe,end-to-end)的传输(这⾥的“端”指源主机和⽬的主机)。传输层可选择TCP或UDP协议。
TCP是⼀种⾯向连接的、可靠的协议,有点像打电话,双⽅拿起电话互通身份之后就建⽴了连接,然后说话就⾏了,这边说的话那边保证听得到,并且是按说话的顺序听到的,说完话挂机断开连接。也就是说TCP传输的双⽅需要⾸先建⽴连接,之后由TCP协议保证数据收发的可靠性,丢失的数据包⾃动重发,上层应⽤程序收到的总是可靠的数据流,通讯之后关闭连接。UDP是⽆连接的传输协议,不保证可靠性,有点像寄信,信写好放到邮筒⾥,既不能保证信件在邮递过程中不会丢失,也不能保证信件寄送顺序。使⽤UDP协议的应⽤程序需要⾃⼰完成丢包重发、消息排序等⼯作。
⽬的主机收到数据包后,如何经过各层协议栈最后到达应⽤程序呢?其过程如下图所示:
以太⽹驱动程序⾸先根据以太⽹⾸部中的“上层协议”字段确定该数据帧的有效载荷(payload,指除去协议⾸部之外实际传输的数据)是IP、ARP还是RARP协议的数据报,然后交给相应的协议处理。假如是IP数据报,IP协议再根据IP⾸部中的“上层协议”字段确定该数据报的有效载荷是TCP、
UDP、ICMP还是IGMP,然后交给相应的协议处理。假如是TCP段或UDP段,TCP或UDP协议再根据TCP⾸部或UDP⾸部的“端⼝号”字段确定应该将应⽤层数据交给哪个⽤户进程。IP地址是标识⽹络中不同主机的地址,⽽端⼝号就是同⼀台主机上标识不同进程的地址,IP地址和端⼝号合起来标识⽹络中唯⼀的进程。
虽然IP、ARP和RARP数据报都需要以太⽹驱动程序来封装成帧,但是从功能上划分,ARP和RARP属于链路层,IP属于⽹络层。虽然ICMP、IGMP、TCP、UDP的数据都需要IP协议来封装成数据报,但是从功能上划分,ICMP、IGMP与IP同属于⽹络层,TCP和UDP属于传输层。
协议格式
数据包封装
传输层及其以下的机制由内核提供,应⽤层由⽤户进程提供(后⾯将介绍如何使⽤socket API编写应⽤程序),应⽤程序对通讯数据的含义进⾏解释,⽽传输层及其以下处理通讯的细节,将数据从⼀台计算机通过⼀定的路径发送到另⼀台计算机。应⽤层数据通过协议栈发到⽹络上时,每层协议都要加上⼀个数据⾸部(header),称为封装(Encapsulation),如下图所示:
TCP/IP数据包封装
不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在⽹络层叫做数据报(datagram),在链路层叫做帧(frame)。数据封装成帧后发到传输介质上,到达⽬的主机后每层协议再剥掉相应的⾸部,最后将应⽤层数据交给应⽤程序处理。
以太网帧格式
以太⽹帧格式
其中的源地址和⽬的地址是指⽹卡的硬件地址(也叫MAC地址),⻓度是48位,是在⽹卡出⼚时固化的。可在shell中使⽤ifconfig命令查看,“HWaddr 00:15:F2:14:9E:3F”部分就是硬件地址。协议字段有三种值,分别对应IP、ARP、RARP。帧尾是CRC校验码。以太⽹帧中的数据⻓度规定最⼩46字节,最⼤1500字节,ARP和RARP数据包的⻓度不够46字节,要在后⾯补填充位。最⼤值1500称为以太⽹的最⼤传输单元(MTU),不同的⽹络类型有不同的MTU,如果⼀个数据包从以太⽹路由到拨号链路上,数据包⻓度⼤于拨号链路的MTU,则需要对数据包进⾏分⽚(fragmentation)。ifconfig命令输出中也有“MTU:1500”。注意,MTU这个概念指数据帧中有效载荷的最⼤⻓度,不包括帧头长度。
ARP数据报格式
在⽹络通讯时,源主机的应⽤程序知道⽬的主机的IP地址和端⼝号,却不知道⽬的主机的硬件地址,⽽数据包⾸先是被⽹卡接收到再去处理上层协议的,如果接收到的数据包的硬件地址与本机不符,则直接丢弃。因此在通讯前必须获得⽬的主机的硬件地址。ARP协议就起到这个作⽤。源主机发出ARP请求,询问“IP地址是192.168.0.1的主机的硬件地址是多少”,并将这个请求⼴播到本地⽹段(以太⽹帧⾸部的硬件地址填FF:FF:FF:FF:FF:FF表示⼴播),⽬的主机接收到⼴播的ARP请求,发现其中的IP地址与本机相符,则发送⼀个ARP应答数据包给源主机,将⾃⼰的硬件地址填写在应答包中。
每台主机都维护⼀个ARP缓存表,可以⽤arp-a命令查看。缓存表中的表项有过期时间(⼀般为20分钟),如果20分钟内没有再次使⽤某个表项,则该表项失效,下次还要发ARP请求来获得⽬的主机的硬件地址。想⼀想,为什么表项要有过期时间⽽不是⼀直有效?
ARP数据报格式
源MAC地址、⽬的MAC地址在以太⽹⾸部和ARP请求中各出现⼀次,对于链路层为以太⽹的情况是多余的,但如果链路层是其它类型的⽹络则有可能是必要的。硬件类型指链路层⽹络类型,1为以太⽹,协议类型指要转换的地址类型,0x0800为IP地址,后⾯两个地址⻓度对于以太⽹地址和IP地址分别为6和4(字节),op字段为1表示ARP请求,op字段为2表示ARP应答。
看⼀个具体的例⼦。
请求帧如下(为了清晰在每⾏的前⾯加了字节计数,每⾏16个字节):以太⽹⾸部(14字节)
0000: ff ff ff ff ff ff 00 05 5d 61 58 a8 08 06
ARP帧(28字节)
0000: 00 01
0010: 08 00 06 04 00 01 00 05 5d 61 58 a8 c0 a8 00 37
0020: 00 00 00 00 00 00 c0 a8 00 02
填充位(18字节)
0020: 00 77 31 d2 50 10
0030: fd 78 41 d3 00 00 00 00 00 00 00 00
以太⽹⾸部:⽬的主机采⽤⼴播地址,源主机的MAC地址是00:05:5d:61:58:a8,上层协议类型0x0806表示ARP。
ARP帧:硬件类型0x0001表示以太⽹,协议类型0x0800表示IP协议,硬件地址(MAC地址)⻓度为6,协议地址(IP地址)⻓度为4,op为0x0001表示请求⽬的主机的MAC地址,源主机MAC地址为00:05:5d:61:58:a8,源主机IP地址为c0 a8 00 37(192.168.0.55),⽬的主机MAC地址全0待填写,⽬的主机IP地址为c0 a8 00 02(192.168.0.2)。
由于以太⽹规定最⼩数据⻓度为46字节,ARP帧⻓度只有28字节,因此有18字节填充位,填充位的内容没有定义,与具体实现相关。
应答帧如下:
以太⽹⾸部
0000: 00 05 5d 61 58 a8 00 05 5d a1 b8 40 08 06
ARP帧
0000: 00 01
0010: 08 00 06 04 00 02 00 05 5d a1 b8 40 c0 a8 00 02
0020: 00 05 5d 61 58 a8 c0 a8 00 37
填充位
0020: 00 77 31 d2 50 10
0030: fd 78 41 d3 00 00 00 00 00 00 00 00
以太⽹⾸部:⽬的主机的MAC地址是00:05:5d:61:58:a8,源主机的MAC地址是
00:05:5d:a1:b8:40,上层协议类型0x0806表示ARP。
ARP帧:硬件类型0x0001表示以太⽹,协议类型0x0800表示IP协议,硬件地址(MAC地址)⻓度为6,协议地址(IP地址)⻓度为4,op为0x0002表示应答,源主机MAC地址为00:05:5d:a1:b8:40,源主机IP地址为c0 a8 00 02(192.168.0.2),⽬的主机MAC地址为00:05:5d:61:58:a8,⽬的主机IP地址为c0 a8 00 37(192.168.0.55)
IP段格式
IP数据报格式
IP数据报的⾸部⻓度和数据⻓度都是可变⻓的,但总是4字节的整数倍。
对于IPv4,4位版本字段是4。4位⾸部⻓度的数值是以4字节为单位的,最⼩值为5,也就是说⾸部⻓度最⼩是4x5=20字节,也就是不带任何选项的IP⾸部,4位能表示的最⼤值是15,也就是说⾸部⻓度最⼤是60字节。
8位TOS字段有3个位⽤来指定IP数据报的优先级(⽬前已经废弃不⽤),还有4个位表示可选的服务类型(最⼩延迟、最⼤吐量、最⼤可靠性、最⼩成本),还有⼀个位总是0。总⻓度是整个数据报(包括IP⾸部和IP层payload)的字节数。每传⼀个IP数据报,16位的标识加1,可⽤于分⽚和重新组装数据报。3位标志和13位⽚偏移⽤于分⽚。
TTL(Time to live)是这样⽤的:源主机为数据包设定⼀个⽣存时间,⽐如64,每过⼀个路由器就把该值减1,如果减到0就表示路由已经太⻓了仍然找不到⽬的主机的⽹络,就丢弃该包,因此这个⽣存时间的单位不是秒,⽽是跳(hop)。
协议字段指示上层协议是TCP、UDP、ICMP还是IGMP。然后是校验和,只校验IP⾸部,数据的校验由更⾼层协议负责。IPv4的IP地址⻓度为32位。
UDP数据报格式
UDP数据段
下⾯分析⼀帧基于UDP的TFTP协议帧。
以太⽹⾸部
0000: 00 05 5d 67 d0 b1 00 05 5d 61 58 a8 08 00
IP⾸部
0000: 45 00
0010: 00 53 93 25 00 00 80 11 25 ec c0 a8 00 37 c0 a8
0020: 00 01
UDP⾸部
0020: 05 d4 00 45 00 3f ac 40
TFTP协议
0020: 00 01 'c'':''\''q'
0030: 'w''e''r''q''.''q''w''e'00 'n''e''t''a''s''c''i'
0040: 'i'00 'b''l''k''s''i''z''e'00 '5''1''2'00 't''i'
0050: 'm''e''o''u''t'00 '1''0'00 't''s''i''z''e'00 '0'
以太⽹⾸部
:源MAC地址是00:05:5d:61:58:a8,⽬的MAC地址是00:05:5d:67:d0:b1,上层协议类型0x0800表示IP。
IP⾸部
:每⼀个字节0x45包含4位版本号和4位⾸部⻓度,版本号为4,即IPv4,⾸部⻓度为5,说明IP⾸部不带有选项字段。服务类型为0,没有使⽤服务。16位总⻓度字段(包括IP⾸部和IP层payload的⻓度)为0x0053,即83字节,加上以太⽹⾸部14字节可知整个帧⻓度是97字节。IP报标识是0x9325,标志字段和⽚偏移字段设置为0x0000,就是DF=0允许分⽚,MF=0此数据报没有更多分⽚,没有分⽚偏移。TTL是0x80,也就是128。上层协议0x11表示UDP协议。IP⾸部校验和为0x25ec,源主机IP是c0 a8 00 37(192.168.0.55),⽬的主机IP是c0 a8 00
01(192.168.0.1)。
UDP⾸部
:源端⼝号0x05d4(1492)是客户端的端⼝号,⽬的端⼝号0x0045(69)是TFTP服务的well-known端⼝号。UDP报⻓度为0x003f,即63字节,包括UDP⾸部和UDP层pay-load的⻓度。UDP⾸部和UDP层payload的校验和为0xac40。
TFTP
是基于⽂本的协议,各字段之间⽤字节0分隔,开头的00 01表示请求读取⼀个⽂件,接下来的各字段是:
c:\qwerq.qwe
netascii
blksize 512
timeout 10
tsize 0
⼀般的⽹络通信都是像TFTP协议这样,通信的双⽅分别是客户端和服务器,客户端主动发起请求(上⾯的例⼦就是客户端发起的请求帧),⽽服务器被动地等待、接收和应答请求。客户端的IP地址和端⼝号唯⼀标识了该主机上的TFTP客户端进程,服务器的IP地址和端⼝号唯⼀标识了该主机上的TFTP服务进程,由于客户端是主动发起请求的⼀⽅,它必须知道服务器的IP地址和TFTP服务进程的端⼝号,所以,⼀些常⻅的⽹络协议有默认的服务器端⼝,例如HTTP服务默认TCP协议的80端⼝,FTP服务默认TCP协议的21端⼝,TFTP服务默认UDP协议的69端⼝(如上例所示)。
在使⽤客户端程序时,必须指定服务器的主机名或IP地址,如果不明确指定端⼝号则采⽤默认端⼝,请读者查阅ftp、tftp等程序的manpage了解如何指定端⼝号。/etc/services中列出了所有well-known的服务端⼝和对应的传输层协议,这是由IANA(Internet Assigned NumbersAuthority)规定的,其中有些服务既可以⽤TCP也可以⽤UDP,为了清晰,IANA规定这样的服务采⽤相同的TCP或UDP默认端⼝号,⽽另外⼀些TCP和UDP的相同端⼝号却对应不同的服务。很多服务有well-known的端⼝号,然⽽客户端程序的端⼝号却不必是well-known的,往往是每次运⾏客户端程序时由系统⾃动分配⼀个空闲的端⼝号,⽤完就释放掉,称为ephemeral的端⼝号,想想这是为什么?
前⾯提过,UDP协议不⾯向连接,也不保证传输的可靠性,例如:
发送端的UDP协议层只管把应⽤层传来的数据封装成段交给IP协议层就算完成任务了,如果因为⽹络故障该段⽆法发到对⽅,UDP协议层也不会给应⽤层返回任何错误信息。
接收端的UDP协议层只管把收到的数据根据端⼝号交给相应的应⽤程序就算完成任务了,如果发送端发来多个数据包并且在⽹络上经过不同的路由,到达接收端时顺序已经错乱了,UDP协议层也不保证按发送时的顺序交给应⽤层。
通常接收端的UDP协议层将收到的数据放在⼀个固定⼤⼩的缓冲区中等待应⽤程序来提取和处理,如果应⽤程序提取和处理的速度很慢,⽽发送端发送的速度很快,就会丢失数据包,UDP协议层并不报告这种错误。
因此,使⽤UDP协议的应⽤程序必须考虑到这些可能的问题并实现适当的解决⽅案,例如等待应答、超时重发、为数据包编号、流量控制等。⼀般使⽤UDP协议的应⽤程序实现都⽐较简单,只是发送⼀些对可靠性要求不⾼的消息,⽽不发送⼤量的数据。例如,基于UDP的TFTP协议⼀般只⽤于传送⼩⽂件(所以才叫trivial的ftp),⽽基于TCP的FTP协议适⽤于 各种⽂件的传输。TCP协议⼜是如何⽤⾯向连接的服务来代替应⽤程序解决传输的可靠性问题呢。
TCP数据报格式
TCP数据段
与UDP协议⼀样也有源端⼝号和⽬的端⼝号,通讯的双⽅由IP地址和端⼝号标识。32位序号、32位确认序号、窗⼝⼤⼩稍后详细解释。4位⾸部⻓度和IP协议头类似,表示TCP协议头的⻓度,以4字节为单位,因此TCP协议头最⻓可以是4x15=60字节,如果没有选项字段,TCP协议头最短20字节。URG、ACK、PSH、RST、SYN、FIN是六个控制位,本节稍后将解释SYN、ACK、FIN、RST四个位,其它位的解释从略。16位检验和将TCP协议头和数据都计算在内。紧急指针和各种选项的解释从略。
TCP协议
TCP通信时序
下图是⼀次TCP通讯的时序图。TCP连接建⽴断开。包含三次握⼿和四次握⼿。
TCP通讯时序
在这个例⼦中,⾸先客户端主动发起连接、发送请求,然后服务器端响应请求,然后客户端主动关闭连接。两条竖线表示通讯的两端,从上到下表示时间的先后顺序,注意,数据从⼀端传到⽹络的另⼀端也需要时间,所以图中的箭头都是斜的。双⽅发送的段按时间顺序编号为1-10,各段中的主要信息在箭头上标出,例如段2的箭头上标着SYN, 8000(0), ACK1001, ,表示该段中的SYN位置1,32位序号是8000,该段不携带有效载荷(数据字节数为0),ACK位置1,32位确认序号是1001,带有⼀个mss(Maximum Segment Size,最⼤报⽂⻓度)选项值为1024。
建⽴连接(三次握⼿)的过程:
- 客户端发送⼀个带SYN标志的TCP报⽂到服务器。这是三次握⼿过程中的段1
客户端发出段1,SYN位表示连接请求。序号是1000,这个序号在⽹络通讯中⽤作临时的地址,每发⼀个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况,另外,规定SYN位和FIN位也要占⼀个序号,这次虽然没发数据,但是由于发了SYN位,因此下次再发送应该⽤序号1001。mss表示最⼤段尺⼨,如果⼀个段太⼤,封装成帧后超过了链路层的最⼤帧⻓度,就必须在IP层分⽚,为了避免这种情况,客户端声明⾃⼰的最⼤段尺⼨,建议服务器端发来的段不要超过这个⻓度。
- 服务器端回应客户端,是三次握⼿中的第2个报⽂段,同时带ACK标志和SYN标志。它表示对刚才客户端SYN的回应;同时⼜发送SYN给客户端,询问客户端是否准备好进⾏数据通讯。
服务器发出段2,也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出⼀个连接请求,同时声明最⼤尺⼨为1024。
- 客户必须再次回应服务器端⼀个ACK报⽂,这是报⽂段3。
客户端发出段3,对服务器的连接请求进⾏应答,确认序号是8001。在这个过程中,客户端和服务器分别给对⽅发了连接请求,也应答了对⽅的连接请求,其中服务器的请求和应答在⼀个段中
发出,因此⼀共有三个段⽤于建⽴连接,称为“三⽅握⼿(three-way-handshake)”。在建⽴连接的同时,双⽅协商了⼀些信息,例如双⽅发送序号的初始值、最⼤段尺⼨等。
在TCP通讯中,如果⼀⽅收到另⼀⽅发来的段,读出其中的⽬的端⼝号,发现本机并没有任何进程使⽤这个端⼝,就会应答⼀个包含RST位的段给另⼀⽅。例如,服务器并没有任何进程使⽤8080端⼝,我们却⽤telnet客户端去连接它,服务器收到客户端发来的SYN段就会应答⼀个RST段,客户端的telnet程序收到RST段后报告错误Connection refused:
$ telnet 192.168.0.200 8080
Trying 192.168.0.200...
telnet: Unable to connect to remote host: Connection refused
数据传输的过程:
- 客户端发出段4,包含从序号1001开始的20个字节数据。
- 服务器发出段5,确认序号为1021,对序号为1001-1020的数据表示确认收到,同时请求发送序号1021开始的数据,服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据,这称为piggyback。
- 客户端发出段6,对服务器发来的序号为8001-8010的数据表示确认收到,请求发送序号8011开始的数据。
在数据传输过程中,ACK和确认序号是⾮常重要的,应⽤程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据包给对⽅之后,只有收到对⽅应答的ACK段才知道该数据包确实发到了对⽅,可以从发送缓冲区中释放掉了,如果因为⽹络故障丢失了数据包或者丢失了对⽅发回的ACK段,经过等待超时后TCP协议⾃动将发送缓冲区中的数据包重发。
关闭连接(四次握⼿)的过程:
由于TCP连接是全双⼯的,因此每个⽅向都必须单独进⾏关闭。这原则是当⼀⽅完成它的数据发送任务后就能发送⼀个FIN来终⽌这个⽅向的连接。收到⼀个FIN只意味着这⼀⽅向上没有数据流动,⼀个TCP连接在收到⼀个FIN后仍能发送数据。⾸先进⾏关闭的⼀⽅将执⾏主动关闭,⽽另⼀⽅执⾏被动关闭。
- 客户端发出段7,FIN位表示关闭连接的请求。
- 服务器发出段8,应答客户端的关闭连接请求。
- 服务器发出段9,其中也包含FIN位,向客户端发送关闭连接请求。
- 客户端发出段10,应答服务器的关闭连接请求.
建⽴连接的过程是三⽅握⼿,⽽关闭连接通常需要4个段,服务器的应答和关闭连接请求通常不合并在⼀个段中,因为有连接半关闭的情况,这种情况下客户端关闭连接之后就不能再发送数据给服务器了,但是服务器还可以发送数据给客户端,直到服务器也关闭连接为⽌。
滑动窗⼝ (TCP流量控制)
介绍UDP时描述了这样的问题:如果发送端发送的速度较快,接收端接收到数据后处理的速度较慢,⽽接收缓冲区的⼤⼩是固定的,就会丢失数据。TCP协议通过“滑动窗⼝(SlidingWindow)”机制解决这⼀问题。看下图的通讯过程
滑动窗⼝
- 发送端发起连接,声明最⼤段尺⼨是1460,初始序号是0,窗⼝⼤⼩是4K,表示“我的接收缓冲区还有4K字节空闲,你发的数据不要超过4K”。接收端应答连接请求,声明最⼤段尺⼨是1024,初始序号是8000,窗⼝⼤⼩是6K。发送端应答,三⽅握⼿结束。
- 发送端发出段4-9,每个段带1K的数据,发送端根据窗⼝⼤⼩知道接收端的缓冲区满了,因此停⽌发送数据。
- 接收端的应⽤程序提⾛2K数据,接收缓冲区⼜有了2K空闲,接收端发出段10,在应答已收到6K数据的同时声明窗⼝⼤⼩为2K。
- 接收端的应⽤程序⼜提⾛2K数据,接收缓冲区有4K空闲,接收端发出段11,重新声明窗⼝⼤⼩为4K。
- 发送端发出段12-13,每个段带2K数据,段13同时还包含FIN位。
- 接收端应答接收到的2K数据(6145-8192),再加上FIN位占⼀个序号8193,因此应答序号是8194,连接处于半关闭状态,接收端同时声明窗⼝⼤⼩为2K。
- 接收端的应⽤程序提⾛2K数据,接收端重新声明窗⼝⼤⼩为4K。
- 接收端的应⽤程序提⾛剩下的2K数据,接收缓冲区全空,接收端重新声明窗⼝⼤⼩为6K。
- 接收端的应⽤程序在提⾛全部数据后,决定关闭连接,发出段17包含FIN位,发送端应答,连接完全关闭。
上图在接收端⽤⼩⽅块表示1K数据,实⼼的⼩⽅块表示已接收到的数据,虚线框表示接收缓冲区,因此套在虚线框中的空⼼⼩⽅块表示窗⼝⼤⼩,从图中可以看出,随着应⽤程序提⾛数据,虚线框是向右滑动的,因此称为滑动窗⼝。
从这个例⼦还可以看出,发送端是⼀K⼀K地发送数据,⽽接收端的应⽤程序可以两K两K地提⾛数据,当然也有可能⼀次提⾛3K或6K数据,或者⼀次只提⾛⼏个字节的数据。也就是说,应⽤程序所看到的数据是⼀个整体,或说是⼀个流(stream),在底层通讯中这些数据可能被拆成很多数据包来发送,但是⼀个数据包有多少字节对应⽤程序是不可⻅的,因此TCP协议是⾯向流的协议。⽽UDP是⾯向消息的协议,每个UDP段都是⼀条消息,应⽤程序必须以消息为单位提取数据,不能⼀次提取任意字节的数据,这⼀点和TCP是很不同的。
TCP状态转换
这个图N多⼈都知道,它排除和定位⽹络或系统故障时⼤有帮助,但是怎样牢牢地将这张图刻在脑中呢?那么你就⼀定要对这张图的每⼀个状态,及转换的过程有深刻的认识,不能只停留在⼀知半解之中。下⾯对这张图的11种状态详细解析⼀下,以便加强记忆!不过在这之前,先回顾⼀下TCP建⽴连接的三次握⼿过程,以及 关闭连接的四次握⼿过程。
CLOSED
:表示初始状态。
LISTEN
:该状态表示服务器端的某个SOCKET处于监听状态,可以接受连接。
SYN_SENT
:这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执⾏CONNECT连接时,它⾸先发送SYN报⽂,随即进⼊到了SYN_SENT状态,并等待服务端的发送三次握⼿中的第2个报⽂。SYN_SENT状态表示客户端已发送SYN报⽂。
SYN_RCVD
: 该状态表示接收到SYN报⽂,在正常情况下,这个状态是服务器端的SOCKET在建⽴TCP连接时的三次握⼿会话过程中的⼀个中间状态,很短暂。此种状态时,当收到客户端的ACK报⽂后,会进⼊到ESTABLISHED状态。
ESTABLISHED
:表示连接已经建⽴。
FIN_WAIT_1
: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对⽅的FIN报⽂。区别是:FIN_WAIT_1状态是当socket在ESTABLISHED状态时,想主动关闭连接,向对⽅发送了FIN报⽂,此时该socket进⼊到FIN_WAIT_1状态。
FIN_WAIT_2状态是当对⽅回应ACK后,该socket进⼊到FIN_WAIT_2状态,正常情况下,对⽅应⻢上回应ACK报⽂,所以FIN_WAIT_1状态⼀般较难⻅到,⽽FIN_WAIT_2状态可⽤netstat看到。
FIN_WAIT_2
:主动关闭链接的⼀⽅,发出FIN收到ACK以后进⼊该状态。称之为半连接或半关闭状态。该状态下的socket只能接收数据,不能发。
TIME_WAIT
: 表示收到了对⽅的FIN报⽂,并发送出了ACK报⽂,等2MSL后即可回到CLOSED可⽤状态。如果FIN_WAIT_1状态下,收到对⽅同时带FIN标志和ACK标志的报⽂时,可以直接进⼊到TIME_WAIT状态,⽽⽆须经过FIN_WAIT_2状态。
CLOSING
: 这种状态较特殊,属于⼀种较罕⻅的状态。正常情况下,当你发送FIN报⽂后,按理来说是应该先收到(或同时收到)对⽅的ACK报⽂,再收到对⽅的FIN报⽂。但是CLOSING状态表示你发送FIN报⽂后,并没有收到对⽅的ACK报⽂,反⽽却也收到了对⽅的FIN报⽂。什么情况下会出现此种情况呢?如果双⽅⼏乎在同时close⼀个SOCKET的话,那么就出现了双⽅同时发送FIN报⽂的情况,也即会出现CLOSING状态,表示双⽅都正在关闭SOCKET连接。
CLOSE_WAIT
: 此种状态表示在等待关闭。当对⽅关闭⼀个SOCKET后发送FIN报⽂给⾃⼰,系统会回应⼀个ACK报⽂给对⽅,此时则进⼊到CLOSE_WAIT状态。接下来呢,察看是否还有数据发送给对⽅,如果没有可以close这个SOCKET,发送FIN报⽂给对⽅,即关闭连接。所以在CLOSE_WAIT状态下,需要关闭连接。
LAST_ACK
: 该状态是被动关闭⼀⽅在发送FIN报⽂后,最后等待对⽅的ACK报⽂。当收到ACK报⽂后,即可以进⼊到CLOSED可⽤状态。
半关闭
当TCP链接中A发送FIN请求关闭,B端回应ACK后(A端进⼊FIN_WAIT_2状态),B没有⽴即发送FIN给A时,A⽅处在半链接状态,此时A可以接收B发送的数据,但是A已不能再向B发送数据。
2MSL
2MSL (Maximum Segment Lifetime) TIME_WAIT状态的存在有两个理由:
- 让4次握⼿关闭流程更加可靠;4次握⼿的最后⼀个ACK是是由主动关闭⽅发送出去的,若这个ACK丢失,被动关闭⽅会再次发⼀个FIN过来。若主动关闭⽅能够保持⼀个2MSL的TIME_WAIT状态,则有更⼤的机会让丢失的ACK被再次发送出去。
- 防⽌lost duplicate对后续新建正常链接的传输造成破坏。lost uplicate在实际的⽹络中⾮常常⻅,经常是由于路由器产⽣故障,路径⽆法收敛,导致⼀个packet在路由器A,B,C之间做类似死循环的跳转。IP头部有个TTL,限制了⼀个包在⽹络中的最⼤跳数,因此这个包有两种命运,要么最后TTL变为0,在⽹络中消失;要么TTL在变为0之前路由器路径收敛,它凭借剩余的TTL跳数终于到达⽬的地。但⾮常可惜的是TCP通过超时重传机制在早些时候发送了⼀个跟它⼀模⼀样的
包,并先于它达到了⽬的地,因此它的命运也就注定被TCP协议栈抛弃。
另外⼀个概念叫做incarnation connection,指跟上次的socket pair⼀摸⼀样的新连接,叫做incarnation of previous connection。lost uplicate加上incarnation connection,则会对我们的
传输造成致命的错误。
TCP是流式的,所有包到达的顺序是不⼀致的,依靠序列号由TCP协议栈做顺序的拼接;假设⼀个incarnation connection这时收到的seq=1000, 来了⼀个lost duplicate为seq=1000,len=1000,则TCP认为这个lost duplicate合法,并存放⼊了receive buffer,导致传输出现错误。通过⼀个
2MSL TIME_WAIT状态,确保所有的lost duplicate都会消失掉,避免对新连接造成错误。
该状态为什么设计在主动关闭这⼀⽅:
(1)发最后ACK的是主动关闭⼀⽅。
(2)只要有⼀⽅保持TIME_WAIT状态,就能起到避免incarnation connection在2MSL内的重新建⽴,不需要两⽅都有。
如何正确对待2MSL TIME_WAIT?RFC要求socket pair在处于TIME_WAIT时,不能再起⼀个incarnation connection。但绝⼤部分
TCP实现,强加了更为严格的限制。在2MSL等待期间,socket中使⽤的本地端⼝在默认情况下不能再被使⽤。
若A 10.234.5.5 : 1234和B 10.55.55.60 : 6666建⽴了连接,A主动关闭,那么在A端只要port为
1234,⽆论对⽅的port和ip是什么,都不允许再起服务。这甚⾄⽐RFC限制更为严格,RFC仅仅是要求socket pair不⼀致,⽽实现当中只要这个port处于TIME_WAIT,就不允许起连接。这个限制
对主动打开⽅来说是⽆所谓的,因为⼀般⽤的是临时端⼝;但对于被动打开⽅,⼀般是server,就悲剧了,因为server⼀般是熟知端⼝。⽐如http,⼀般端⼝是80,不可能允许这个服务在2MSL内不能起来。
解决⽅案是给服务器的socket设置SO_REUSEADDR选项,这样的话就算熟知端⼝处于TIME_WAIT状态,在这个端⼝上依旧可以将服务启动。当然,虽然有了SO_REUSEADDR选项,但sockt pair这个限制依旧存在。⽐如上⾯的例⼦,A通过SO_REUSEADDR选项依旧在1234端⼝上起了监听,但这时我们若是从B通过6666端⼝去连它,TCP协议会告诉我们连接失败,原因为Address already in use.
RFC 793中规定MSL为2分钟,实际应⽤中常⽤的是30秒,1分钟和2分钟等。
RFC (Request For Comments),是⼀系列以编号排定的⽂件。收集了有关因特⽹相关资讯,以及UNIX和因特⽹社群的软件⽂件。
粘包拆包发生的原因
产生原因主要有这3种:滑动窗口、MSS/MTU限制、Nagle算法
- 滑动窗口
TCP流量控制主要使用滑动窗口协议,滑动窗口是接受数据端使用的窗口大小,用来告诉发送端接收端的缓存大小,以此可以控制发送端发送数据的大小,从而达到流量
控制的目的。这个窗口大小就是我们一次传输几个数据。对所有数据帧按顺序赋予编号,发送方在发送过程中始终保持着一个发送窗口,只有落在发送窗口内的帧才允许被发送;
同时接收方也维持着一个接收窗口,只有落在接收窗口内的帧才允许接收。这样通过调整发送方窗口和接收方窗口的大小可以实现流量控制。
现在来看一下滑动窗口是如何造成粘包、拆包的?
粘包:假设发送方的每256 bytes表示一个完整的报文,接收方由于数据处理不及时,这256个字节的数据都会被缓存到SO_RCVBUF(接收缓存区)中。如果接收方的SO_RCVBUF中缓存了多个报文,那么对于接收方而言,这就是粘包。
拆包:考虑另外一种情况,假设接收方的窗口只剩了128,意味着发送方最多还可以发送128字节,而由于发送方的数据大小是256字节,因此只能发送前128字节,等到接收方ack后,才能发送剩余字节。这就造成了拆包。
- MSS和MTU分片
MSS: 是Maximum Segement Size缩写,表示TCP报文中data部分的最大长度,是TCP协议在OSI五层网络模型中传输层对一次可以发送的最大数据的限制。
MTU: 最大传输单元是Maxitum Transmission Unit的简写,是OSI五层网络模型中链路层(datalink layer)对一次可以发送的最大数据的限制。
当需要传输的数据大于MSS或者MTU时,数据会被拆分成多个包进行传输。由于MSS是根据MTU计算出来的,因此当发送的数据满足MSS时,必然满足MTU。
MTU是以太网传输数据方面的限制,每个以太网帧都有最小的大小64bytes最大不能超过1518bytes。刨去以太网帧的帧头 (DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和帧尾 CRC校验部分4Bytes(这个部分有时候大家也把它叫做FCS),那么剩下承载上层协议的地方也就是Data域最大就只能有1500Bytes这个值 我们就把它称之为MTU。
由于MTU限制了一次最多可以发送1500个字节,而TCP协议在发送DATA时,还会加上额外的TCP Header和Ip Header,因此刨去这两个部分,就是TCP协议一次可以发送的实际应用数据的最大大小,也就是MSS。
MSS长度=MTU长度-IP Header-TCP Header
TCP Header的长度是20字节,IPv4中IP Header长度是20字节,IPV6中IP Header长度是40字节,因此:在IPV4中,以太网MSS可以达到1460byte;在IPV6中,以太网MSS可以达到1440byte。
需要注意的是MSS表示的一次可以发送的DATA的最大长度,而不是DATA的真实长度。发送方发送数据时,当SO_SNDBUF中的数据量大于MSS时,操作系统会将数据进行拆分,使得每一部分都小于MSS,这就是拆包,然后每一部分都加上TCP Header,构成多个完整的TCP报文进行发送,当然经过网络层和数据链路层的时候,还会分别加上相应的内容。
需要注意: 默认情况下,与外部通信的网卡的MTU大小是1500个字节。而本地回环地址的MTU大小为65535,这是因为本地测试时数据不需要走网卡,所以不受到1500的限制。
3、 Nagle算法
TCP/IP协议中,无论发送多少数据,总是要在数据(DATA)前面加上协议头(TCP Header+IP Header),同时,对方接收到数据,也需要发送ACK表示确认。
即使从键盘输入的一个字符,占用一个字节,可能在传输上造成41字节的包,其中包括1字节的有用信息和40字节的首部数据。这种情况转变成了4000%的消耗,这样的情况对于重负载的网络来是无法接受的。
为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。
Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。 所谓“小段”,指的是小于MSS尺寸的数据块,所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。
Nagle算法的规则:
1)如果SO_SNDBUF(发送缓冲区)中的数据长度达到MSS,则允许发送;
2)如果该SO_SNDBUF中含有FIN,表示请求关闭连接,则先将SO_SNDBUF中的剩余数据发送,再关闭;
3)设置了TCP_NODELAY=true选项,则允许发送。TCP_NODELAY是取消TCP的确认延迟机制,相当于禁用了Nagle 算法。
4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
总结:UDP无粘包拆迁现象,最多会出现丢包问题,因为是不可靠通信方式; Nagle的算法通常会在TCP程序里添加两行代码,在未确认数据发送的时候让发送器把数据送到缓存里。任何数据随后继续直到得到明显的数据确认或者直到攒到了一定数量的数据了再发包。降低网络负载