文章目录
- TCP协议
-
- [TCP 协议段格式](#TCP 协议段格式)
-
- 确认应答机制
- [16位窗口大小 下定义](#16位窗口大小 下定义)
- 32位序号和32位确认序号
基于UDP应用场景
UDP,tcp这样的协议根本不是直接谈UDP。tcp的应用场景,一定是上层写了应用层协议,所以才有UDP协议的应用场景。
比如http
TCP协议
传输层最重要的协议,几乎没有之一
因为TCP基于通信时保证可靠性,对于高效传输也有一定策略。
TCP协议是目前应用层协议使用时常见的协议。
比如网络版本计算器,底层是TCP套接字。
http底层就是TCP协议,用的也是TCP套接字。
TCP叫传输控制协议
为什么叫传输控制,从何而来
TCP在OS内部存在自己的发送和接受缓冲区。
每建立一个链接的的时候,此时OS内部就要为该链接创建发送缓冲区和接受缓冲区
应用层也要定义缓冲区来接受用户输入,和输出结果给用户--用户级缓冲区
计算器为了能接受请求定义string in_buffer
通过read把数据读上来。
当上层调用write,read接口时,本质不是把数据发送到网络中!
那是什么呢?
比如发送,发送的数据拷贝到TCP的发送缓冲区,应用层就认为数据已经交给了系统。
至于数据什么时候发送,发 送多少,出错了怎么办?
完全由OS,TCP协议自主决定
所以write,read,recv ,send与网络相关的发送接口,本质不是发送函数而叫做拷贝函数
好像在哪里听过啊?
以前讲文件的时候不就是这样吗?
往文件里写也用的wirte接口,所以当时说C语言提供缓冲区,也是用户级缓冲区,我们把数据写到文件里,写到OS内部,要通过fd写入的,每个文件都有自己的文件缓冲区,所以应用层读写文件时把数据写到文件里,本质就是把数据拷贝到内核的文件缓冲区里,至于数据什么时候刷,刷多少,整个刷新细节你不用关心,是由OS和磁盘交互的,今天看来磁盘和网络有区别吗?
把磁盘设备换成网卡,不就完成数据发送,IO吗,冯诺依曼体系结构里文件是IO,往网络里写本质不就是在硬件层面上把内存里的数据写到网卡上,也是IO,大家在理念上是一致的,更何况Linux一切接文件。
从今天开始对于网络的理解,就可以这么认为了
我的应用层只负责对收上来的数据进行协议处理,把报文和报文分开,诸如反序列化,变成结构化数据,根据协议对数据做处理,把数据写好的响应发给TCP缓冲区应用层的工作就完了,至于数据什么时候发由TCP决定。
通信双方都支持TCP协议,所以CS的地位是对等的。所以一方学懂了另一方一样。
应用层把数据本质拷贝到发送缓冲区。
至于数据怎么发,其实把传输层发送缓冲区
的数据导到对方的接受缓冲区里,本质也是拷贝!
只不过这个拷贝工作需要通过网络而已,通过网络可能会出错,所以需要很多策略。
接收方通过read调用时会阻塞,由于接受缓冲区没有数据,有数据则资源就绪了。
后面网络和文件揉在一起说说
解释为什么UDP或Tcp 只有同样一个FD既可以读也可以写,因为一个fd配套两个缓冲区,所以可以即读又写了。-- 全双工
这就是TCP缓冲区的理解。
TCP下层还有协议,我们把数据交给下层,并不是直接发送给对方。
1。传输层的接受缓冲其实就是内存空间
2。OS为了管理对应的内存其实是把整个内存想成一个个4KB空间,所以OS有这么多内存块,OS怎么知道哪些已经被占用了那些没有,那些被锁定了,那些过期了?
OS就要对内存块管理。
为了管理内存所以每个内存块都要有strcut page这样的结构
所以接受发送缓冲区,无非就是多个内存块即strcut page构成的。
OS中用数组把100多万个page管理起来,对内存管理转为对数组的增删查改。
打开一个网络相当于得到一个文件描述符,底层一定有Strcut file对象,Strcut file对象以前指向磁盘,今天网络Strcut file对象指向网络设备,每个Strcut file后面跟上两个缓冲区,由多个4kb构成的,此时上层不变下层直接切换成网络这样的效果。
为什么突然扯到这里呢?
问题
所以为什么叫传输(能理解)控制协议(能理解)?
控制是什么意思?
应用层把数据经过write拷贝到发送缓冲区里,至于发送缓冲区里的数据什么时候发,发多少,本质就是在控制如何发送的问题。
这些工作由TCP协议自主决定,所以把他叫做传输控制协议。
为什么提供发送缓冲区呢?
当前对端来不及接受了,发送端怎么知道呢?用户一直把数据往缓冲区写,因为有缓冲区的存在,即便来不及接受了,但对于应用层来讲,可能并不影响应用层继续向OS拷贝,数据拷贝多了,发送缓冲快满了,OS就让对方接受缓冲区快点拿数据了。
TCP是具有接受发送缓冲区,进行全双工通信的,进行数据发送控制的一种协议。
TCP 协议段格式
应用层 请求和响应
传输层 数据段
网络层 数据报
链路层 数据帧
问题
1、报头和有效载荷如何分离,如何交付给上层?
TCP协议基本结构分三部分
TCP报头前20字节就是TCP标准报头
TCP支持对应的选项--我们忽略但存在
第三部分,TCP协议的有效载荷
如何交付给上层?
前两个字段,端口交付给上层目标进程
对TCP报文向上交付给进程
一行是4字节,0-31
标准报头一共五行,所以标准报头是4x5=20字节
报头和有效载荷如何分离?
当读取TCP数据时,在缓冲区里把前20字节拿出来,报头信息全拿到了,标准报头全拿到了他也是约定长度。
TCP报文里有一个字段,4位首部长度
凡是报文里介绍首部长度,他就真的是。
表示报头总长度,你不是说了标准报头是20字节吗,因为还包含了选项,选项也可以不带。
4位首部长度 【0000,1111】【0-15】
全写成1表示数字也就是15,怎么可能是20 呢?
是不是有问题呢?
不是,因为4位首部长度计算的时候,有基本的大小单位:4字节
则表示长度范围【0,60字节】
所以选项最多是40字节。
如果不带选项,则四位首部长度设为X
x * 4 = 20 => x = 5 = 0101
4位首部长度准确的把报头从整个报文里去掉
报头和有效载荷如何分离我们怎么做呢
实际上先读取前20字节,固定长度,之后根据4位首部长度计算出实际总大小再减去20剩下的就是选项的大小,没有的就不读了,剩下的就是数据
所以如何分离?
固定长度(前20字节)+ 自描述字段
你收到一个数据能不能按照指定字节数对数据做截取呢?
你直接把数据收上来,截取前20字节,整个报文也是结构体,地址强转成结构体指针直接从里面提取4位首部长度,这个4位长度描述是自身的报头大小,所以他叫自描述字段。(我们网络版本计算也带有有效载荷长度,也叫做自描述字段)
固定长度+自描述 的方式 能把标准报头和选项全部读上去,因为标准报头就是20字节,剩下的就是数据,所以能作分离。
1、报头和有效载荷如何分离,如何交付给上层?
搞定
整个报文也是结构体,他所谓的封装是把报头结构体变量形成的对象内容拷贝到数据前面。
能封装,也能拆开
下一个问题
16位窗口大小
左边客户端,有应用层和传输层/TCP
TCP有自己的接受缓冲区和发送缓冲区。
对于服务端也是这个结构
此时应用层构建Http请求
把数据通过write结构写到发送缓冲区里,传输层要发送这个数据给对方,此时要给发送的数据添加TCP报头
没来得及发送的就放到发送缓冲区里,可能有多个Http请求
封装报头经过底层交到对方的接受缓冲区里时,对方收到这个报文要进行报头有效载荷分离,套接字创建出来都要有自己的接收缓冲区,至此去掉报头的Http有效载荷请求放到接受缓冲区中。
问题
你心里要特别清楚,客户端和服务器基于tcp协议进行通信时,互发消息的时候,发送的可是完整的tcp报文哦,即一定携带完整的tcp报头
不管课件如何简写,双方通信时发数据发确认,当你看到数据时可不仅仅是数据他一定要涵盖tcp报头+数据,响应同理
看起来是发syn+ack,本质上是把报头特定的属性字段设置了,归根结底所有请求和响应都要基于tcp报头为载体
发起Http请求时一定要想到请求包含报头一大堆东西的。
tcp一样,报头必须完整
换句话说,C给S发消息时基于套接字,上层来看都是通过fd来读写的。
所以你打开多个套接字建立多个连接每个连接都要有一对收发缓冲区
双方通信时发送的报文携带完整报头
tcp协议要保证可靠性
不可靠的表现呢?
数据传输出现重复,乱序,丢包
发送的过程本质是把数据拷贝给对方,对方接受缓冲区是固定大小的,所以他的剩余空间就少了,如果应用层就不想读,此时发送方应用层给对方一直发消息,客户端和服务器双方进行通信时,客户端不清楚服务器接受能力的,所以客户端一直给服务器发消息时,服务器来不及接受,最终可能导致服务器接受缓冲区被写满,客户端又不知道就继续发,最后导致接受缓冲区没空间导致数据丢包。
所以CS通信时,服务器接受能力很少的时候我们要让客户端发慢点或者干脆不发了,
由发送方向服务端发消息,通过控制发送数据的速度让对方来得及接受从而规避大面积丢包的情况,我们称为流量控制。
tcp注定要解决这个问题
tcp可靠性里有一种叫做丢包重传
如果报文丢失了,发送发可以对服务器补发的。
如果对方来不及接受了出现大面积丢包,tcp害不害怕丢包,不害怕他有重传啊。
可以吗这样,可以。
但有更好的方案啊。
虽然你有重传,但是这样不合理,报文经过千里迢迢到了对端,我没有明显错误却要把我丢弃掉,所以他不合理。
我们不能让他启用重传策略,浪费曾经的发送,效率必定不高
既然来不及接受,那就要发送慢一点
tcp保证可靠性,凭什么说他保证可靠性?
最基本的一个特点:确认应答(最重要的机制)
比如我们两个相隔千里,老师说的话同学听到没听懂没老师不知道,所以老师经常说听懂没,听懂扣1,要求你给我响应的过程就叫做确认应答。
所以暂时认为客户端给服务器发送任何消息的时候,服务器都要对收到的消息进行响应
那你能保证你扣的6,老师一定能看到吗
确认应答机制
CS双方地位对等
如果C给S发消息,即便服务器没有任何话说,但服务器至少要对消息进行一次确认应答。
服务器想给客户端发消息,客户端OS也要立即给服务器确认应答。
这样就能保证对于C来讲,客户端刚刚发的消息服务器收到了。
基于确认应答保证了客户端到服务器方向的可靠性
同理服务器给客户端发消息,客户端做应答保证服务器到客户端方向数据的可靠性
重谈通信过程
客户端向发送消息交给服务器,服务器收到了这个消息,TCP协议要立即给对方应答
问题
无论发送消息,确认应答最后都是tcp报文,无论发的是什么消息什么应答,一定携带完整的tcp报文或者是报头
发送方 发送的量非常大,也很快
导致对方来不及接受,所以需要流量控制,无非就是让客户端发送数据慢一点,要发慢一点依据是什么?慢多少啊?
靠谁来决定呢?
由对方接受缓冲区当中剩余空间的大小决定
对于发送方来讲,发送速度由对方的接受缓冲区中剩余空间的大小决定!!!
剩多少空间让我知道了,我就可以根据你剩余空间最多发送把你缓冲区打满的数据,发满了我就不发了。
关键在于,客户端是给你发送数据,客户端怎么知道你服务器端剩余空间的大小呢?
我们tcp基于确认应答机制的,你发了一个报文我要给你响应,别忘了请求和响应都要是完整的报文,所以发一个消息对方会给我响应的,我收到响应才发 第二个目前认为。
收响应的时候,我就收到来自服务器给客户端发来的响应,响应中16位窗口大小填充就是我服务端接受缓冲区剩余空间大小。
得知对方接受能力是多少,客户端就知道应该最多发多少数据了。
这就叫16位窗口大小。
16位窗口大小填的应该是谁的接受缓冲区剩余空间大小呢?
客户端给服务器发,要进行流量控制,服务器有没有可能给客户端发消息呢?
tcp是全双工的。
所以从客户端到服务器端要进行流量控制,那么从服务器到客户端发送要不要也进行流量控制呢?
要
所以双方互发消息,互相确认应答,双方会出现很多tcp报头往来,双方根据报头里16位窗口大小,互相得知对方的接受缓冲区剩余空间大小,双方就可以进行互相流量控制。
16位窗口大小 下定义
我要填充的报头一定是我要给对方发的。
所以16位窗口填写的是自己的接受缓冲区中剩余空间的大小!!
这么多报头传输效率会不会很低?
后面有滑动窗口
(应用层比如Http有主从关系,你是客户端你只能向服务器发起请求
tcp双方地位对等,即你能向我发送报文我给你确认应答,左侧右侧都要互相有流量控制。
)
32位序号和32位确认序号
tcp说自己有确认应答机制,我们该如何理解确认应答呢?
为什么确认应答是保证可靠性最重要的一个核心点呢?
思考一个问题
这个世界,存不存在100%可靠的网络协议?
例子,我跟你不是面对面,相隔100米
我给你发了个消息,我们去吃饭吧
你回了个好的,这样能保证我给你的消息你是收到了的,但是你怎么知道刚刚说的好的被我收到了呢?
那我要保证你刚刚给我发来的好的消息,我也想让你知道我已经收到了,怎么办呢?我再给你回收到了你的好的了
可是当我给对方发了收到了你的好的了,确实能保证上一次的好的消息被我收到了,可是最新的消息怎么保证对方也收到了呢?那对方再给你个应答吧, 最新的消息怎么保证对方一定收到了呢?
1。站在我个人角度只要我收到应答,我就能保证我最近一次发送的消息对方收到了。
同理对对方也一样
2。没有应答的数据,我们无法保证可靠性,所以最新的一条消息,不管是我给对方说,还是对方给我说永远存在最新的一条消息,是没有应答的,所以在人类世界里几乎无法保证发出的消息时100%可靠的!
所以世界上不存在100%可靠的协议的
虽然整体不存在,但是局部上呢?
我刚给你发的消息,我给你应答,对你来讲,你并不确定这个应答我是否收到,对我来讲我只要收到对应的应答,我能保证从左向右方向上数据的可靠性。
对右边来讲,右边给左边发的消息只要右边也收到应答,他无法保证 最新的消息被自己收到,但只要右边收到了应答,就能保证从右向左的可靠性
虽然最新的一条消息没有应答,无法保证最新的一条消息可靠,但是局部上最新消息之前的消息我们其实是可以保证双方在两个方向上的可靠性。
这样说有点抽象,直接说tcp真实的方案
左C,右S
tcp最基本,最原始的通信过程
所以刚刚场景中,除了最新的消息,上面之前每一个报文的都有应答
所以之前的消息能保证各个方向上的可靠性
正常情况不会存在这样的场景的
我给你发个消息,你给我应答了,收到了,站在我的角度呢我就不再给你发消息了。
我们没必要对应答再作应答,实际上是鸡生蛋,蛋生鸡的问题,无穷尽了。
CS通信最终要的是保证两个方向上的可靠性
所以C给S发一条消息,tcp数据
S要合适的时候对消息进行应答,因为是客户端给服务器发消息,我要保证消息被对方收到了,我是主动的对方被动,我只要保证我发的消息被对方收到了,这不就达到了宏观层面上我们两个通信的目的吗。
所以我给对方发消息,对方只要给我应答,我收到应答时能保证我发的消息被对方收到了,这就叫保证客户端到服务器方向上的可靠性。
我们不需要再对应答再给S进行响应。
如果服务器要给客户端发消息tcp数据本质也是完整的tcp报文。
服务器是主动的一方,他要给客户端发消息,本质的目的就是想确认把这个数据发送给C,包括C给我做应答,你也是告诉对方你刚给我发的消息我已经收到了。
所以C也给S发了个应答
虽然C不能确定应答有没有被S收到,暂时我们不管,但假设S收到了应答,服务器立马就意识到我刚刚发的消息对方也收到了,所以只要收到应答,我们从右向左的可靠性也能保证。
所以要发送的一个数据,对应配套的收到应答,就能保证两个朝向上的可靠性。
所以第一阶段总结论