简单了解传输层协议TCP

上图为TCP报头结构,由20字节固定首部+可选字段构成

其中数据偏移就是4位首部长度(记录报头长度的),基本单位为4字节(比如若首部长度为0101,就是5*4=20字节),其中可选字段部分最多40字节(15*4=60字节,60-20=40字节)

TCP叫做可靠性协议

可靠性协议的含义:尽最大努力发送报文,保证能在丢失时进行检测和重传

但是要注意可靠性不代表百分百保证对方接收到报文

假设我们要保证报文被对方百分百接收到,就有以下过程:

(首先要知道,对发出去的报文,要有应答,收到应答,就代表历史报文绝对被对方收到)

发送方需要收到接收方的应答,保证自己的发送的报文一定被对方收到了

可是同样的,接收方发回的应答报文不能保证发送方能收到,所以发送方还得发送应答报文给接收方代表它收到了,很明显如果一直这样下去,就会成为一个循环

很明显,这是错误的

所以事实上,不存在绝对可靠的协议,最新的报文,永远都不会有应答

TCP协议的可靠性在于:对于历史报文的可靠性(百分百保证发送方送给接收方的报文,接收方一定收到了)

TCP规定:A主机发送给报文B主机,B主机必须应答,这个应答报文可以不携带任何有效信息,A主机收到了就代表双方都收到了,因为不需要对应答保证可靠性

这个就叫做确认应答机制

当A主机发送给B主机的报文保证可靠,B主机发送给A主机的报文保证可靠(不包括应答报文),就是全双工的可靠性

总而言之,可靠性不代表报文百分百必须送到,而是你收到了,我得知道,没有收到,我也知道

报文丢失

如果A(假设是A发的),没有收到B的应答报文

就会分成两种情况

1、报文真的丢失了

2、应答丢失了

当A没有收到应答报文,它首先会判定是报文真的丢失了,因为不管是报文丢了还是应答丢了,对于A而言,都是没收到应答

那A怎么知道自己是没有收到任何应答,还是还没等到

A会给自己设置一个deadline

超时就是丢包,丢包就是规定出来的

如果没有收到应答,发送方就会进行 重传(没有收到报文之后的补发措施)

B如果收到了报文(是应答丢了),再收到了A发的同样的报文,就会进行去重

报头中的32位序号就是用来进行去重操作的,找到序号相同的报文,B就会把旧的那一份丢掉

TCP有两种通讯方式

TCP通信,双方地位是对等的,它们的通信也是对称的

方式一:A发报文,得到B的报文,才能再发给B报文

方式二:A给B发一批报文,B需要对每个报文都做应答

真实的通信情况就是方式二

方式二的A的等待时间就可以重叠(跟发送一个报文的等待时间一样,这里只考虑超时重传)

对于方式二:B收到报文的顺序跟A发送的顺序不一定一样,这是因为从A到B的路线,其实有很多种,路径不一样就可能出现,晚出发的报文会早到

B解决乱序问题就可以通过序号 来进行重新排序,保证按序到达

B发回的应答报文,也需要有序号,要不然如果有信号丢失,A就没法确定哪个的报文应答丢了,这个序号就是确认序号

确认序号:

收到的序号+1 就是 确认序号

含义:该序号之前的所有数据,我全部收到了

假设确认序号是3001,就代表前面的1000 2000 3000 都收到了(注意:3001没有收到)

为什么序号和确认序号要分开成为两个

明明每次使用都只要用到一个序号,另外一个序号用不到,为什么不干脆就用一个序号

发送时只用到序号,应答时只用到确认序号

TCP是全双工的,既然需要发送应答报文,完全可以把要发送的数据也送过去

因为数据可以捎带应答

发送数据和发送确认就可以同时进行了

窗口大小

当接收方的接收缓冲区空间已经不够了,发送方发送的信息太快,接收方来不及接收,剩余的就会扔掉,让发送方重传

这无疑是对网络资源的一种浪费(网络传输也是需要消耗资源的)

就需要进行流量控制

为了进行流量控制,发送方就得知道接收方的接收能力

接收方,如何衡量自己当前的接收能力

接受能力就是接收缓冲区内,剩余空间的大小

发送方如何得知对方的接收能力

报头里面的16位窗口大小,就是用来传递接收方 接收缓冲区的剩余大小

标志位

服务端会 连接无数个客户端

客户端会发送无数报文给到服务端

有的报文是应答,有的是建立连接的,有的是断开连接,有的是数据报文

