当前位置:   article > 正文

更深层次理解传输层两协议【UDP | TCP】【UDP 缓冲区 | TCP 8种策略 | 三次握手四次挥手】

更深层次理解传输层两协议【UDP | TCP】【UDP 缓冲区 | TCP 8种策略 | 三次握手四次挥手】

 博客主页:花果山~程序猿-CSDN博客

文章分栏:Linux_花果山~程序猿的博客-CSDN博客

关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!

目录

再谈端口号 

 端口号的返回划分

netstat(重要)

pidof

一,UDP协议

UDP协议格式

理解tcp/udp报文

UDP的特点

UDP的缓冲区

怎么理解全双工,半双工呢?

二,TCP协议(传输控制协议)

协议格式

tcp协议的实现向上,向下交付

a. 序号与确认序号

理解tcp协议的可靠性

b. 16位窗口大小

c. 6个标记位

连接管理机制

d. 紧急指针

e. 策略

1. 确认应答(ACK)机制

2. 超时重传机制 

3. 滑动窗口

4. 流量控制

5. 网络拥塞控制

6. 延迟应答

7. 捎带应答

f. tcp机制小结

g.面向字节流

h.黏包问题

I: TCP异常情况

k. listen()的第二个参数

结语


嗨!收到一张超美的风景图,愿你每天都能顺心!

再谈端口号 

曾经的理解:端口号 (Port) 标识了一个主机上进行通信的不同的应用程序。

如今,TCP/IP协议,使用“源IP” ,"目的IP",“源端口号”,“目的端口号”,“协议号这五个组来标记一个网络服务。

如下,就是体现在同台机器上,web浏览器打开多个页面,端口号不同;不同机器,源IP不同的例子:

 端口号在网络中是用来标识一台机器中的一个特定通信的。问?一个进程是否能绑定多个端口号?

答:当然,比如上面web的cline多开网页,操作系统为其分配新端口号,而一个端口号不能代表多个网络通信,不然操作系统无法正确将数据信息向应用层交付。

 端口号的返回划分

0 - 1023: 知名端口号 , HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的。因此我们需要 避开这些端口号
例如:
  • ssh服务器, 使用22端口
  • ftp服务器, 使用21端口
  • telnet服务器, 使用23端口
  • http服务器, 使用80端口
  • https服务器, 使用443
详细请输入查看:
cat /etc/services

1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的(我们网上买的服务器大多默认不开放端口号,需要我们手动打开)

netstat(重要)

是一个用来查看网络状态的重要工具 .
语法 netstat [ 选项 ]
功能 :查看网络状态
常用选项
  • n 拒绝显示别名,能显示数字的全部转化成数字
  • l 仅列出有在 Listen (监听) 的服务状态
  • p 显示建立相关链接的程序名
  • t (tcp)仅显示tcp相关选项
  • u (udp)仅显示udp相关选项
  • a (all)显示所有选项,默认不显示LISTEN相关

sudo 提升权限后,我们可以详细看到服务的进程名。

pidof

在查看服务器的进程id时非常方便.
语法:pidof [进程名]
功能:通过进程名, 查看进程id

 常用使用方法:pidof   [进程名] | xargs kill -9   (命令解释,xargs 将标准输入内容转化为指令行参数)

一,UDP协议

UDP协议格式

  • 16位UDP长度, 表示整个数据报(UDP头部+UDP数据)的最大长度(报头固定长度,非常有利于分离报头);
  • 如果校验和出错, 就会直接丢弃。

在我们使用套接字应用时,我们为什么经常使用 uint16_t类型? 答:因为传输层使用的TCP/UDP协议要求的。 

问?在传输层是如何实现向上交付于向下交付

答:向下交付:我们只需上层协议发送的数据,字符串拼接形成报头。

向上交付:提取目的端口号,然后从8字节开始提取有效载荷,最后操作系统完成信息分配。

理解tcp/udp报文

本质上是一个结构体(位段) ,当需要获取报文里面的数据时,只需要进行数据强转为udp结构体类型,就能正确的读取每个数据。

UDP的特点

UDP传输的过程类似于寄信.
无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;
不可靠: 简单,没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;
面向数据报: 不能够灵活的控制读写数据的次数和数量( 报文不能拆分,也不能合并,发多少收多少,发几次,也需要收几次);

UDP的缓冲区

UDP没有真正意义上的 发送缓冲区。调用 sendto会直接交给内核, 由 内核将数据传给网络层协议进行后续的传输动作;
UDP具有接收缓冲区. 但是这个接收缓冲区 不能保证收到的UDP报的顺序和发送UDP报的 顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;
UDP socket 既能读 , 也能写 , 这个概念叫做 全双工

怎么理解全双工,半双工呢?

 全双工,就是既能发送信息,同时又能接收信息,两者不冲突。半双工发送与接收信息两者互斥则不允许。

