TCP协议

TCP协议全讲解

tcp报头

在这里插入图片描述

tcp在传输层有发送缓冲区和接受缓冲区,我们在用户级缓冲区调用的write,read,recv,send等接口,都是将用户级缓冲区的数据拷贝给发送缓冲区。

4位首部长度

计算的时候有基本的大小单位,单位4字节。所以tcp报头的长度大小是0到60字节。标准报头长度是20字节选项长度大小为40字节。

报头与有效载荷分离:固定长度+自描述字段

16位窗口大小

填充的就是接受缓冲区剩余空间的大小

客户端与服务器端控制发送数据的速度,防止服务器端的接受缓冲区被写满导致数据大面积的丢包。

客户端发送数据的速度是由谁决定?接受缓冲区剩余空间的大小

32位序号和32位确认序号

保证数据的按序到达。

发送缓冲区发送数据块的最后一个字符的下标就是序号

确认序号是对收到的报文的序号加一。

确认序号表示序号之前的数据,已经全部收到了

下一次发送要从确认序号之后的序号开始发送。

可能有的应答丢失了,但是之后的应答到来了,所以就不用重传了。允许应答丢失。

为什么要有确认序号?因为一个报文既可能是应答又可能是请求。

六个标记位

一个服务器是对多个客户端进行通信的

tcp收到的报文是有各种类型的。不同的类型决定了服务器要做不同的动作。

6个标记位存在的意义就是区分tcp报文的类型

ACK:确认序号是否有效。服务器对客户端应答时,这个标记位需要被置1

SYN:双方建立连接时,要进行三次握手,这个标记位被置

FIN:通知对方,本端要关闭了

**PSH:**提示接收端应用程序立刻从tcp缓冲区把数据读走。催促对方尽快把缓冲区数据读走。

**RST:**对方要求重新建立连接,我们把携带RST标识的称为复位报文段。tcp建立连接时也可能失败

在这里插入图片描述

客户端当ACK发送出去后,就认为三次连接建立好了。如果第三次应答丢失时,

服务器端当收到了ACK后,就认为三次连接建立好了

客户端与服务器认为连接建立不一致,使用RST标记位。

URG: 特殊情况下使用,紧急指针是否有效。允许数据插队,优先被读取。

这个位置被设置,16位紧急指针才有效,16紧急指针里面是数据的偏移量。

紧急数据只能带一个字节。

确认应答(保证可靠性)

客户端对服务器发送任何消息时,服务器都要给应答。

没有应答的数据,我们是无法保证可靠性的,所以最新的一条数据,是没有应答的,所以我们无法保证发出去的消息是100%可靠的。

我们只保证一个方向上的数据发送是可靠的,只要保证发送的数据收到了对方的应答即可。

但是客户端是怎么保证自己是否收到应答呢?一段时间,如果没有收到应答,客户端就认为数据丢失了。

没有收到应答之前数据继续保存在缓冲区中。

捎带应答

客户端与服务器端互发消息过程中,大部分的应答都是包含在响应当中,单纯只为了一个应答而响应的比较少。

当客户端与服务器端有大量消息发送时,我们没有收到应答时按理来说是不能发送下一个,但是这样效率太低。

实际上我们一次发送一批数据,服务器对每一个数据都应答即可。

客户端按顺序发送,服务器端不一定按照顺序接收。会发生数据包乱序问题,乱序是不可靠的一种。

发送缓冲区发送数据块的最后一个字符的下标就是序号

捎带应答,就是一个响应是应答又携带了数据。

连接管理机制

在这里插入图片描述

accept不参与三次握手,只是将底层建立好的连接拿上来。

tcp通信是基于连接的,建立和断开,三次握手和四次挥手。

为什么挥手是四次呢?建立连接时,服务器发送SYN是捎带应答在ACK当中的,建立连接服务器需要立即响应。

但是当断开连接时,服务器可能消息还没有发送完,所以再发送FIN时会有延迟。

连接建立成功是跟accept没有关系,三次握手是双方操作系统自动完成的

这些连接建立好了之后,被没有被上层accept接受时,需要在底层的连接队列维护起来。

listen的第二个参数,描述的是底层建立好的连接队列的最大长度。

这个参数不能太长:当链接太多时,上层不能及时的将连接拿上去,但是又不断有新的连接到来,下层需要长时间的维护一个长的全连接队列。

第二个参数加一就是在连接队列内最大的连接数。当全连接队列已经满了的时候,服务器端将不在接受三次握手中的最后一次ACK,让连接不能建立成功。所以服务器端是SYN_RCVD。但是服务器端不会长时间维护这个半连接状态。半连接队列的节点不会长时间维护,过段时间就会被释放掉。

为什么要三次握手?

通信的双方至少跟对方进行了一次收发消息,可以保证这次tcp连接的可靠性。验证全双工通路是否通畅