这意味着,报文本身是需要有类型的

TCP 协议报头中,就必须有对应的字段表明报文类型

标志位就是用来标识报文的类型(发送啥标志位就是将啥 标志位 置一)

ACK置1就是应答报文,由于可以捎带应答,所以大部分报文ACK标志位都是1

SYN 叫做连接建立的请求报文,比如客户端要求建立连接服务端

在TCP这里,是通过三次握手建立连接的

发送方需要先发个SYN 给到接收方,接收方表示可以连接,就会发送SYN+ACK,发送方发送应答,发回去ACK

什么是连接

不管是服务端还是客户端(客户端也可以连接多个服务端),双方os内部都会存在大量的连接,os就需要管理多个链接

TCP链接的描述结构体叫做struct tcp_sock,tcp_sock内部的第一个成员为 struct inet_connection_sock,他管理面向连接协议的通用信息,比如:全连接队列、半连接队列、重传策略等等

全连接队列:全连接队列(也叫 Accept 队列,Accept Queue)是 TCP 三次握手完成后,已经建立起完整连接,但还没来得及被应用程序调用 accept() 取走的 socket 所暂存的队列。

半连接队列:半连接队列(SYN Queue) 是服务端用于暂存 “只完成了 TCP 两次握手,但还没完成第三次握手” 的连接请求的队列。(客户端还没有发送ack报文的时候)

当完成三次握手,os会创建一个struct tcp_sock 对象,放到监听 fd 对应的全连接队列内,服务端accept的时候再为它分配一个全新的 struct file

为什么要进行三次握手

要通信之前,得保证网络通畅

三次握手能保证 发送方 接收方 各自进行一次发送和接收,验证 双方都可以收和发(以最小次数,验证全双工)

两次握手保证不了,只能保证A能发数据和收数据,B只能保证能收数据,发数据保证不了

断开连接需要进行四次挥手

客户端主动发送FIN

服务端发送 ACK(同意断开)

服务端发送 FIN(我也要断开链接)

客户端发送 ACK

为什么要进行四次挥手

四次挥手,是以最小成本的方式,争的了双方的同意

断开连接是双方的事情,双方都需要收到一次FIN

四次挥手之后,连接结构体自然就被释放了,对应的文件结构体也就释放了

客户端主动发送FIN 的意思是,我再也不发了

服务端知道后,表示自己还要发消息,客户端就还会收到消息(结束全双工),直到服务端也表示不发信息了,就断开链接了

在上层的表现就是,双方都调用close()

还出现一种情况,服务端的ACK FIN 一起发给客户端

此时四次挥手就变成了三次挥手

同理,三次握手,也可以变成四次握手

(只不过建立连接时服务器都会无条件答应,所以建立连接和应答永远会压缩在一起,所以不会出现四次握手)

而断开链接时,服务器必须得在条件满足的前提下才能断开链接,所以说是四次挥手

RST标志位

RST 标志位代表“重置连接”(Reset),它的作用是立即、强制地终止一个 TCP 连接,相当于“直接把电话线拔了”。

三次握手时,最后一个ACK有没有被接收方收到是不确定的

发送到接收是有时间差

在客户端角度,只要将最后一次的ACK发出去就算是三次握手成功

而最后一个ACK 是有可能丢的

一旦服务器没收到最后一个ACK,就会重新发 SYN ACK

可是如果客户端认为自己已经链接成功(没有收到服务器发来的syn ack),会给服务端开始发送报文,服务端收到报文之后就会增加 RST 标志位,发送RST报文,要求重新链接

当然,RST标志位还有其它应用场景,要更复杂的多,这里只简单讲述这一个例子

PSH标志位

全称:push

一个极端的例子

如果接收端的接收缓冲区剩余空间大小为0,发送端就得等,可是要等多久是不确定的,发送端需要发送报头(接收缓冲区满了,也可以接收报头,不带数据就行),接收端就需要做应答,就需要重填窗口大小

如果接收端窗口大小一直为0,发送端发送了好几次,也依然是0,就会把PSH设置为1,接收端接收到它就需要立刻通知应用层把缓冲区数据立即取走

PSH标志位同样对发送端起到催促作用

在TCP的正常运作中,默认会使用一种叫 Nagle算法 的机制。它的目的是减少网络上小数据包的数量,提高网络利用率。简单说,它会尽量把多个小的数据块攒在一起,等攒到一定大小再一次性发送。