全双工的特点:发送与接收没有使用公共缓冲区,比如:UDP发送是直接交给OS发送,接收有另一个专门的缓冲区接收。

UDP 使用注意事项
我们注意到 , UDP 协议首部中有一个 16位的最大长度 . 也就是说一个 UDP 能传输的数据最大长度是 64K(包含UDP 首部)。然而64K 在当今的互联网环境下 , 是一个非常小的数字 .
如果我们需要传输的数据超过 64K, 就需要在应用层手动的分包 , 多次发送, 并在接收端手动拼装 ;

基于 UDP 的应用层协议
  • NFS: 网络文件系统
  • TFTP: 简单文件传输协议
  • DHCP: 动态主机配置协议—(连wifi, 或者路由器时,在路由器中会启动这个程序,连接成功将会为我们自动分配一个IP,断开回收)
  • BOOTP: 启动协议(用于无盘设备启动)
  • DNS: 域名解析协议
当然 , 也包括你自己写 UDP 程序时自定义的应用层协议。

二,TCP协议(传输控制协议)

协议格式

tcp协议的实现向上,向下交付

向下交付:我们在学习udp时发现,报头的本质是一个位段类型,传输层接收到应用层传递的tcp报头数据(设置这个结构体的数据,后拷贝)后,为数据添加报头后向下进行交付。

向上交付:如下图,我们得先理解4位首部长度

a. 序号与确认序号

理解tcp协议的可靠性

        我们知道UDP协议不关心,数据报文是否被收到,可靠性不高。反过来tcp协议具有可靠性,需要关心数据是否被收到,因此某端在成功接收到数据报后,需要通过一种方法返回已收到信息。 

先由下图,逐步引出对tcp的理解:

序号与确认序号的5层理解:

1. 将请求与应答一一对应

2. 确认序号的含义:表示前面的数据已经安全接收。如果C端接收到了3001的确认序号,则代表3000,2000,1000已经完整接收。

3. 允许部分确认丢失,或者不应答。假设C端收到3001,前面的完整接收,所以C端不会选择重发;如果C端传递时丢失2000,当S端收到3000,1000而没有2000时,3001将不会被设置,而1001将会被设置(只收到1001),这就是不应答。

4. 为什么要用两个序号,而不是一个数字?(小面试) 任何一方通信都是全双攻的,各自也需要保证各自的信息是否传达成功,接收方也会携带自身的数据。

5. 保证数据按序到达。乱序是一种不可靠,提取数据是按照顺序来的,而有了序号,OS就会对资源进行排序,保证数据按序到达。

两序号作用:保证了确认应答,和数据排序。

b. 16位窗口大小

本质上是一种流量控制,在上一小章中,我们知道UDP没有发送端缓冲区,接收端有缓冲区,那tcp是否有缓冲区呢?

答:有接收,发送缓冲区。数据在应用层,进行send,recv后数据将首先存放在系统中的发送,接收缓冲区。

  • 发送缓冲区:TCP协议会将数据分割成多个数据包,如果网络拥塞或接收方接收数据速度慢,发送缓冲区可能会满,此时发送方会等待接收方确认已接收的数据,然后继续发送数据。
  • 接收缓冲区:如果接收缓冲区满了,接收方会通知发送方暂停发送数据,直到接收方处理完缓冲区中的数据。

那如何让发送方知道,接收方缓冲区满了?

16位窗口大小:标记当前tcp连接所剩余缓冲区大小

c. 6个标记位

每个报文都有其自身所携带的信息,他们会被分成6类,我们可以将6个标记位理解为报文类型。 

6 位标志位(报文类型) :
  • SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段。
  • FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段。
  • ACK: 具有应答特征的报文。一般ACK都会被设置为1,除了第一次请求。
  • URG: 设置为1时,表示紧急指针有效,后面讲紧急指针会详细讲。
  • PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走(接收端缓冲区满时,发送端可能会发送PSH报文)。
  • RST: 对方要求重新建立连接;  比如三次握手出现异常,就会向对方发送RST的重新建立连接

关于6个标记位的认识,我们通过tcp连接管理机制来学习吧(三次握手,四次挥手)

连接管理机制

1.三次握手

        首先我们需要理解:服务端管理相应的连接是需要资源的(内存 + cpu) 

        服务端对大量客户端连接的管理,必然是先描述,后管理。一个客户端的连接,服务器必然会为其创建合适的数据类型,然后再用数据结构进行管理。

 示意图:

上图示意:第一次客户端向服务端发送SYN被设置的完整报头,服务端也会发送SYN+ACK被设置的报头,并且会进入SYN_RCVD的状态,最后等待客户端发送来的ACK报头。

理解:为什么是三次握手,而不是一次,2次或者是4次?

