TCP和UDP协议发生在传输层
传输层主要处理:端到端的连接
TCP和UDP
UDP 是一个人面向无连接的,可类比成写信,UDP发送写信到邮箱之后,对方不能够立即就可以收到,先后发送的信件也不一定是按照顺序接收,甚至里面信的内容有可能也不会完整 而TCP可以类比成电话,我们拨打电话到对方接通,互相通话,结束后挂断,这些都能够完成,并且能够确认对方是否能够接收到数据,
为什么要有TCP/IP 协议
数据的IO操作,硬件的数据到另外一个硬件上面也是需要有协议的,在冯诺依曼体系中,也是设计一个硬件和一个硬件之间都是相互独立的,硬件之间的通信也都是需要协议(因为有“线”) 总线就是用来解决通信的问题
一个单处理器系统中的总线有三种
- CPU 内部连接各个寄存器及其运算部件之间的总线,称为内部总线
- CPU 同计算机系统的其他高速功能部件(存储器,通道等互相连接的总线),称为系统总线
- 中,低速度IO设备之间互相连通的总线位,IO 总线
结论
- 在网络中的各个设备,各个电脑之间也是直接或者间接使用“线”来进行连接的
- 只不过网络中,“线”变的更长了,可以通信的距离变遥远了
- 因此一旦传输距离变长的话,就需要一些协议来保证数据可以可靠的到达
TCP(传输控制协议)
TCP在两个端点(应用程序之间提供了可靠的,面向连接的,双向字节流通信信道) TCP端点:表示TCP连接一端的内核所维护的信息(通常会进一步对这个术语进行缩写),“一个TCP端点”或者“客户端TCP”来表示“客户端应用程序所维护的TCP端点”
TCP 实现有效的载荷和首部分离是通过4位首部实现的 TCP实现交付给上层工作是由16位目的端口号
假设首部长度为6,说明选项有4字节,这样就可以完整的读取报头数据,然后数据的读取可以根据数据的协议做处理,
TCP 报文格式
序
列
号
:
序列号:
序列号: TCP 连接中发送的每一个字节都是按照顺序编号 在建立连接的时候,设置整个要传送的字节流的其实序号为计算机生成的随机数 序列号则是本报文段发送的第一个字节的序号,
确
认
号
确认号
确认号 是期望收到对方下一个报文段的序列号,而且确认号为N,表示前N-1为止的数据都已经正确收到了
首
部
长
度
首部长度
首部长度 表示TCP 头部有多少个32为bit(有多少个4字节),因为是4位所以TCP头部最大长度是15*4=60 最小是固定20个字节
窗
口
大
小
窗口大小
窗口大小 表示自己接收缓冲区剩余的空间大小,要求对方发送数据的时候要考虑到这一点,这个值通常是变化的(16位)
校
验
和
校验和
校验和 校验的范围包括首部和数据 发送端填充CRC 校验,接收端校验不通过,则认为数据有问题
紧
急
指
针
紧急指针
紧急指针 指出了紧急数据的末尾在报文段中的位置
六
位
标
志
位
六位标志位
六位标志位 URG(urgent):为1代表了紧急指针有效 ACK:为1代表有效,连接建立后所有的报文ACK都是1 PSH(push):提示接收端应用程序立即从TCO 缓冲区读走 RST:表明出现了严重差错,必须释放连接重新建立 SYN:为1代表了这是一个连接请求 FIN:通知对方,本端要关闭了,我们称携带FIN 表示的为结束报文段
连接之前
TCP 三次握手四次挥手
在第一次发送SYN 的时候{SYN,1000-》(标号)(0-》数据的大小) <mass 1460 (传输文件的大小上限)>}
- 确认号+1 是因为在原来的序列号还要加上SYN这个标志位的大小
- 三次握手完成在代码层面上的体现就是:accept()和connect()成功执行返回,所以accept()执行发出错误信号出来,或者connect都是没有建立成功
- 普通报文不携带数据,不消耗序列号,所以后面再在传输的时候不会加上标志位,该多大就多大
- 假如说客户端先发起关闭,第一次挥手断开连接(半关闭,有一端关了,另一端没有关闭第一次挥手),客户端如果还有数据就不能再发送了,相当于socket内部的写缓冲区关闭了,但是读缓冲区还留着
- 后面服务器也要发起关闭,也要有两次挥手(半关闭是导致4次挥手的原因)
- 半关闭并非是关套接字,而是关里面的写缓冲区,读缓冲区还没有关闭,
- 数据会被分成段,如果一个段在到达时是存在错误的,这个段就会被丢弃,确认信息也不会发送,发送者在发送每一个段的时候,都会开启一个定时器,如果定时器超时之前没有收到确认,就会重传这个段,
滑动窗口
假如说客户端硬件设备比较先进,一直在给服务端发送数据,那么就会把服务器端的内核缓冲区给挤爆,如果有阻塞机制的话,那么服务器端就阻塞在了那边,如果没有阻塞机制的话,那么服务器端的前面的数据就会被覆盖掉 所以这个时候就会有一个叫做滑动窗口的机制,服务端告诉客户端,用来存放数据的缓冲区有多大,如果满了的话,就不要再发送了
滑动窗口就是为了防止数据丢失,防止出现丢包的情况
16位的滑动窗口就是滑动窗口大小不能超过2^16,32位序号就是序号的数值不能超过2 ^32,
- recvfrom和sendto这些函数实际上就是一个拷贝函数:TCP 当中有接收缓冲区,和发送缓冲区,recvfrom,sendto就是把数据拷贝到TCP 缓冲区里面,以及从TCP 缓冲区里面把数据拷贝到用户层,
- 文件描述符的生命周期是谁这进程的,一旦客户端建立好联系之后死机了,进程退出了,此时文件描述符也就退出了
- 流量控制:填写TCP 滑动窗口的大小,告知对方自己能够接收到上限,达到两个方向上传输速度的控制,就叫做流量控制
流量控制和快重传
流量控制
我们上文解释过了窗口的含义,还有一个概念是滑动窗口 滑动窗口其实就是发送缓冲区的一部分(实际上缓冲区是头尾相接的环形) 假设A收到了B发来的确认报文,窗口是20字节,确认号是31,这样就可以构造出自己的发送窗口
在没有收到B确认报文的情况下,允许A 可以把滑动窗口的数据都发出去,但是在A 收到B 的确认报文之前,发送出去的数据都要暂时保留在这个滑动窗口里面,以便重传
显然,A的发送串口的数据不能超过B 的接收能力,
发送窗口的大小由前沿和后研进行决定,窗口越大,则网络吞吐效率高,
收到新确认,后沿就可以往前移动,没有收到的话,后沿就不能移动
前沿通常向前移动,但是由于接收方接受能力变低,前言可能也不会动 TCP 不支持前沿向后移动 滑动窗口既保证了发送方的效率,也保证了接收方的接受能力,这就是所谓的流量控制
快重传
如果丢包了怎么处理呢
-
像上面这样的,即使ACK600丢失了,也不需要进行重传,因为后续的ACK700可以被发送方收到,后续的报文可以说明前面的报文已经获取了(收到多少窗口往右移动就可以了) -
还有一种情况是发送方的数据报文丢失 那么发送方会连续收到接收方发来的相同的报文,连续收到三个,就要重新发送对应的报文,这种机制就叫做“高速重发控制”,也叫“快重传”
快重传vs超时重传
ACK 的语义非常重要,因为决定了即使收到了后面的数据,
超时传输
TCP 的发送方在规定时间内没有收到确认报文就要重传已发送的报文 没有收到确认报文的原因有两种
- 发送方的发送报文丢失
- 确认方的确认报文丢失,如果是这样的话,接收方会收到一份相同的报文,TCP 协议通过序列号识别出了这个重复保卫呢,就会进行丢弃,(TCP具有去重能力),所以也能够保证报文的按序达到,
如何保证TCP 有超时重传的机制
- 需要超时重传实际上也就说明了发送方数据发送出去后也不能将数据删除或者覆盖掉(因为可能对面没有收到,还要重新发送一次)
- 需要发送方收到对方的ACK才可以把数据移除掉
TCP 为了保证高性能通信,动态计算了这个最大超时时间(不能让一直等待)
由于这个和带宽(用于表示一秒钟内网络传输的总容量)有关,所以这个超时时间是动态的 Linux中,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍 如果重新发出一次还得不到银达,就要等待2500ms进行重新发送,如果还是不能应答,就在等待4500进行重传,一次类推以指数形式递增,累积到一定的重传次数,TCP 就会认为网络或者对端主机出现了异常,强制关闭连接
拥塞控制
滑动窗口时靠内核进行维护的,我们在发送的时候不仅要考虑对方滑动窗口所能接收的最大值,还要考虑网络在台,因为一个计算机网络中都是有很多主机所共享的,因此可能会因为其他主机之间的通信变得十分拥堵,在网络拥堵的时候,如果发送大量的数据包,就会导致数据包时延,丢失,这是TCP 就会重传输数据,但是重传会导致网络负担更加,这就是拥堵控制
拥塞控制简单来说:网络发送拥堵的时候,TCP 会减少发送量,为了在发送时调节发送数据量,定义量拥塞窗口这个概念,拥赛窗口也是发送的窗口,他会根据网络的拥堵程度进行动态变化,发送窗口的大小实际上是拥塞窗口和接受窗口中的最小值:(滑动窗口=min{拥塞窗口,对端窗口的大小})
- 此处引入量拥塞窗口的概念
- 发送的一开始定义拥塞窗口的大小为1
- 每次收到ACK 应答,拥塞窗口+1,这样子实际上,我们第一次发送一个数据,收到回应之后,拥塞窗口就变成了2,在对端接收缓冲区允许的情况之下,我们可以发送2倍的数据
- 每次发送数据包的时候,讲拥塞窗口和接收端主机反馈的窗口大小进行比较,去较小的值作为实际发送的窗口
- 阀值主要时用来判断合适指数增长何时线性增长
- ssthresh数值表示拥塞控制窗口大小的一个阀值,峰值会影响阀值的大小,这里的峰值就是引起数据丢包的窗口大小,每次出现丢包的情况阀值就会变成丢包时峰值的一半
- 慢启动是能够防止一个快速的TCP发送压垮整个网络
- 慢启动的阈值是为了避免传输速率指数级增长导致发送者在短时间内就会压垮整个网络,TCP拥堵算法也就是为防止这种事情发生,他为速率的增长提供了一个管理实体
- 一开始都是慢启动算法,刚开始的指数增长是为了尽快让传递批量数据,更快的达到不丢包的极限,所以就需要使用指数增长,从而达到慢启动阀值(ssthresh)的初始值之后,我们就线性增加即可了,此时不能再指数增长了,
- 当网络拥塞的时候,发生超时重传或快重传后需要重新设置拥塞窗口大小,此时将慢启动的阀值设置为发送网络拥塞的一半,然后从1开始按照指数级发送数据,到达阀值再进行线性增长
- ssthresh设置为cwnd/2
- cwnd重置为1
发生快重传
当发送端收到连续三个重复的确认时,把慢开始的阀值ssthresh减半,由于发送方并不认为此时网络拥塞,所以此时不执行慢开始算法,即:不会讲cwnd设置为1,而是设置成为ssthrsh减半之后的值
延迟应答
提高效率的一种策略,网络传输的时间可能比准备的时间还要长,所以我希望一次多发一些数据过去,提高效率
延迟应答:如果接受数据的主机立刻返回ACK 应答,这时候返回的窗口可能比较小
假设接收端缓冲区为1M,一次接收到500K 的数据,如果立即应答,返回的窗口就是500K,但实际上可能处理端处理的速度很快,10ms之内就把500K的数据从缓冲器里面消费掉,但这情况下,接收端还远没有达到自己的上限,及时窗口再大一点,也能处理的过来,如果接收端稍微等待一会在应答,比如说等待200ms再应答,这个时候返回的窗口大小就是1M
因为窗口越大,网络吞吐量就越大,传输效率也就越高 但是并非所有的包都可以延迟应答 数量限制:每隔N个包就应答一次 时间限制:超过最大延迟时间就应答一次 具体的数量和超时时间,依操作系统不同也有会有差异。一般N取2,超时时间取200ms
捎带应答
服务器最常见的就是一问一回的模式,如果每次消息都是一问一答,类似于
- 请求
- 请求的ACK
- 响应
- 响应的ACK
如果变成捎带应答,就是响应的同时,把ACK 顺便带过去,在正文中附加一些数据,在双向通信的时候,又做确认又做应答
- 请求
- 请求的ACK+响应
- 响应的ACK
这样就减少了包的传输个数,降低了通信成本,提高了效率
粘包问题
粘包问题中的”包“,指的是应用层的数据包 TCP是基于字节流的,只维护发送出去多少,确认了多少,并不会维护消息和消息的边界,这就导致了粘包问题,他在应用层取数据的时候,不知道从哪里到哪里是一个完整的应用层数据包,面向字节流读文件都会有这种问题
数据就变得混乱了
怎么解决呢?
由于找不到应用层的数据始末,所以去TCP 上面做功夫肯定是不行的,所以解决这个问题就是要在应用层协议中加上包和包之间边界,比如在应用层数据报的结尾加上一个**;**这样在读取的时候,就能区分出一个完整的应用层数据报了,
TCP 异常处理
TCP协议传输时会出现以下几种情况 连接崩溃的情况
- 机器关机/重启:会释放文件描述符,发送FIN,和正常关闭一样,和进程终止是一样的
- 进程终止:和机器重启一样
在进程毫无准备的情况下,突然结束进程, 我们知道TCP 连接是通过socket进行的,socket本质就是打开了一个文件,文件就存在于PCB的文件描述符表之中,每次打开一个socket文件都会在文件描述符表添加一项,删除会减少的一项 当强制结束进程时,PCB没了,里面的文件描述符表也没了,就相当于自动关闭了,也依然会执行四次挥手的过程
- 网线断开/机器断电:断电时没有时间给操作系统去反应,来不及四次挥手,如果客户端断电,服务器会尝试重新连接,重连一定次数的话,就会放弃连接,接收端的连接还在,当接收端对我进行写入,会收到一个reset然后TCP 内不设置一个保活定时器,询问对方是否还在,不在就释放连接
但是最好不要把保活定时器作为一个应用层的工具,因为不同的应用层对于连接是否需要关闭是不一样的,比如QQ,在断线之后,也会定期尝试重新连接
TCP总结
UDP
用户资料包协议
UDP概述
UDP 作为传输层的协议,他规定了数据什么时候发送,发送多少的问题,交付给上层,是由目的端口号来完成的,有效载荷和数据进行分离的工作是由固定报文首部长度来实现的
UDP过程分析
当对端UDP 层获取到这个报文的时候,先识别8字节,然后就可以拿到这个报文的长度是多少,测试报文有没有出错,然后通过16为目的端口号,得知要标识交付给上一层的哪一个协议
一些特点:
- 报文通过目的端口号进行报文的分用,通过定长找到对应的报文长度,字段进行获取有效载荷
- UDP没有任何一个填充字段或者选项,说明他的内容很简单
- UDP 支持一对一,一对多,多对一,和多对多的交互通信,UDP 可以提供全多工的服务,只不过不可靠
主要特点:
- 无连接,知道对端的IP和端口号就直接进行了传输,不需要建立连续
- 不可靠,没有确认机制,没有重传机制,如果网络故障没有发送给对方,UDP协议层也不会发送给应用层错误信息
- 面向数据包,应用层交给UDP多长的报文,UDP就会发送多少的报文,不会拆分也不会合并(不会出现粘包问题)
UDP 的缓冲区: UDP没有真正意义上的缓冲区,调用sendto之后,直接将数据交给内核,由内核将数据传给网络层协议进行后续的传输动作
UDP具有接收缓冲区: 接收缓冲区不能保证收到的UDP 数据报顺序和发送顺序一致,如果缓冲区满了,再达到的UDP数据报就会被丢弃
UDP报文格式
数据在自下而上的传输过程,首先一开始通常是我们的客户端主动连接服务器的
- 既然如此,那么客户端肯定是知道服务器的端口号的,我们访问的服务器端口号肯定是众所周知的
- 长度:数据报的长度,最小为8字节(只要报头),最大为64K,如果传输的数据超过64K,需要在应用层手动的分包,多次发送,并在接收端手动拼接
- 校验值:检测UDP数据报是否有错误,有错就丢弃,在这里计算报文,还需要加上12字节的一个伪头部进行计算,这里的校验更多是为了验证发给我的这个协议,IP的校验偏重是否有错
使用UDP 的应用层协议
|