但这样会导致一种现象:延迟。比如你敲击键盘发送一个字节,Nagle算法可能会等一等,看有没有更多数据一起发,这就产生了延迟。

这时候就可以通过设置PSH标志位,来告诉TCP协议栈:“别等了,立刻把这份数据发出去,别管Nagle算法了”

URG标志位

它是紧急指针

它表示的是16位紧急指针是否有效

如果URG标志位为0,这16位紧急指针就无效

指针:只要具有一定的指向性,就可以叫做指针(比如索引)

这里的紧急指针,代表的就是偏移量

可以把发送缓冲区想象成一个char类型的buffer

每一个字节都有自己的下标(就是上文讲的32位序号的序号)

初始序号真实情况下是随机的序号(不是从0开始的),根据某种算法转成数组下标(就是从0开始的偏移量)

而数组下标就是偏移量

紧急指针:发送的报文数据的某个字节在数组里的下标(通常情况下就是一个字节)

注意:紧急指针里是什么数据根本不重要,它只需要存在,接收方就知道需要发送中断信号

正常情况下,接收端是以先进先出,如果带了URG,接收端就会优先读取对应紧急指针指向的字节

为什么需要有紧急指针

比如正在上传某段资源时,突然要求暂停上传数据,按照正常应该是先上传完,才能读到要求暂停的报文,就需要加上紧急指针,让暂停的报文插队,接收端就会优先读取这个报文

紧急指针的机制需要双方应用层都进行处理

也可以建立两条链接,一个收发数据,一个收发控制命令,就是不使用紧急指针

确认应答机制(ACK)

确认应答是确保可靠性的机制

确认序号就是下一次发送序号的起始序号

一旦发送丢包(不管是数据报文丢了,还是应答丢了),发送方都不能一直等下去,就需要设置时间间隔

一旦超时直接重传

这个特定的时间间隔,不能太短(因为报文传输也需要时间,会导致频繁重传报文)也不能太长

这个特定的时间间隔是浮动的,跟网络的健康程度有关,因此会选择动态计算这个最大超时时间

等待时间就是 500ms*2的n次方-1

n为重传的次数

意思就是第一次会等500-1ms,第二次就会等500*2-1ms,直到重传次数达到一定程度,发送端tcp就会认为网络或者对端主机出现异常,强行关闭连接

流量控制

简单来讲就是发送端利用接收端的发送过来的窗口大小来控制自己发送报文的速度

发送端不能过多依赖重传机制(就是一股脑全发出去,不管接收方有没有能力全部接受完,准备丢啥就传啥),因为依赖重传会导致电力、带宽等资源的浪费,网络传输也是需要消耗资源的

一般窗口大小就是2的16次方,但在如今64KB已经太小了,就需要有一个窗口扩大因子(这个窗口扩大因子M 就在tcp首部的选项中,所以实际窗口大小是 窗口字段的值左移M 位)

滑动窗口

tcp具有可靠性,就意味着一个报文,一旦把它发送出去了,在收到应答之前,这个报文都不会被发送方丢弃,这个丢弃的时间点,应该在收到应答之后。就意味着,发送的数据会持续留在发送缓冲区内部,直到收到应答报文

同时为了效率,发送端tcp往往会选择一次性发送一批报文,假设一个报文携带1460字节(初始序号这里假设为0),总共要发送9000字节,发送端会根据窗口大小(这里假设接收端剩余空间大小为4000字节),就会一次性发送三个报文(1460、1460、1080),序列号分别为0,1460,2920。发送完成之后,发送端tcp就会等待不再发送直到接收方传来ack

而滑动窗口,指的就是这个根据窗口大小,在发送缓冲区内划定的这个区域(就是这个大小为4000字节的区域)

每一次发送,都会发送滑动窗口内的一批报文

大致可以如图这样抽象理解,上面的管道就是发送缓冲区,滑动窗口就是其中的一部分区域

有了滑动窗口,发送缓冲区就可以划分为三个区域

滑动窗口的工作过程:

1、正常工作过程

最开始的时候

滑动窗口=min(ACK_win,真实发送的数据量)

ACK_win:对方发来的剩余接收缓冲区大小

start_win:滑动窗口起始位置

收到确认应答之后,比如收到2001,窗口的左侧就应该指向2001

而直接发送的数据范围应该以对方的接受能力为主,就代表右侧应该等于 起始位置+ ACK_WIN

这其实就是流量控制的底层实现

当滑动窗口大小为0,就是start==end,就是对方接收缓冲区剩余空间已经为0了