答:

1.  如果是一次请求,服务端就会为客户端进行创建对象,那一台简单的客户端循环发出请求,服务端的资源就很容易消耗完,这种攻击叫做SYN洪水

2.  如果是4次请求?我们知道服务端是作为大量客户端请求相应的处理中心,在前三步已经达成连接后,还通知客户端,这一步是没有意义的。

3.  2次为什么不可以? 服务端对客户端进行请求应答了,为什么服务端还需要等待客户端进行应答? 在服务端进入通信状态时,先让客户端进入通信状态,这样只有在客户端都准备完成后,服务端才工作,这滞后性,变相的提高了服务端工作效率。

4.   并且3次握手不一定成功。服务端建立连接后并不会向客户端进行连接成功的应答。

2.四次挥手

为什么Time_wait状态设置为2MSL?
  • MSLTCP报文的最大生存时间(一般设置为60s),因此TIME_WAIT持续存在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
  • 同时,也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN,重发到接收一般会少于2MSL。 这时虽然客户端进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK);

 有那些情况下,TIME_WIAT会导致bind失败

  • 服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求)。
  • 这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接。
  • 由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip,源端口, 目的ip, 目的端口, 协议). 其中服务器的ip和端口和协议是固定的。如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了(短时间内重复登录), 就会出现问题。
  • 服务端异常崩溃

而为了解决上面场景,编写套接字时使用setsockopt()设置属性 选项SO_REUSEADDR1, 表示允许创建端口号相同,但IP地址不同的多个socket描述符。

 

d. 紧急指针

        功能:URG被设置的报文,TCP会优先提取紧急指针(偏移量)所标示的数据范围,优先提交应用层

理解:我们知道数据都是客户端依次发出的,tcp协议会先将大量数据进行排序,丢失的申请重发(数据交付是要排队的),而URG设置后报文的部分数据则会优先被交付(俗称插队)不过这个多出现在服务端的管理

e. 策略

1. 确认应答(ACK)机制

TCP协议中会对应用层载入的数据,进行一个字节一个字节的标记,说白就是char数组下标+1,这也是上面说过的序列号。 

 

功能:每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据,下一次你从哪里开始发。 

2. 超时重传机制 

就是发送端从发送数据后开始计时,等到达一定时间后,未收到ACK则重新发送。

情况一:ACK丢失

主机B的ACK丢失,但主机B已经缓存了数据,主机A在下一个ACK接收时超过特定时间,主机A会触发超时重传,重新发送,

这时,主机B就有可能会收到大量的重复数据(不过主机B会去重),接收到后返回ACK。这就是一次超时重传。

网络情况比较复杂,在不同的网络环境中,超时时间的设置也不同,那如何动态设置超时时间?

答:Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍,第二次重发2 * 500ms,第三次重发3* 500ms,当积累到一定次数后,TCP会认为网络或者主机出现问题,强制关闭连接。

 

3. 滑动窗口

        从上面的确认应答机制,我们可以发现一些问题,发送端发送一个报文,会等待ACK返回,然后再向接收端发送下一个内容。效率太低了,那有什么方法来提高效率?

首先了解一下窗口:窗口大小指的是无需等待确认应答而可以继续发送数据的最大值

如下,该窗口大小为4000字节

理解:

  • 在窗口大小内,可以不接收ACK,连续发送多段数据报文,在确认窗口内的数据都到达后,向后移动,移动的大小为win_start + 对方的接收能力。
  • 操作系统需要维护该滑动窗口,需要记录窗口中的数据是否收到应答,收到应答后才将发送数据删除。
  • 窗口越大,网络吞吐量越大。

可能存在的异常情况:
情况一:数据包接收,ACK丢失

 

这种情况一般不会有影响,因为确认序号是当前序号的前面序号已经全部接收,因此下一个ACK将会确认。

情况二:数据包丢失

主机B在接收主机A连续发送的多个数据报中,首先将所收到的数据载入接收缓冲区中,排序检查发现缺失1001~2000,因此后面所返回的ACK的确认序号会全部设置为1001,当主机A收到连续3个同样的ACK确认序号为1001,这就会触发重发机制,滑动窗口会定位该数据段,重新发送。

这种机制被称为:“高速重发机制”(也叫快重传),但这要求主机A连续收到3次相同的ACK

如何理解同时存在快重传超时重传

  • 快重传比超时重传速度快,效率高,但有前提。
  • 快重传与超时重传之间是相辅相成的,

比如下面情况:

 

4. 流量控制