SYN洪水:目标是通过发送大量的半连接请求来耗尽服务器的资源。攻击者通过向目标服务器发送大量SYN请求,但不完成TCP三次握手中的后续步骤(即不发送ACK确认),导致服务器持续等待连接完成,从而占用大量系统资源。这种攻击如果足够频繁,就会导致服务器无法响应其他合法请求,进而造成拒绝服务。

连接假如没有建立好,维护的成本在客户端,不是在服务器端。

奇数次握手可以确保一般情况握手失败的连接成本是嫁接在客户端。

四次挥手

TIME_WAIT状态:当服务器主动断开tcp连接时,会进入TIME_WAIT状态,等待一段时间之后自动释放。

等到双方还未收到的消息,收到。提高四次挥手的容错性。

连接没有彻底断开,IP和port正在被使用,所以服务器重启时可能会bind失败,因为ip和端口号还在被使用。

我们不允许服务器重启时还要这么长时间。

setsockopt()

保证服务器在time-wait状态下立即重启。

超时重传

主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;

主机A未收到B发来的确认应答, 也可能是因为ACK丢失了;

如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发;

主机B可能会收到重复报文,但是报文有序号,可以直接去重。

超时时间:

最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.但是这个时间的长短, 随着网络环境的不同, 是有差异的.如果超时时间设的太长, 会影响整体的重传效率;如果超时时间设的太短, 有可能会频繁发送重复的包;

TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.
Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时
时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2500ms 后再进行重传.
如果仍然得不到应答, 等待 4
500ms 进行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接.

流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,
就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
窗口大小字段越大, 说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
发送端接受到这个窗口之后, 就会减慢自己的发送速度;
如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数
据段, 使接收端把窗口大小告诉发送端.

属于可靠性的一种,也能提高效率

第一次的时候,怎么保证发送的数据量是合理的?

三次握手中,双方也交换了报文,已经协商了双方的接受能力。

第三次握手的时候可以携带数据。

滑动窗口

在这里插入图片描述

滑动窗口是在发送缓冲区内的一部分

因为有滑动窗口的区域,才可以向对方发送大量的tcp报文

滑动窗口的大小:

如果丢包了怎么理解滑动窗口?

当一个ack丢了,但是它之后的报文返回了确认信息,也就是序号比这个数据序号大的回来应答了。

如果数据3001丢失,那么4001,5001的确认序号只能填写2001

确认序号的定义保证了滑动窗口线性的连续向后更新,不会出现跳跃的情况。

在这里插入图片描述

滑动窗口大小变化

不会向左移动,只能向右,移动的时候大小会变化。

大小是动态变化:

右边不变,左移动。对方上层不取数据。

左右都移动,可以范围扩大也可以缩小。

窗口左边的大小:根据确认序号设置

窗口右边的大小:确认序号 + 对方窗口的大小。

快重传

当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 “我想要的是 1001”
一样;
如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已
经收到了, 被放到了接收端操作系统内核的接收缓冲区中;

延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小.
假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来;
如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高. 我们的目标是在保证网络不拥塞的情况下尽量提高传输
效率;
那么所有的包都可以延迟应答么? 肯定也不是;
数量限制: 每隔N个包就应答一次;
时间限制: 超过最大延迟时间就应答一次;
具体的数量和超时时间, 依操作系统不同也有差异; 一般N取2, 超时时间取200ms

拥塞控制

如果发送数据,出现问题,不仅仅是对方主机出现问题, 也可能是网络出现了问题!

如果通信双方出现了大量的数据丢包问题,tcp会判断网络出现了问题。

我们不能立即对报文进行超时重发!!会加重网络的拥塞。

主机判断网络的健康程度,超过拥塞窗口,会引发网络拥塞,否则不会。

拥塞控制的策略:

TCP引入 慢启动 机制, 先发少量的数据, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据

此处引入一个概念程为拥塞窗口
发送开始的时候, 定义拥塞窗口大小为1;
每次收到一个ACK应答, 拥塞窗口加1;
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗
口;

滑动窗口的大小 = min(窗口大小,拥塞窗口)

面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
调用write时, 数据会先写入发送缓冲区中;
如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出
去;
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
然后应用程序可以调用read从接收缓冲区拿数据;
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可
以写数据. 这个概念叫做 全双工
由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:
写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次
read一个字节, 重复100次;

实际上就是将上层一个或者多个报文同时放在缓冲区中,每次发送时都不会按照一个报文发送,而是可能会发送一个半报文,这就要求对方的应用层需要一个一个的处理报文。

数据包粘包

那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界.
对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request结构, 是固定大小的, 那么就从缓冲
区从头开始按sizeof(Request)依次读取即可;
对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔
符不和正文冲突即可);
思考: 对于UDP协议来说, 是否也存在 “粘包问题” 呢?
对于UDP, 如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用
层. 就有很明确的数据边界.
站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现"半
个"的情况.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值