网络层是为主机之间提供逻辑通信;传输层为应用进程之间提供端到端的逻辑通信。
逻辑通信”的意思是“好像是这样通信,但事实上并非真的这样通信”。从IP层来说,通信的两端是两台主机。但“两台主机之间的通信”这种说法还不够清楚。严格地讲,两台主机进行通信就是两台主机中的应用进程互相通信。从运输层的角度看,通信的真正端点并不是主机而是主机中的进程。也就是说,端到端的通信是应用进程之间的通信。
即“主机 A 的某个进程和主机 B 上的另一个进程进行通信”。简称为“计算机之间通信”。
传输层有两个主要协议:
- TCP(Transmission Control Protocol),传输控制协议
- UDP(User Datagram Protocol),用户数据报协议
TCP 传送的数据单位协议是 TCP 报文段(segment)。
UDP 传送的数据单位协议是 UDP 报文或用户数据报。
第一章 UDP协议
UDP 只在 IP 的数据报服务之上增加了很少一点的功能:
- 复用和分用的功能
- 差错检测的功能
1.1 UDP特点
UDP 是无连接的,发送数据之前不需要建立连接,因此减少了开销和发送数据之前的时延。
UDP 使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表。
UDP 是面向报文的。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。UDP 一次交付一个完整的报文。
UDP 没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。这对某些实时应用是很重要的。很适合多媒体通信的要求。
UDP 支持一对一、一对多、多对一和多对多的交互通信。
UDP 的首部开销小,只有 8 个字节,比 TCP 的 20 个字节的首部要短。
1.2 UDP数据格式
UDP长度(Length)占16位:首部的长度 + 数据的长度
UDP检验和(Checksum)
检验和的计算内容:伪首部 + 首部 + 数据
伪首部:仅在计算检验和时起作用,并不会传递给网络层
UDP端口(Port)
UDP首部中端口是占用2字节
可以推测出端口号的取值范围是:0~65535
客户端的源端口是临时开启的随机端口
防火墙可以设置开启\关闭某些端口来提高安全性
常用命令:
- netstat –an:查看被占用的端口
- netstat –anb:查看被占用的端口、占用端口的应用程序
- telnet 主机 端口:查看是否可以访问主机的某个端口
- 安装telnet:控制面板 – 程序 – 启用或关闭Windows功能 – 勾选“Telnet Client” – 确定
第二章 TCP协议
TCP 是面向连接的运输层协议,在无连接的、不可靠的 IP 网络服务基础之上提供可靠交付的服务。为此,在 IP 的数据报服务基础之上,增加了保证可靠性的一系列措施。
2.1 TCP特点
TCP 是面向连接的运输层协议。
每一条 TCP 连接只能有两个端点 (endpoint),每一条 TCP 连接只能是点对点的(一对一)。
TCP 提供可靠交付的服务。
TCP 提供全双工通信。
面向字节流
- TCP 中的“流”(stream) 指的是流入或流出进程的字节序列。
- “面向字节流”的含义是:虽然应用程序和 TCP 的交互是一次一个数据块,但 TCP 把应用程序交下来的数据看成仅仅是一连串无结构的字节流。
TCP的几个要点:
- 可靠传输
- 流量控制
- 拥塞控制
- 连接管理(建立连接、释放连接)
2.2 TCP数据格式
数据偏移
- 占4位,取值范围是二进制 0b0101 ~ 0b1111(5~15)
- 数据偏移 * 4 = 首部长度(Header Length)
- 首部长度是 20 ~ 60 字节
保留字段
- 占6位,目前全为0
- TCP 关于保留字段的细节:有些资料中,TCP首部的 保留(Reserved)字段 占3位,标志(Flags) 字段占9位(Wireshark中也是如此)
UDP的首部 中有个两个字节(16位)的字段记录了整个UDP报文段的长度(首部+数据)。
但是,TCP的首部 中仅仅有个 4 位(数据偏移)的字段记录了 TCP报文段的首部长度,并没有字段记录TCP报文段的数据长度。
- UDP首部中占16位的长度字段是冗余的,纯粹是为了保证首部是32位对齐
- TCP\UDP的数据长度,完全可以由IP数据包(网络层)的首部推测出来
- 传输层的数据长度 = 网络层的总长度 - 网络层的首部长度 - 传输层的首部长度(首部长度固定)
检验和( CheckSum)
跟UDP一样,TCP检验和的计算内容:伪首部 + 首部 + 数据
伪首部:占用12字节,仅在计算检验和时起作用,并不会传递给网络层
标志位(Flags)URG、ACK、PSH、RST、SYN、FIN
URG(Urgent)
- 当 URG = 1 时,紧急指针字段才有效。表明当前报文段中有紧急数据,应优先尽快传送
ACK(Acknowledgment)
- 当 ACK = 1 时,确认号字段才有效
- 当 ACK =0 时,确认号无效。
PSH(Push)
- 接收 TCP 收到 PSH = 1 的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。
RST(Reset)
- 当 RST = 1 时,表明连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立连接
SYN(Synchronization)
- 当 SYN = 1、ACK = 0 时,表明这是一个建立连接的请求
- 若对方同意建立连接,则回复 SYN = 1、ACK = 1
FIN(Finish)
- 当 FIN = 1 时,表明数据已经发送完毕,要求释放连接
序号、确认号、窗口
序号(Sequence Number)
- 占4字节
- 首先,在传输过程的每一个字节都会有一个编号
- 在建立连接后,序号代表:这一次传给对方的TCP数据部分的第一个字节的编号
确认号(Acknowledgment Number)
- 占4字节
- 在建立连接后,确认号代表:期望对方下一次传过来的TCP数据部分的第一个字节的编号(客户端浏览器希望服务器)
窗口(Window)
- 占2字节
- 这个字段有流量控制功能,用以告知对方下一次允许发送的数据大小(字节为单位)
2.3 可靠传输
ARQ(Automatic Repeat–reQuest)协议,自动重传请求
自动重传请求(Automatic Repeat-reQuest,ARQ)是OSI模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认帧,它通常会重新发送。ARQ包括停止等待ARQ协议和连续ARQ协议。
停止等待ARQ协议
停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功(停止等待),需要重新发送(超时重传),直到收到确认后再发下一个分组;
在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认;
确认丢失
若 B 所发送的对 M1 的确认丢失了,那么 A 在设定的超时重传时间内不能收到确认,但 A 并无法知道:是自己发送的分组出错、丢失了,或者 是 B 发送的确认丢失了。因此 A 在超时计时器到期后就要重传 M1。
假定 B 又收到了重传的分组 M1。这时 B 应采取两个行动:
- 丢弃这个重复的分组 M1,不向上层交付。
- 向 A 发送确认。不能认为已经发送过确认就不再发送,因为 A 之所以重传 M1 就表示 A 没有收到对 M1 的确认。
确认迟到
传输过程中没有出现差错,但 B 对分组 M1 的确认迟到了。
A 会收到重复的确认。对重复的确认的处理很简单:收下后就丢弃。
B 仍然会收到重复的 M1,并且同样要丢弃重复的 M1,并重传确认分组。
停止等待ARQ的太低,可以使用连续ARQ+滑动窗口协议进行改进。
连续ARQ协议+滑动窗口协议
基本思想:
- 发送方一次可以发出多个分组。
- 使用滑动窗口协议控制发送方和接收方所能发送和接收的分组的数量和编号。
- 每收到一个确认,发送方就把发送窗口向前滑动。
- 接收方一般采用累积确认的方式。
- 采用回退N(Go-Back-N)方法进行重传。
下图:A为发送方,B为接受方
现在假设每一组数据是100个字节,代表一个数据段的数据,每一组给一个编号
SACK 选择性确认
在TCP通信过程中,如果发送序列中间某个数据包丢失(比如1、2、3、4、5中3丢失了)
TCP会通过重传最后确认的分组后续的分组(最后确认的是2,会重传3、4、5)
这样原先已经正确传输的分组也可能重复发送(比如4、5),降低了TCP性能
为改善上述情况,发展出了 SACK(Selective acknowledgment,选择性确认)技术
- 告诉发送方哪些数据丢失,哪些数据已经提前收到
- 使TCP只重新发送丢失的包(比如3),不用发送后续所有的分组(比如4、5)
SACK信息会放在TCP首部的选项部分
- Kind:占1字节。值为5代表这是SACK选项
- Length:占1字节。表明SACK选项一共占用多少字节
- Left Edge:占4字节,左边界
- Right Edge:占4字节,右边界
一对边界信息需要占用8字节,由于TCP首部的选项部分最多40字节,所以
- SACK选项最多携带4组边界信息
- SACK选项的最大占用字节数 = 4 * 8 + 2 (加上kind和length)= 34 < 40 符合
上图的意思,发送方 给 接收方 发了1 - 1000 的10个包,其中棕色的包代表传过去了,白色的包代表丢失了,此时开启了SACK选项的话,接收方 给 发送方确认收到M2包, TCP首部中可选部分会记录,收到了
Letf Edge = 301,Right Edge = 401,
Letf Edge = 501,Right Edge = 601,
Letf Edge = 701,Right Edge = 801,
Letf Edge = 901,Right Edge = 1001,
发送方收到之后就知道M3,M5,M7,M9包修饰,就会重传这几个包
思考题:
1、若有个包重传了N次还是失败,会一直持续重传到成功为止么?
答:这个取决于系统设置,有的系统设置重传5次还未成功就会发生eset报文(RST)断开连接
2、为什么选择在传输层将数据分成多段,而不是网络层或者数据链路层?
答:因为可以提高重传性能。
可靠传输是在传输层进行控制的,网络层和数据链路层不保证可靠传输。
如果在传输层不分段,若丢包,整个传输层的数据都需要重传
如果在传输层分段, 若丢包,只重传丢包的一段数据即可
3、如果接收窗口最多能接收4个包,但发送方只发了2个包,接收方如何确定后面还有没有2个包?
答:等待一定时间后没有第3个包,就会返回确认收到2个包给发送方
2.4 流量控制
流量控制是点对点、端对端,两台设备之间的。
一般说来,我们总是希望数据传输得更快一些。但如果发送方把数据发送得过快,接收方就可能来不及接收,这就会造成数据的丢失。
- 接收方只能把收到的数据包丢掉,大量的丢包会极大着浪费网络资源
- 所以要进行流量控制
什么是流量控制?
流量控制 (flow control) 就是让发送方的发送速率不要太快,既要让接收方来得及接收,也不要使网络发生拥塞。
原理
利用滑动窗口机制。
- 通过确认报文中窗口字段来控制发送方的发送速率
- 发送方的发送窗口大小不能超过接收方给出窗口大小
- 当发送方收到接收窗口的大小为0时,发送方就会停止发送数据
rwind = receive window = 接收窗口
有一种特殊情况 死锁
B (接收方)向 A(发送方)发送了零窗口的报文段后不久,B 的接收缓存又有了一些存储空间。于是 B 向 A 发送了 rwnd = 400 的报文段。
但这个报文段在传送过程中丢失了。A 一直等待收到 B 发送的非零窗口的通知,而 B 也一直等待 A 发送的数据。
如果没有其他措施,这种互相等待的死锁局面将一直延续下去。
为了解决这个问题,TCP 为每一个连接设有一个持续计时器 (persistence timer)。
解决方案:
为了解决这个问题, TCP 为每一个连接设有一个持续计时器 (persistence timer) 。只要 TCP 连接的一方收到对方的零窗口通知,就启动该持续计时器。
- 若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带 1 字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。
- 若窗口仍然是零,则收到这个报文段的一方就重新设置持续计时器。
- 若窗口不是零,则死锁的僵局就可以打破了。
2.5 拥塞控制
详细内容参见:https://www.cnblogs.com/wkfvawl/p/12813103.html
在某段时间,若对网络中某资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种现象称为拥塞 (congestion)。图中两个链路汇合到R3前 700 + 600M 大于1000M的带宽。
最坏结果:系统崩溃。
拥塞控制
- 防止过多的数据注入到网络中
- 避免网络中的路由器或链路过载
拥塞控制是一个全局性的过程,产生原因
- 点缓存的容量太小;
- 链路的容量不足;
- 处理机处理的速率太慢;
- 拥塞本身会进一步加剧拥塞;
根本是: ∑ 对资源需求 > 可用资源 网络能够承受现有的网络负荷。
- 涉及到所有的主机、路由器 (动态问题)
- 以及与降低网络传输性能有关的所有因素
- 是大家共同努力的结果
相比而言,流量控制是点对点通信的控制
拥塞控制方法
- 慢开始(slow start,慢启动)
- 拥塞避免(congestion avoidance)
- 快速重传(fast retransmit)
- 快速恢复(fast recovery)
几个概念
- MSS(Maximum Segment Size):
每个段最大的数据部分大小(在建立连接时确定)
一般是 MTU(最大传输单元 数据链路层1500) - 20 (网络层首部)- 20(传输层TCP首部 也有可能是32) = 1460
实际大小,需要发送方和接收方双方协商。
- cwnd(congestion window):拥塞窗口
- rwnd(receive window):接收窗口
- swnd(send window):
发送窗口 swnd = min(cwnd, rwnd)
- 当rwnd < cwnd时,是接收方的接收能力限制发送窗口的最大值
- 当cwnd < rwnd时,则是网络的拥塞限制发送窗口的最大值
慢开始
目的:用来确定网络的负载能力或拥塞程度。
算法的思路:由小到大逐渐增大拥塞窗口数值。
接收窗口(cwnd)的初始值比较小, 然后随着数据包被接收方确认(收到一个ACK), cwnd就成倍增长(指数级)
拥塞避免
- ssthresh(slow start threshold): 慢开始阈值, cwnd达到阈值后, 以线性方式增加
- 拥塞避免(加法增大):拥塞窗口缓慢增大, 以防止网络过早出现拥塞
- 乘法减小:只要网络出现拥塞, 把ssthresh减为拥塞峰值的一半, 同时执行慢开始算法(cwnd又恢复到初始值)
- 当网络出现频繁拥塞时, ssthresh值就下降的很快
快速重传
接收方: 每收到一个失序的分组后就立即发出重复确认, 使发送方及时知道有分组没有到达, 而不要等待自己发送数据时才进行确认
发送方: 只要连续收到三个重复确认(总共4个相同的确认),就应当立即重传对方尚未收到的报文段, 而不必继续等待重传计时器到期后再重传
快速恢复
- 当发送发连续接收到三个确认时,就执行乘法减小算法,把慢启动开始门限(ssthresh)减半,但是接下来并不执行慢开始算法。
- 此时不执行慢启动算法,而是把cwnd设置为ssthresh的一半, 然后执行拥塞避免算法,使拥塞窗口缓慢增大
2.6 序列号、确认号
先明确几个概念:
序列号(sequence number):seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
确认号(acknowledgement number):ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1。
序列号是想告诉对方现在数据包发送到哪里了。
确认号是告诉对方我已经收到多少了,请接下了发送多少
标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:
- URG:紧急指针(urgent pointer)有效。
- ACK:确认序号有效。(为了与确认号ack区分开,我们用大写表示)
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:发起一个新连接。(synchronous建立联机)
- FIN:释放一个连接。
客户端与服务器从建立连接到发送数据的完整流程如下图
下面的说明解释都以相对为例
第一步建立连接,客户端向服务器发送SYN
,数据部分为0
,建立连接是没有数据部分,只有头部信息。
发送方:seq = 0
,len = 0
第二步,接收方:ack = 0 + 1
第三步,发送方为了响应上一次的ack
,客户端给回服务器的响应中,seq
为1
。发送方期望下一次的响应从1
开始,所以ack = 1
第三步和第四步都是客户端向服务器发送信息,所以seq
和ack
相同,不同的是第四步数据部分不为0
第五步到第八步皆为服务端向客户端发送信息,所以ack皆为上一次接收到的数据长度k + 1。
不同之处在于每次请求的seq为上一次的长度加上本次数据长度。
第N个包的序号 = 前面N-1个包的总长度 + 1
第九步客户端的ack
为前几次服务器数据之和 + 1
,而seq
为上一次发送数据长度 + 1
总结:
2.7 建立连接
三次握手
看过上面的序列号和确认号的知识后,建立连接的三次握手机制便很好理解了。
- CLOSED:client处于关闭状态
- LISTEN:server处于监听状态,等待client连接
- SYN-RCVD:表示server接受到了SYN报文,当收到client的ACK报文后,它会进入到 ESTABLISHED 状态
- SYN-SENT:表示client已发送SYN报文,等待server的第2次握手
- ESTABLISHED:表示连接已经建立
状态变化:
- 客户端和服务器同时属于closed状态,表示没有连接关系。
- 客户端发送请求,客户端打开发送(SYN-sent)状态,同时服务器打开监听(Listen)状态;
- 服务器在接收到客户端的请求时,服务器切换为回复(SYN-recvd)状态;
- 客户端在接收到服务器的响应时,客户端切换为稳定连接(Estab-lished)状态的同时发送第二次数据包。
- 服务器在接收到客户端的第二次数据时,服务器切换为稳定连接(Estab-lished)状态。
- 双方建立稳定连接后,开始正常通信数据。
前2次握手的特点?
SYN 都设置为1
数据部分的长度都为0
TCP头部的长度一般是32字节(TCP头部至少是20字节,还有40字节是可以变化的)
- 固定头部:20字节
- 选项部分:12字节
双方会交换确认一些信息
- 比如MSS、是否支持SACK(选择性确认)、Window scale(窗口缩放系数) 等
- 这些数据都放在了TCP头部的选项部分中(12字节)
为什么建立连接的时候,要进行3次握手?2次不行么?
主要目的:防止server端一直等待,浪费资源
如果建立连接只需要2次握手,可能会出现的情况:
假设client发出的第一个连接请求报文段,因为网络延迟,在连接释放以后的某个时间才到达server,本来这是一个早已失效的连接请求,但server收到此失效的请求后,误认为是client再次发出的一个新的连接请求,于是server就向client发出确认报文段,同意建立连接。如果不采用“3次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有真正想连接服务器的意愿,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的连接已经建立,并一直等待client发来数据,这样,server的很多资源就白白浪费掉了
采用 “三次握手” 的办法可以防止上述现象发生
例如上述情况,client没有向【server的确认】发出确认,server由于收不到确认,就知道client并没有要求建立连接
如果第3次握手失败了,会怎么处理?
- 此时server的状态为 SYN-RCVD,若等不到client的 ACK,server会重新发送 SYN+ACK 包
- 如果server多次重发 SYN+ACK 都等不到client的 ACK,就会发送 RST包,强制关闭连接
2.8 释放连接
释放连接是在建立连接的基础上,由客户端发起的连接释放,ACK为1,因为在前面客户端与服务器有进行过数据的转发
四次挥手
为什么释放连接的时候,要进行4次挥手?
TCP是全双工模式,通信是双方的
第1次挥手:当主机1发出FIN报文段时
- 表示主机1告诉主机2,主机1已经没有数据要发送了,但是,此时主机1还是可以接受来自主机2的数据
第2次挥手:当主机2返回ACK报文段时
- 表示主机2已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的
第3次挥手:当主机2也发送了FIN报文段时
- 表示主机2告诉主机1,主机2已经没有数据要发送了
第4次挥手:当主机1返回ACK报文段时
- 表示主机1已经知道主机2没有数据发送了。随后正式断开整个TCP连接
两边通道全关掉,才会进入CLOSED状态
前两次是客户端要求结束关系,但是这不影响服务器后面发送数据给客户端,客户端进行确认
后两次是服务器要求结束关系,这次就是真的结束关系了
状态解读
FIN-WAIT-1
:表示想主动关闭连接- 向对方发送了
FIN
报文,此时进入到FIN-WAIT-1
状态
- 向对方发送了
CLOSE-WAIT
:表示在等待关闭- 当对方发送
FIN
给自己,自己会回应一个ACK
报文给对方,此时则进入到CLOSE-WAIT
状态 - 在此状态下,需要考虑自己是否还有数据要发送给对方,如果没有,发送
FIN
报文给对方
- 当对方发送
FIN-WAIT-2
:只要对方发送ACK
确认后,主动方就会处于FIN-WAIT-2
状态,然后等待对方发送FIN
报文CLOSING
:一种比较罕见的例外状态- 表示你发送
FIN
报文后,并没有收到对方的ACK
报文,反而却也收到了对方的FIN
报文 - 如果双方几乎在同时准备关闭连接的话,那么就出现了双方同时发送
FIN
报文的情况,也即会出现CLOSING
状态 - 表示双方都正在关闭连接
- 表示你发送
LAST-ACK
:被动关闭的一方在发送FIN
报文之后,最后等待对方的ACK
报文- 当收到
ACK
报文后,即可进入CLOSED
状态了
- 当收到
TIME-WAIT
:表示收到了对方的FIN
报文, 并发送出了ACK
报文,就等2MSL
后即可进入CLOSED
状态了- 如果
FIN-WAIT-1
状态下,收到了对方同时带FIN
标志和ACK
标志的报文时- 可以直接进入到
TIME-WAIT
状态,而无须经过FIN-WAIT-2
状态
- 可以直接进入到
- 如果
CLOSED
: 关闭状态- 由于有些状态的时间比较短暂, 所以很难用
netstat
命令看到, 比如SYN-RCVD
、FIN-WAIT-1
等
细节
◼ TCP/IP协议栈在设计上,允许任何一方先发起断开请求。这里演示的是client主动要求断开
◼ client发送ACK后,需要有个TIME-WAIT阶段,等待一段时间后,再真正关闭连接
一般是等待2倍的MSL(Maximum Segment Lifetime,最大分段生存期)
✓ MSL是TCP报文在Internet上的最长生存时间
✓ 每个具体的TCP实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟
为什么客户端最后还要等待2MSL?
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
◼ 如果client发送ACK后马上释放了,然后又因为网络原因,server没有收到client的ACK,server按照传统就会重发FIN
这时可能出现的情况是
① client没有任何响应,服务器那边会干等,甚至多次重发FIN,浪费资源
② client有个新的应用程序刚好分配了同一个端口号,新的应用程序收到FIN后马上开始执行断开连接的操作,本来它可能是想跟server建立连接的
抓包
有时候在使用抓包工具的时候,有可能只会看到 “3次“挥手
这其实是将第2、3次挥手合并了
当server接收到client的FIN时,如果server后面也没有数据要发送给client了
这时,server就可以将第2、3次挥手合并,同时告诉client两件事
- 已经知道client没有数据要发
- server已经没有数据要发了