绪论
“没有那么多天赋异禀,优秀的人总是努力翻山越岭。”本章主要讲到了再五层网络协议从上到下的第二层传输层中使用非常广泛的Tcp协议他的协议字段结构,通过这些字段去认识其Tcp协议运行的原理底层逻辑和基础。后面将会再写一篇Tcp到底是通过什么调高运行效率的,敬请期待!
话不多说安全带系好,发车啦(建议电脑观看)。
TCP协议字段
TCP协议段格式:
此处只需要对里面的字段内容有个印象,下面将会对这些字段进行逐渐解析。
将Tcp协议结构化,在代码中tcp协议结构:
tcp的数据发送模式:
- 串行发送:
- 并行发送数据:
对于上面这种方法:server收到的报文顺序可能是乱序的(因为在网络中传输不能确定到达的先后顺序)
为保证按序到达就有了:
1. 32位序号:
接收方就可以根据报头中的序号,排序组合(每个报文都是带有自己的序号的!)
而返回的确认信息也需要有序号,也就是报头中的
2. 32位确认序号
它一般设置成对应的32位序号+1
作用:
- 通过确认序号告诉用户已经收到了那些数据(返回应答是填写确认序号),它也能判断那些报文丢失。
- 通过确认序号也能知道返回到那个具体位置
(先记住即可后面还会再底层讲解)
都是表示报文的序号,为何要把32位序号和32位确认序号分开
因为这样就能实现捎带通信,也就是Server将应答和信息存放到一起发送给Client客户端
再所以因为他既是数据又是应答,所以就需要有两个序号来分别代表数据和应答
也就有了:
捎带应答
捎带应答就是通过序列号和确认序列号实现把数据和应答合并成一个报文进行发送
用括号圈起来的(ACK和信息) 他们直接通过同一个tcp报文就发送回来了
总结:tcp保证可靠性但不仅仅保证可靠性,为了进行可靠性的同时还要提高效率(并行发送、捎带应答)
3. 4位首部长度
四位首部长度的单位是:4byte
又因为有4位所以换成10进制长度就有16个也就是:[0,15] 再 * 4(基本单位) ,得到tcp报文最长为60 byte
通过他确定就能确定有效载荷的长度,当得到报文长度再减去报头前的固定长度(20byte) 就得到了有 效载荷(数据的长度)的最长长度40byte
如果tcp长度是20,报头固定长度20byte(也表明没有有效载荷)
如果tcp长度是40,报头固定长度20byte,有效载荷数据就是20byte
报头有效载荷的分离是通过4位首部长度进行把报头和有效载荷区分出来,再将有效载荷向上交付是通过存的源和目的端口号来找到对应所需数据的软件位置。
当Client发送大量数据,但Server读取不够快就会导致接收缓冲区被填满(当装满后装不下的数据将会被丢弃!)
所以若接收方来不及接收了,发送方就不要再发送了,就需要进行流量控制(OS控制tcp协议来完成,传输控制协议(流量控制…))
如何进行流量控制:发送方就需要得知接收方的接受能力(接收缓冲区的大小)
4. 6位标记位
serve在接收信息时,一定会收到各种各样的不同类型的tcp报文
所以对于不同的报文就需要有不同的类型进行区分和分别的使用!
所以才有各种标记位
例:ACK就是应答类型的报文!
具体的标记位:
1.ACK:确认报文接收到了
2. SYN:握手请求(建立链接)
3. URG:紧急报文(不在通过序号进行排序)
4. PSH:push推送,告诉对方,尽快进行把数据向上交付(指令服务)
5. RST:reset重置,连接重置
虽然说tcp可靠的,但并不是说保证发送数据100%成功,而是数据发送出现问题了/发送成功了/发送失败了自身知道,并提供解决方案。
RST使用的例子:
上图含义:在tcp三次握手中若第三次握手的ACK丢失后,客户是无法得知的因为客户认为的是只要把ACK报文发送过去就握手完成建立链接了,所以此时本质是没有链接上的,当客户发送信息给服务器时,服务器就会告知客户端并没有链接成功(也就是会发送设置了RST标记位的报文),这就是发送RST的情况,也就是所谓的链接不一致,需要重置连接。
tcp的三次握手就是赌最后一个报文对方收到了,也就表明了tcp的三次握手是不一定成功的!!
6. 16位紧急指针
它代表的是:需要紧急处理的数据在有效载荷中的偏移量
这样就能优先找到需要紧急处理的数据,并且紧急数据的大小只能是一个字节
处理紧急指针的方法:
当使用recv函数时对flags字段进行设置MSG_OOB:
- recv参数flag为0时默认为阻塞式读取常规数据
- 选项为MSG_OOB时就是收到接收紧急数据(又称带外数据:正常通信外的数据)选项。
应用场景:需要加急处理的情况
- 终止或暂停上传行为
- 服务器的检测管理
7. 16位窗口大小
用来进行流量控制的字段
- 发送条件不满足,发送方就会写满了发送缓冲区,进程就要阻塞。
- 16位窗口大小,OS就会填写接收方TCP协议字段中的接收缓冲区剩余的空间大小,然后返回给发送方,这样发送方就知道接收方的接收能力,接收方就会只会发送小于16位窗口大小的数据。
如何确定接收方接收到消息:
当发送消息后,接收方会返回一条接收成功 应答消息 这样只要收到应答就能100%保证历史最近一条数据被对方收到(确认应答!)
Tcp滑动窗口:
tcp的起始序号:起始序号是由tcp协议进行随机生成的
序号 = 起始序号 + 真实序号
附:在tcp通信之前,会进行三次握手交换tcp报头,在这过程中随机序号双方也就能协商好。
使用随机序号就能减少历史报文、以及黑客入侵的影响。
滑动窗口过程:
既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了).
并发大量暂时用不到的ACK的数据段进行提前预留,当要使用时就能直接使用减少发送间所需的时间,从而提高效率的解决方案
收到第一个ACK后(应答), 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推。
操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉。
具体如下图:
上图白色窗口就是所要发的数据,可以把它理解为一个队列当数据输出后另外一个数据进来,也就是往右移的过程
而移动的本质其实也就是数组中指针(数组下标)的移动。
滑动窗口的大小:
由对方的接收能力决定(后面会讲 其实是对方的接收缓冲区16为窗口大小的大小和当前网络的拥塞窗口大小决定)
滑动窗口如何更新:
通过接收方返回win_start(开始放数据的位置下标值) 和 win滑动窗口的大小(接收方设定返回)
1. win_start = 确认序号(序号 + 1)
2. win_end = win_start + win(偏移量)
最开始每发送数据第一次滑动窗口的大小:同样是在三次握手期间协商的!
滑动窗口是会变小的,当只进行确认数据,而不取数据时,这样滑动窗口内的接收能力就会不断变小。
因为win_start是不断右移,而因为用户没有读取数据(返回win = 0),win_end指针就不会移动,所以win值就会变小
所以滑动窗口也是会变成0的,同理当win_start移动到win_end处是win就是0
滑动窗口也是能变大的,用户当把收到数据都获取(就有空间了就修改win),这样窗口就会扩大接收能力,这样的话win_end就会右移,从而让win窗口变大会,变成协商好的原窗口的大小。
窗口探测机制:
上述滑动窗口的变化也就是流量控制的解决方案:
1. 接收方接收能力弱时:发送慢
2. 接收能接收能力强时:发送快
接收端如何把窗口大小告诉发送端呢?
回忆我们的TCP首部中, 有一个16位窗口字段, 它就是存放了窗口大小信息
那么问题来了, 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么? 实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位;
会不会滑动到越界:
其实发送缓冲区设计成环形结构(因为前面已经发送的数据其实是可以被覆盖的!)
附:
确认序号的序号代表着该序号之前的数据全部收到了
滑动窗口的报文丢失了怎么办?
当发送的报文丢失时:
通过查看ACK的确认序号是否完整:
本章完。预知后事如何,暂听下回分解。
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量计算机网络细致内容,早关注不迷路。