接收端处理数据的速度是有限的。 如果发送端发的太快 , 导致接收端的缓冲区被打满 , 这个时候如果发送端继续发送 , 就会造成丢包, 继而引起丢包重传等等一系列连锁反应 .
因此 TCP 支持根据接收端的处理能力 , 来决定发送端的发送速度 .。 这个机制就叫做 流量控制 (Flow Control) ;
理解:
1、接收端可以通过ACK,在报文中窗口大小设置接收缓冲区剩余量的大小,让发送方动态设置滑动窗口的大小,如果窗口大小为0,那么滑动窗口设置为0,也就是停止发送数据。
2.   在第一次发送方向接收方发送信息时,为防止数据溢出,发送方会定期发送询问窗口大小的数据段,动态设置滑动窗口。

5. 网络拥塞控制

当我们发现少量丢包时,我们可能会触发超时重传(或者快重传);但如果遇到大量丢包呢?这就是网络拥塞了,TCP协议则会立即停止重传。

在学习网络拥塞前,我们需要以更大的视角,网络中分布着大量的用户端,服务端。

因为网络上有很多的计算机 , 可能当前的网络状态就已经比较拥堵。 在不清楚当前网络状态下 , 贸然发送大量的数据, 是很有可能引起雪上加霜的。
TCP 引入 慢启动 机制 , 先发少量的数据 , 探探路 , 摸清当前的网络拥堵状态 , 再决定按照多大的速度传输数据 ;

意义:当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降; 拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。

6. 延迟应答

当接收方接收到发送的信息后,并不立即发送ACK,而是等待200s让应用层将数据取走,如果立即发送ACK下次窗口大小为500K, 而等待后下次窗口大小则为1mb,这样通过延迟应答机制,窗口变大,吞吐量增大,吞吐量增大,传输效率提高。

那么所有的包 都可以延迟应答 ? 肯定也不是
  • 数量限制:每隔N个包,延迟应答一次
  • 时间限制:超过延迟应答的时间立即应答一次。

7. 捎带应答

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

f. tcp机制小结

tcp传输控制协议之所以这么复杂,是既保证了可靠性,又尽可能的提高了效率。

保证可靠传输:

  • 连接管理机制
  • 数据序列(保证数据有序)
  • 检验和
  • 确认应答机制
  • 超时重传
  • 流量控制(保证数据不异常丢包)
  • 网络拥塞控制

保证效率:

  • 滑动窗口
  • 快重传(不用等待发送方触发超时重传)
  • 延迟应答
  • 捎带应答

g.面向字节流

创建一个 TCP socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区
  • 应用层通过write写入发送缓冲区;
  • 接收方接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区,然后通过read从缓冲区中提取数据。
由于缓冲区的存在 , TCP 程序的读和写不需要一一匹配(指的是发送方的发送次数,与提取次数不相同,区别与UDP)

h.黏包问题

我们知道TCP协议接收方从接收缓冲区中读取数据时,数据包之间是不像UDP那样一个一个地读取,而是一段一段的读取,有可能到的内容存在1.5个数据包。问题是我们怎么解决这个数据包之间的分界问题?

答:这个是应用层的事情,TCP只负责数据在将数据发出,收到的数据拷贝到接收缓冲区中,数据包分界tcp不关心。

应用层常见的区分法:

1. 定长数据包,由于TCP没有区分数据包,通过read()读取相应的数据包,在应用层提前声明数据包的长度。

2. 分割符,比如设置"\r\n****\r\n"来区分数据包。

3. 自定义描述 + 分割符, 比如我们曾经的“length\r\n*****\r\n”来动态分割数据包

I: TCP异常情况

  • 进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别。
  • 机器重启: 和进程终止的情况相同。
  • 电脑断电或者拔网线:接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset. 使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放。

对于一些长连接的服务,例如QQ聊天,客户端如果检测到长时间未进行数据发送,服务端的资源任然占用着,则会在客户察觉不到的情况下关闭连接,在客户需要发送时,会再次连接。

k. listen()的第二个参数

backlog:指定在拒绝新连接之前,操作系统可以排队等待的最大已连接数量。

所以accept是否参加了三次握手?? 答:并没有,accept只是对已经连接的进行调用获取他们的套接字。

Linux 内核协议栈为一个 tcp 连接管理使用两个队列 :
  •         半链接队列(用来保存处于SYN_SENTSYN_RECV状态的请求信息,一段时间后会被释放)
  •         全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求)

一旦新的连接请求时,由于已经超过了等待的最大连接数, 因此服务器,不会发出SYN + ACK(不同意连接),并将此次连接信息载入半连接队列(未连接成功的),在netstat上显示此次连接为 SYN_RECV 状态, 而不是 ESTABLISHED 状态。

  而全连接队列的长度会受到 listen 第二个参数的影响,最大等待数量 = listen 的第二个参数 + 1。  

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获,请动动你发财的小手点个免费的赞,你的点赞和关注永远是博主创作的动力源。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/533332
推荐阅读
相关标签
  

闽ICP备14008679号