窗口大小也会变大,只要对方的剩余接收缓冲区变大

滑动窗口永远只会向右滑动(不管是滑动窗口的左指针还是右指针,跟算法里面的滑动窗口类似)

2、异常工作过程

接收方只有收到报文才会返回确认应答,就是说如果发送方收到6001,就是6001之前的报文都送到了

如果接收方没收到某一个报文,只能确认应答这个报文之前的序号(就是说如果上一个报文序号是4001,4001+1000(数据大小)=5001,但是后续没收到5001序号的报文,而是收到6001,接收端不能发送确认序号为7001,因为5001序号没收到,所以只会继续返回确认序号5001的ack报文)

就会导致主机A收到多个5001

只要达到3个,就会把这个一定丢失的报文给重传了

然后从回来的确认应答判断还丢了哪些

这就是快重传机制

如果应答报文达不到三个,触发快重传,就会进行超时重传

确认序号:序号之前的报文都已经确认收到

这种确认序号的定义,就会保证滑动窗口的左侧不会跳过任何没有经过确认的报文

发送完,应答之前,数据就一定会在窗口内部

收到应答之后,滑动窗口的数据就会划分到左侧区域,本质就是删除数据

滑动窗口有没有可能越界

可以把缓冲区当做一个环形数组

不会发生越界

滑动窗口就是流量控制的底层逻辑

拥塞控制

可以发现之前的策略 都是和另一端有关

其实除了考虑接收端的接受能力,还要考虑网络情况

为了应付网络情况,就有了拥塞控制

注意:只能解决能力范围以内的网络问题,如果是路由器坏掉这种问题,是没法应付的

丢包 —— 这就是一种网络问题

如果只有极少数报文丢包,就是正常情况,直接重传就好

如果丢了大量的报文,就可以判定网络出现问题了

就会认为是发生了网络拥塞问题(注意:tcp只能判定原因是这个,它只能推测出这一种情况)

比如大量报文积在了路由器

此时就不能直接进行重传了,因为会加重网络的拥堵情况

此时就要进行拥塞控制

如何进行拥塞控制

前提:网络已经拥塞了

它会先重发一点点报文(比如一个报文),如果没收到应答,就会继续发一个报文

如果收到了应答,就会持续增加报文数量

这就是 慢启动 机制

拥塞窗口

可以当做一个整数(报文数),如果超过这个数,就有可能会发生网络拥塞

它就是用来衡量网络拥塞的指标,用来衡量网络接收能力的指标

目前为止已经有三个窗口了

三个窗口的关系

滑动窗口=min(win窗口,拥塞窗口)

注:暂时不考虑发送报文数量少的情况,所以不考虑报文数量的影响

怎么做到拥塞时,控制发的报文数量的:缩小拥塞窗口的大小,就可以控制发送的报文的数量

慢启动:就是以指数级增长增加发送的报文数量(当然还要考虑对方的接受能力,不可能让它一直指数级增长)

至于为什么叫做慢启动:就是因为它前期很慢

而且后期速度恢复的很快 能够在可靠性和效率中寻找平衡点

他会有一个阈值(ssthresh),超过这个阈值,就会以线性方式增长

如果超过阈值之后线性增长时又遇到了网络拥塞,阈值就会减少成为此时拥塞窗口的一半,然后拥塞窗口的大小重新为1

拥塞窗口应不应该是变化的

拥塞窗口大小一定是变化的,因为网络健康状态是变化的,发生拥堵的上限一定是变化的

那一开始拥塞窗口是多大

拥塞控制就是一种为了保证可靠性的策略,但同时也考虑了效率问题的策略

延迟应答

它是为了提高 tcp传输效率而出现的机制

比如通过时间延迟,或者只有当发送方发来下一个报文达到某种数量时才进行应答

延迟的这段时间,应用层可能就拿走了接受缓冲区内一部分数据,使得可接收范围变大

延迟应答时,就有一定概率 给发送方更新一个更大的窗口大小

捎带应答

就是把要发送的应答,和要发送的数据一起发送过去

主要出现在:tcp双方互相发消息

是为了提高报文发送的效率

比如三次握手的时候,最后一次ack发出,客户端就会认为已经建立好连接,就可以捎带应答

面向字节流

就是读的次数和写的次数没有关系

区分报文完全靠应用层(所以就需要应用层的协议,比如HTTP,比如 json 的序列和反序列化)

面向数据报:就是写多少次,读就得多少次

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值