三握,四挥,滑动窗口会了吗?图解带你20分钟搞定TCP传输层

图解带你20分钟搞定TCP/IP协议

第二章 网络之TCP/IP协议


本节重点

1.理解传输层的作用, 深入理解TCP的各项特性和机制 2.对整个TCP/IP协议有系统的理解 3.对TCP/IP协议体系下的其他重要协议和技术有一定的了解

一、1.应用层

我们之前编写完了基本的 java socket ,要知道,我们之前所写的所有代码都在应用层,都是为了完成某项业务,如翻译等。关于应用层,我们在讲解完毕基本的 TCP/IP 协议之后,我会单独来写一篇博客来介绍。

二、1.传输层

传输层负责数据能够从发送端传输接收端.

2.1 再谈端口号

端口号(Port)标识了一个主机上进行通信的不同的应用程序;
在这里插入图片描述

在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);
在这里插入图片描述

端口号范围划分
0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.
认识知名端口号(Well-Know Port Number)
有些服务器是非常常用的, 为了使用方便, 人们约定一些常用的服务器, 都是用以下这些固定的端口号: ssh服务器, 使用22端口
ftp服务器, 使用21端口telnet服务器, 使用23端口http服务器, 使用80端口https服务器, 使用443
我们自己写一个程序使用端口号时, 要避开这些知名端口号.
两个问题
1.一个进程是否可以bind多个端口号?
2.一个端口号是否可以被多个进程bind?

2.2 UDP协议

UDP协议端格式
在这里插入图片描述

16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度; 如果校验和出错, 就会直接丢弃;
UDP的特点
UDP传输的过程类似于寄信.
无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;
不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;
面向数据报: 不能够灵活的控制读写数据的次数和数量;
理解 UDP 的 “不可靠”
在这里插入图片描述

面向数据报
应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并; 用UDP传输100个字节的数据:
如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节;
UDP的缓冲区
UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;

UDP的socket既能读, 也能写, 这个概念叫做 全双工
UDP使用注意事项
我们注意到, UDP协议首部中有一个16位的最大长度. 也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部).

然而64K在当今的互联网环境下, 是一个非常小的数字.

如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装;
基于UDP的应用层协议
NFS: 网络文件系统
TFTP: 简单文件传输协议
DHCP: 动态主机配置协议
BOOTP: 启动协议(用于无盘设备启动)
DNS: 域名解析协议
当然, 也包括你自己写UDP程序时自定义的应用层协议;

2.3 TCP协议

TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制;
TCP协议段格式
在这里插入图片描述

源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去; 32位序号/32位确认号: 后面详细讲;
4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4
= 60
6位标志位:
URG: 紧急指针是否有效 1为紧急标志位
ACK: 确认号是否有效
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
16位窗口大小: 后面再说
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含
TCP首部, 也包含TCP数据部分.
16位紧急指针: 标识哪部分数据是紧急数据;
40字节头部选项: 暂时忽略;

2.4 TCP特性1:确认应答(保障TCP稳定的核心机制)

在这里插入图片描述

2.5 TCP 特性2:超时重发

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210418233247542.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0lMT1ZFTVlERUFS,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210418233329141.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0lMT1ZFTVlERUFS,size_16,color_FFFFFF,t_70#pic_center)

2.6 TCP 特性3:三次握手,四次挥手

![在这里插入图片描述](https://img-blog.csdnimg.cn/20210418233617948.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0lMT1ZFTVlERUFS,size_16,color_FFFFFF,t_70#pic_center)

在这里插入图片描述
在这里插入图片描述

2.7 TCP 特性4:滑动窗口

**窗口大小指的是无需等待确认应答而可以继续发送数据的最大值.上图的窗口大小就是4000.个字节(四个段). 发送前四个段的时候,不需要等待任何ACK,直接发送; 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推; 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;** ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210418233800299.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0lMT1ZFTVlERUFS,size_16,color_FFFFFF,t_70#pic_center) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210418233912601.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0lMT1ZFTVlERUFS,size_16,color_FFFFFF,t_70#pic_center)

2.8 TCP 特性5:流量控制

**流量控制**
   接收端处理数据的速度是有限的.如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度.这个机制就叫做流量控制(FlowControl);

接收端将自己可以接收的缓冲区大小放入TCP首部中的"窗口大小"字段,通过ACK端通知发送端;
窗口大小字段越大,说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后,就会减慢自己的发送速度;
如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端.

接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息;
那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?
实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位

2.9 TCP 特性6:拥塞控制

拥塞控制

虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据.但是如果在刚开始阶段就发送大量
的数据,仍然可能引发问题.
因为网络.上有很多的计算机,可能当前的网络状态就已经比较拥堵.在不清楚当前网络状态下,贸然发送大
量的数据,是很有可能引起雪上加霜的.

TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数
据;
此处引入一个概念程为拥塞窗口
发送开始的时候,定义拥塞窗口大小为1;每次收到一个ACK应答,拥塞窗口加1;
每次发送数据包的时候将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度,是指数级别的."慢启动"只是指初使时慢,但是增长速度非常快.
·为了不增长的那么快,因此不能使拥塞窗口单纯的加倍.
此处引入一个叫做慢启动的阈值
·当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长

当TCP开始启动的时候,慢启动阈值等于窗口最大值;
·在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;

少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案.
TCP拥塞控制这样的过程,就好像热恋的感觉

2.10 TCP 特性7:延迟应答

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

2.11 TCP 特性8:捎带应答

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是"一发一收"的.意味着客户端给服务器说了"How are you",服务器也会给客户端回一个"Fine, thank you";
那么这个时候ACK就可以搭顺风车,和服务器回应的"Fine, thank you"一起回给客户端

三.TCP的其他问题

3.1 面向字节流

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

3.2 沾包问题

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

3.3 TCP异常情况

进程终止:进程终止会释放文件描述符,仍然可以发送FIN.和正常关闭没有什么区别.机器重启:和进程终止的情况相同.
机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset.即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在.如果对方不在,也会把连接释放.
另外,应用层的某些协议,也有一些这样的检测机制.例如HTTP长连接中,也会定期检测对方的状态.例如QQ,在QQ断线之后,也会定期尝试重新连接.

四. 总结

4.1为什么TCP这么复杂?

因为要保证可靠性,同时又尽可能的提高性能.
可靠性:
校验和序列号(按序到达)确认应答
超时重发 连接管理 流量控制 拥塞控制
提高性能:
滑动窗口 快速重传 延迟应答 捎带应答
其他:
定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)基于TCP应用层协议
HTTP
HTTP SS SHT elnet FTPS MTP

4.2 TCP/UDP对比

我们说了TCP是可靠连接,那么是不是TCP一定就优于UDP呢?TCP和UDP之间的优点和缺点,不能简单,绝对的进行比较
TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;
UDP用于对高速传输和实时性要求较高的通信领域。例如,早期的QQ,视频传输等.另外UDP可以用于广播;
归根结底, TCP和UDP都是程序员的工具,什么时机用,具体怎么用,还是要根据具体的需求场景去判定.用UDP实现可靠传输(经典面试题)
参考TCP的可靠性机制,在应用层实现类似的逻辑;例如:
引入序列号,保证数据顺序;
引入确认应答,确保对端收到了数据;
引入超时重传,如果隔一段时间没有应答,就重发数据;

猜你喜欢

转载自blog.csdn.net/ILOVEMYDEAR/article/details/115831038