1、传输层概述
传输层:是负责端与端之间的连接(端口),传输层只负责端口,不负责网络。 网络数据在传输过程中的五元组信息:源端口,目的端口,源ip,目的ip,协议。
传输层向它上面的应用层提供通信服务,传输层属于面向通信部分的最高层,同时也是用户功能中的最底层;只有位于网络边缘部分的主机的协议栈才有传输层,而网络核心部分中的路由器在转发时只用到下三层的功能。
通信:真正进行通信的实体是在主机中的进程,是一台主机中的进程和另一台主机中的进程在交换数据,严格来讲,两台主机进行通信就是两台主机中的应用进程互相通信
2、传输层----UDP协议
2.1 UDP协议
?
2.2 UDP协议数据协议格式
- 16位UDP长度:表示整个数据报(UDP首部+UDP数据)的最大长度
- 校验和:如果出错,则全部丢弃
?
2.3 校验和
本质上:是接收方用来校验udp数据在传输过程中是否失真 1)udp数据失真:发送数据和接收数据不一致,处理:接收方的传输层直接将数据丢弃 2)udp数据没有失真:直接交给应用层 ? 校验和的计算方式 如何计算(发送方): 1)将udp数据报(udp协议头+有效数据),分割成多个16比特位的小段,除了校验和的16个比特位外,其余比特位进行相加操作 2)将求得的结果反码运算,将反码运算的结果放到16个比特位的校验和当中 ? 如何校验(接收方): 1)将所有的16个比特位的小段全部加起来,如果比特位全1,则表示数据正常
?
2.4 udp缓冲区
1)udp缓冲区是整条发送和接收的
2)UDP没有真正意义上的 发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。
3)UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了,再到达的UDP数据就会被丢弃。
?
2.5 基于UDP的应用层协议
- DHCP协议(动态主机分配协议):谁上网就分配ip
- DNS协议(域名解析协议):将域名转化成ip地址,使用udp协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- BOOTP:启动协议(用于无盘设备启动)
?
2.6 问题总结
1)如果应用层提交给传输层udp协议超过65535字节,怎么办?
- 应用层自定制协议,将数据在应用层分割成满足udp的传输小块,在使用udp协议进行传输
2)如何保证在接收方的应用层表示若干个udp数据是一个应用层数据?
- 在设计应用层协议时,给属于同一个数据报的数据子块打上相同的ID,即不同的数据子块具有相同的ID。
3) 如何保证发送数据和接收数据的顺序一致?
- 在报头当中加上数据的偏移量,偏移量描述子块数据在整个应用层数据报当中的位置
?
3、传输层----TCP协议
3.1 TCP协议特性
?
3.2 TCP协议段格式
- 4位TCP报文长度:最长 15 * 4 = 60字节。
- 6位标志位:
URG:紧急指针是否有效 ACK:确认号是否有效 PSH:提示接收端应用程序立刻从TCP缓冲区把数据拿走 RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段 SYN:请求建立连接;我们把携带SYN标识的称为同步报文段 FIN:通知对方,本端要关闭了。我们把携带FIN标识的称为结束报文段 - 16位校验和:发送端填充,CRC校验。
- 16位紧急指针:标识哪里就是紧急数据
- 40字节头部选项
?
3.3 连接管理机制
三次握手 第一次握手:建立连接时,客户端发送syn包(syn=1)到服务器,客户端进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
?
四次挥手 因为 TCP 连接是全双工的,也就是说通信的双方都可以向对方发送和接收消息,所以断开连接需要双方的确认。
第一次挥手 客户端认为没有数据要再发送给服务端,它就向服务端发送一个 FIN 报文段,申请断开客户端到服务端的连接。 发送后客户端进入 FIN_WAIT_1 状态。
第二次挥手 服务端收到后,向客户端发送一个确认报文段ACK,表示已经接收到了客户端释放连接的请求,以后不再接收客户端发送过来的数据。但是因为连接是全双工的,所以此时,服务端还可以向客户端发送数据。 服务端进入 CLOSE_WAIT 状态。 客户端收到确认后,进入 FIN_WAIT_2 状态。
第三次挥手 服务端发送完所有数据后,向客户端发送 FIN ACK 报文段,申请断开服务端到客户端的连接。 发送后进入 LAST_ACK 状态。
第四次挥手 客户端接收到 FIN 请求后,向服务端发送一个确认报文段 ACK ,并进入 TIME_WAIT 阶段 。 服务端收到客户端的确认报文段后就立即进入 CLOSED 状态。 客户端 TIME_WAIT 阶段 会持续一段时间,这个时间为报文段在网络中的最大生存时间,如果该时间内服务端没有重发请求的话,客户端就进入 CLOSED 状态。如果收到服务端的重发请求就重新发送确认报文。
这样全双工的连接就被释放了。
? TIME_WAIT状态导致服务端快速启动的问题 1)TIME_WAIT只有主动断开的连接放回拥有 2)原因主动断开连接方的状态TIME_WAIT到状态CLOSED状态需要等在2MSL的时间(ACK的MSL+重传FIN的MSL) 3)结论:主动断开连接方TIME_WAIT到CLOSED状态,之前监听的端口没有释放
?
包序管理 1)对于tcp连接双方,各维护一套序号,在三次握手期间双方协商各自序号的起始位置 2)三次握手的序号不一定从0开始 3)纯ACK数据包不消耗序号的 4)确认序号的计算:发送方发送的数据的起始序号+【数据长度】(如果没有数据长度,则加1即可,例如:SYN,有数据长度,例如:PSH)
?
3.4 保证可靠传输
确认应答机制
SYN确认序号:发送方发送的序号+1 PSH确认序号:发送方发送的序号+发送方发送的数据长度
超时重传机制 如果对方没有确认应答,此时隔一定的时间之后,就需要重复传输这样的数据。
重传是为了进一步降低丢包的可能性,重传的间隔时间采用的是一种比较悲观的态度。
RTT:报文往返时间 RTO:超时重传时间,动态变化的。(2 * RTT) ?
3.5 滑动窗口
滑动窗口机制 发送方维持发送窗口,位于发送窗口内的分组都可以连续发送出去,而不需要等待对方确认,这样信道利用率就提高了。
发送方每收到一个确认,就把发送窗口滑动一个分组位置,接收方采用累积确认方式,在收到几个分组后,对按序到达的最后一个分组发送确认。
优点:容易实现,确认丢失也不必重传
缺点:不能向发送方反映出接收方已经正确收到的所有分组信息
滑动窗口机制就是为了提高通信双方发送效率的。允许TCP发送“一部分数据”,这些数据丢到网络中,可以暂时不要应答,发送的速率就比之前要确认的效率高很多。
滑动窗口丢包问题 1)ACK被丢弃 这种情况丢包不要紧,因为可以通过后序的ACK进行确认。
2)数据包丢失了 当某一段报文丢失之后,发送端会一直受到1001这样的ACK,提醒发送端我要的数据时 1001 如果发送端主机连续发送三次收到了同样一个1001这样的应答,发送端就会重新发送 1001~2000的数据。
? 滑动窗口和TCP发送缓冲区是否有关系? 是有关系的。因为TCP发送方发送数据的时候,是将数据提交到发送缓冲区。滑动窗口就是将发送缓冲区的当中的数据,进行了标识:那些事发送过的,那些是没有发送的,那些是发送了没有收到确认的。
滑动窗口的大小是一成不变的吗? 窗口大小是变化的,可能受网络因素的影响和接收方的接收缓冲区的大小。
0号窗口:告诉发送方,目前接收不了数据。 窗口探测包:发送方主动询问接收方的接收能力。
?
3.6 流量控制
接收端处理数据的速度是有限的。如果发送端发送的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包。
持续计时器:解决盲等死锁。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,若计时器到期,就发送一个零窗口探测报文段,而对方就在确认这个报文段时给出了现在的窗口值,若窗口值仍是零,那么收到报文的一方就重新设置持续计时器,若不是零,那么死锁就被打破
?
3.7 拥塞控制
拥塞窗口和发送窗口共同决定TCP发送方发送到网络上面的数据量 ?发送窗口 > 拥塞窗口:按照拥塞窗口的大小决定发送 ?发送窗口 < 拥塞窗口:按照发送窗口的大小来进行发送
TCP 的拥塞控制主要是根据网络中的拥塞情况来控制发送方数据的发送速率,如果网络处于拥塞的状态,发送方就减小发送的速率,这样一方面是为了避免继续增加网络中的拥塞程度,另一方面也是为了避免网络拥塞可能造成的报文段丢失。
TCP 的拥塞控制主要使用了四个机制,分别是慢启动、拥塞避免、快速重传和快速恢复。
1)慢启动:先发送少量的数据,通过上一次返回的时间进行判断,如果上一个数据包返回的比较快,TCP就认为转发能力较好,一次增加网络的转发量。
2)拥塞避免:随着传输轮次的增加,拥塞窗口是呈线性方式进行增加的(这样将窗口的增长速率由指数增长,变为加法线性增长);直到增加到拥塞状态,则执行(慢开始----老的说法),(快恢复----新的说法)
3)快恢复:因为网络中可能已经出现了拥塞情况,所以会将慢启动的阀值减小为原来的一半,然后将拥塞窗口的值置为减半后的阀值,然后开始执行拥塞避免算法,使得拥塞窗口缓慢地加性增大。
4)快重传:当发送方接收到同样三个确认应答之后,就会立即进行重传丢失的报文
?
3.8 捎带应答和延时应答
延时应答:接收方为了给发送方通知更大的窗口大小,所以在接收到发送方发送的数据之后,不会立即给发送方回复确认应答,而是等待应用层调用recv函数从接收缓冲区当中将数据读走之后在回复应答。
捎带应答:给对方发送数据的时候TCP报文当中,也夹杂着给对方回复的ACK ?
4、TCP粘包问题
多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使数据包的边界发生错位,导致读出错误的数据分包,进而曲解原始数据含义。
1)设计方案一:定长发送 在进行数据发送时采用固定长度的设计,也就是无论多大数据发送都分包为固定长度(此处定长为记为LEN),也就是发送端在发送数据时都以LEN为长度进行分包。
这种方法会有缺陷:
- 最后一个不足长度包会被无效字符填充,但这也为接收端处理带来了麻烦。可以在每一个数据包的最前面增加一个定长的报头,然后将该数据包的末尾标记一并发送。接收方根据这个标记确认无效字节序列,从而实现数据的完整接收。
- 在发送包长度随机分布的情况下,会造成带宽浪费。
综上,此方案适在发送数据包长度较为稳定(趋于某一固定值)的情况下有较好的效果。
? 2)设计方案二:尾部特殊标记序列 在每个要发送的数据包的尾部设置一个特殊的字节序列,通过尾部序列确认数据包的边界。
这种方法的缺陷较为明显:
- 接收方需要对数据进行分析,甄别尾部序列。
- 尾部序列的确定本身是一个问题。
? 3)设计方案三:头部标记分步接收 这个方法是,定义一个用户报头,在报头中注明每次发送的数据包大小。接收方每次接收时先以报头的size进行数据读取,这必然只能读到一个报头的数据,从报头中得到该数据包的数据大小,然后再按照此大小进行再次读取,就能读到数据的内容了。
由图看出,数据发送多了封装报头的动作;接收方将每个包的接收拆分成了两次。
这方案的缺陷:
- 报头虽小,但每个包都需要多封装sizeof(_data_head)的数据,积累效应也不可完全忽略。
- 接收方的接收动作分成了两次,也就是数据读取操作的 recv 或者 read 的系统调用操作多了一倍,这对内核而言的开销是一个不能完全忽略的影响。
优点:避免了程序设计的复杂性,其有效性便于验证,对软件设计的稳定性要求来说更容易达标。综上,方案三乃上上策!
?
- 序列化:将对象转换成二进制
- 反序列化:将二进制转化成对象
?
5、异常情况
主机掉电(拔网线)
- 掉电的是接收方:发送方会触发超时重传,尝试重新建立链接。
- 掉电的是发送方:接收方如果一直接收不到数据的话,达到一定的时间,就会给对方发送一个“心跳包”,如果没有心跳了,就会重新尝试链接;如果链接建立失败了,彻底释放链接
? 心跳检测步骤: 1 客户端每隔一个时间间隔发生一个探测包给服务器 2 客户端发包时启动一个超时定时器 3 服务器端接收到检测包,应该回应一个包 4 如果客户机收到服务器的应答包,则说明服务器正常,删除超时定时器 5 如果客户端的超时定时器超时,依然没有收到应答包,则说明服务器挂了
?
保活计数器 当数据双方没有数据往来的时候,超过保活计数器时间之后,就会给对方发送心跳包,如果对方没有确认收到心跳包,那么就连续发送十次心跳包,还是没有确认的话,那么就关闭连接。
|