udp && tcp协议

文章目录

  • [1. UDP协议](#1. UDP协议)
    • [1.1 端口号](#1.1 端口号)
    • [1.2 UDP协议格式](#1.2 UDP协议格式)
    • [1.3 UDP特性](#1.3 UDP特性)
    • [1.4 报文的封装](#1.4 报文的封装)
  • [2. TCP协议](#2. TCP协议)
    • [2.1 TCP协议格式](#2.1 TCP协议格式)
    • [2.2 TCP策略](#2.2 TCP策略)
    • [2.3 面向字节流](#2.3 面向字节流)
    • [2.4 粘包问题](#2.4 粘包问题)
  • [3. TCP全连接队列](#3. TCP全连接队列)
    • [3.1 backlog](#3.1 backlog)
    • [3.2 全连接队列的原理](#3.2 全连接队列的原理)
  • [4. `tcpdump`](#4. tcpdump)

1. UDP协议

1.1 端口号

在前面的文章中说过,每一个要网络通信的进程,都要绑定IP地址和端口号

其中端口号划分为两部分:

  1. 0~1023:一些知名协议已经绑定了的端口号
  2. 1024~65535:OS随机分配的端口号

在OS内部,端口号与进程之间的关系类似于哈希表,通过键值port能找到唯一的进程与之对应

同时,也能说明,一个进程能够绑定多个端口号,但一个端口号只能被一个进程绑定

1.2 UDP协议格式

  1. 如何将报头与有效载荷分离?

    UDP报头字段固定8字节,读取其中的16位UD长度,减去8字节就是有效载荷的长度

  2. 如何分用?

    根据报头中的16位目的端口号将有效载荷向上层交付

1.3 UDP特性

  1. 无连接
  2. 不可靠
  3. 面向数据报

无连接,在socket编程章节中已有所体现,使用udp通信的双方创建好套接字就能直接通信

而对于不可靠这一特性,讲完tcp的可靠性,自然也就懂了

所谓面向数据报,发送方发多少,接收方就接多少,不能灵活控制发送和接受的次数

udp注意事项:udp没有发送缓冲区,调用sendto直接由内核发送,但有接受缓冲区;udp报头字段中的16位长度表明udp最大长度是64KB,如果发送的数据超过64KB,则需要分批发送

1.4 报文的封装

所谓的报头,在内核中其实就是结构体字段

在应用层,由于双方操作系统、机器大小端、计算机语言等的不同,发送的数据需要先进行序列化

而传输层属于内核,内核的部分双方几乎一样,都是用C写的,进行序列化再传输反而增加了传输量

因此,横向来看,udp报头是以结构体直接发送的

纵向来看,在OS中,可能同时存在各种各样的报文,有准备向上交付的,有准备向下交付的,有要丢弃的...那么OS就需要对这些报文进行管理 ----- 先描述,再组织;所以,报文的本质也是一个内核数据结构,它有非常多的属性,其中我们重点了解两个指针

当应用层向下交付时,数据存放在内核中的一段内存上,该内存前面预留了一些空间

最开始,sk_buff中的headdata都指向数据的起始位置

当要封装udp报头时,head向前移动udp报文长度个字节,并赋好值,这样就完成了报文的封装,往下依旧如此

2. TCP协议

2.1 TCP协议格式

  1. 如何将有效载荷与报头分离?

    tcp协议格式中的4位首部长度,每bit位表示4字节,总共能表示[0, 60]字节

    tcp协议格式前20字节固定,取出后,拿4位首部长度 - 20字节就是选项,剩余的就是数据

  2. 如何分用?

    接收方根据16位目的端口号向上交付

2.2 TCP策略

我们知道,tcp协议是可靠的、面向连接的,那么tcp是如何保证可靠性,如何连接的呢?这与tcp策略息息相关

2.2.1 确认应答机制(ACK)

发送方发送的报文,可能在传输的过程中丢了,也可能对方收到了,那发送方如何得知报文究竟是丢了还是对方收到了?

很简单,如果对方收到报文,只要给我个应答,就证明我之前发送的数据对方一定收到了;相反,如果我没收到应答,则证明我发送的报文对方没收到

但是又有问题了,S怎么得知应答对方一定收到了呢?在这里,我们规定,不对应答进行应答,因为如果对应答进行应答,就会无限套下去,C又怎么得知自己的应答对方一定收到...

因此,对于应答,我们无法保证可靠性,但只要收到应答,历史发送的数据对方一定收到;而如果没收到应答,不管是报文丢失,还是应答丢失,发送方就认为对方没收到报文

所谓可靠性,指的是对方收到报文了,我能知道,对方没收到报文,我也能知道

我们把这种确认应答机制也叫做ACK机制,当进行应答时,需要将tcp报头字段中的ACK标志位置1,双方都采用ACK机制,就能保证数据传输的可靠性

序号与确认序号

tcp有两种通信模式,一种是向上述那样,发送一个报文,回一个应答

但更常用的则是:发送方可以一次发送多个报文,接收方在接收到报文后分别对每个报文进行应答,从而提高通信效率

那么接收方对每个报文应答后,发送方如何确认哪个应答对于哪个报文呢?这就需要tcp报头中的序号与确认序号

当发送方发送报文时,需要对该报文填写唯一性标识 ---- 序号,当接受方收到报文进行应答时,填写确认序号,其中确认序号 = 序号 + 1

其中确认序号的含义是:+1之前的数据已经全部收到,下次从+1的数据开始发

比如当发送方发送序号为2000的报文,接收方应答时确认序号填写2001,如果接收方收到应答,则认为2000之前的数据对方已经全部收到,下次从2001开始发

同时,如果应答部分丢失,只要收到最近一次报文的应答,就认为之前的报文对方一定收到,因此,该做法允许部分应答丢失

从上述例子看,貌似tcp只需要一个序号也能对每个发送的报文进行确认,当发送时,填写序号,应答时,将序号当作确认序号来填,不也ok吗?为什么要以序号 + 确认序号的方式?

当发送方发送报文时,对方进行应答的同时,也想发送数据,此时为了提高通信效率,接收方将ACK与要发送的数据作为一个报文,将ACK标志位置1 + 填写确认序号表示应答,填写序号 + 数据表示要发送的数据;在这种情况,就需要序号 + 确认序号

6个标志位

udp协议中我们说过,OS内有各种各样的报文,在tcp这里更是如此,有应答报文、还有后面讲的发起连接的报文、断开连接的报文等等

因此,OS要对它们进行管理的同时,还要能够对它们进行区分,如何区分报文?根据tcp报头中的6个标志位

序号的理解

根据我们学过的知识,应用层调用sendto,本质是将数据拷贝到发送缓冲区,在这里,我们不妨将缓冲区看作字符数组,于是每一字节就天然的有一下标,序号的本质就是发送缓冲区数组的下标

当确认序号为1001时,表示1000之前的数据已经收到,下次从下标为1001的位置开始发

2.2.2 超时重传机制

当发送方发送报文,在一段时间以内,没有收到应答,此时就认为报文丢失,会重新发送报文,我们把这种机制叫做超时重传机制

那么超时时间应该是多少呢?由于报文是经过网络传输的,而网络的状态是浮动的,有时好,有时坏,当网络好时,超时时间应该短点,以提高效率,但网络坏时,超时时间应该长些,因此,超时时间也应当是浮动

一般来说,linux下的超时时间起初是500ms,第二次发送没收到应答时,超时时间变为2 * 500ms,以此类推,如果连续多次超时重传后还是无法收到应答,则关闭连接

没有收到应答的原因可能是报文丢失,也可能是应答丢失,如果是应答丢失,表明对方收到报文,如果重新发送报文,且对方收到,此时对方的OS内就有两份相同的报文;OS会根据序号来判断两份报文是否相同,如果相同,则丢弃新报文的并给出应答

tcp通信模式中,常见的是第二种,多个报文一并发送时,由于报文是经过网络传输的,它们走的线路不同,到达对方主机的时间也就不同,也就意味着发送的报文可能是有序的,但接受时可能是无序的,此时,OS会根据报文的序号来排序,确保向上层交付的数据是有序的

总上来看,序号的作用主要有3点:

  1. 对发送的报文进行唯一性确认
  2. 对报文进行去重
  3. 使报文按序到达

2.2.3 连接管理机制

三次握手

我们总是tcp是面向连接的,那么它是如何建立连接的?

使用tcp协议通信的双方,在通信之前需要建立连接,通常由client发起连接

当server调用listen后,进入LISTEN状态,client调用connect,发起连接,连接报文中不能携带数据,将SYN标志位置1,表示是连接报文,询问sever,我要和你建立连接,你是否同意

server收到报文后,同意建立连接的同时,反问client,我同意你到我的一个连接,但我也要和你建立连接,你是否同意

client收到报文后,同意server到client的连接

在上述过程中,client发起SYN,收到SYN + ACK,发送ACK,总共收发3个报文;server收SYN,发SYN + ACK,收ACK,也总共收发3个报文

我们把上述建立连接的过程叫做tcp的三次握手

那么问题来了,如果client发出的最后一个ACK丢失了呢,由于无法保证应答的可靠性,不就代表连接建立失败了吗?事实上,的确如此,所以建立建立的本质就是在赌最后一个ACK对方一定收到

那如果最后一个ACK就是丢了呢?其实也是有办法的,要注意,对于client,在发送最后一个ACK时,它到server的连接已经建立好

但对于server,只有收到ACK时,它到client的连接才建立好

也就是说client->server的连接与server->client的连接建立好之间,有一段时间间隔

在这段间隔之内,也就是server->client的连接建立好之前,client有可能已经给server发送了数据报文,如果最后一个ACK丢了,而server又收到了client发送的数据报文,此时server判断自身连接还没建立,就会给client发送一个将RST标志位置1的报文,表示异常连接,要求client重新建立连接,client收到后,就会断开连接,重新发起三次握手

不仅仅是上述示例中,在通信的任何时刻,只要发现tcp连接异常,都可以向对方发送RST标志位置1的报文,重新建立连接

四次挥手

建立连接后,双方正常通信,突然client不想通信了,关闭了文件描述符,于是发起断开连接的报文 --- 将FIN标志位置1,询问对方,我想和你断开连接,你是否同意,server同意了,给了应答

过会,server也不想和client通信了,于是也关闭文件描述符,发起断开连接的报文,server要client断开连接,问client是否同意,client也同意,至此,双方的连接彻底断开

同理,client和server收发报文各自总共4次,我们也把双方断开连接的过程叫做tcp四次挥手

在这里,又有几个问题

  1. 根据上文说的,能不能将server发给client的ACK与server要与client断开连接FIN报文合并?

    不能,一方想要与对方断开连接,原因是我要发给对方的数据已经发完了,所以我要和对方断开连接,但对方要给我发送的数据不一定发完

    因此,client发送的数据发完,就和对方断开连接,此时server仍可能要给client发送数据,等到server的数据发完,由server自主发起断开连接

    这也是为什么断开连接叫四次挥手

  2. 根据现有的知识,一个fd对应一个连接,一个连接在传输层对应两个缓冲区,发送和接受缓冲区,既然client已经关闭fd了,也就是销毁发送和接受缓冲区了,为什么还能接受server发来的数据?

    这里的close(),我们可以理解为shutdown(sockfd, SHUT_WR),也就是只关闭了发送缓冲区,等到双方的连接都断开,才是真正的close()

需要注意的是,client应答ACK后,到server收到应答,需要一段时间,这也是为什么之前我们写tcp服务器时,关闭服务器后又立马重启发现绑定端口号错误的原因,只要没收到应答,连接就还在,端口号就还在被占用,也就绑定失败了

理解三次握手

建立连接,为什么是三次握手?

在OS内,连接有很多种,有正在三次握手的连接、正在四次挥手的连接、正常通信的连接...OS需要对这些连接管理 --- 先描述,再组织,连接在OS内本质也是内核数据结构,需要花费空间创建结构体对象,花费时间管理,这意味着管理连接是需要成本的

首先,来看一次和两次为什么不行,一次不用说了,SYN报文的可靠性都不能保证;而两次中,client发送SYN,server收到应答就建立好连接,由于建立连接的成本太低,client可以向server发送大量建立连接的报文,而server对每个连接都默认接受,导致server承受过重压力,可能会引发SYN洪水问题

而三次握手,虽然也可能存在SYN洪水的问题,但最重要的是:

  1. 验证了双方网络的连通性
  2. 以最小的成本建立双方通信的共识

client能收、能发,client->server网络正常,server也能收、能发,server->client网络正常,双方网络连通

client想和server通信,server同意了,server也想和client通信,client同意了,双方达成共识

以上两点,一次两次握手都达不到,因此,这才是为什么tcp建立连接是三次握手的原因

理解四次挥手

实际上,跟三次握手同理,四次挥手之所以叫做四次挥手,在于它以最小的成本建立双方断开连接的共识

根据我们四次挥手图的理解,对方在收到建立连接的报文后,如果不关闭fd,会处于CLOSE_WAIT的状态;同时,主动断开连接的一方,在收到对方断开连接的报文后,会处于TIME_WAIT的状态,下面我们来验证

我们拿tcp_echo_server来验证

那么主动断开连接的一方为什么最后要处于TIME_WAIT的状态?

该状态维持多长时间?

能不能不处于该状态,好让下次服务器直接重启?

报文是经过网络传输的,当断开连接时,报文可能还在传输的路上,如果连接直接断开,又立马重启了,这时就可能收到上次在网络中遗留的报文,影响到了本次通信,因此,处于TIME_WAIT 的状态就是为了等待这些报文到达后,将它们丢弃;也有另一个原因,就是最后一个ACK,server不一定收到,超时时间后,server会再次发送FIN,此时client还没退出,就能再ACK,确保server也能退出

处于TIME_WAIT的状态的时长一般是2 * MST,这里的MST是报文的最大生存时间,根据操作系统而变

shell 复制代码
cat /proc/sys/net/ipv4/tcp_fin_timeout # 查看当前系统的MST

要想client退出后,不进入TIME_WAIT状态,将server的listensockfd设置以下即可

2.2.4 流量控制

应用层调用write,将数据拷贝到发送缓冲区,由tcp控制数据的传输;如果对方的接受缓冲区满了,那么到达的报文也就直接丢弃了,这种做法虽然可行,但却不合理,报文的传输消耗了网络资源,CPU等硬件资源,却就这样被丢弃

因此,tcp在传输数据时,应该要考虑对方的接受能力,根据对方接受能力的大小来控制数据应该发多少,我们把这种策略叫做流量控制

所谓对方的接受能力,实际就是对方接受缓冲区剩余空间的大小

那么,发送方该如何得知对方的接受能力呢?

这就引入tcp报头字段中的"16位窗口大小"了,在对报文应答时,不仅要将ACK标志位置1,有数据的加数据,还要将自身发送缓冲区剩余空间的大小填入16位窗口大小中,这样,对方在收到ACK时,就能得知我当前的接受能力

tcp报文字段中的选项中,可能会有窗口扩大因子的选项,实际的窗口大小 = 窗口大小 << 窗口扩大因子

实际上,在tcp三次握手时,双方就是在告知对方自身最初的接受能力

在极端情况下,对方的接受能力为0,此时发送方停止发送数据,对方也就不ACK了,对方的接受能力也就无从得知了,此时如何判断对方有接受能力了,从而继续发送数据?

一方面,接收方会时不时的发送窗口更新通知的报文,主动告知自身的接受能力;另一方面,发送方也会时不时的发送窗口探测报文,对方只要ACK,就能得知其接受能力

那如果对方的接受能力仍然是0呢?发送方可以发送PSH标志位置1的报文给对方,提醒对方尽快将缓冲区里的数据向上交付;任何时候,只要希望数据尽快向上交付,都能将PSH标志位置1

至此,还剩一个URG标志位,它通常配合16紧急指针使用,为1表示紧急指针有效,否则无效;紧急指针实际是一个偏移量,指向接受缓冲区中某1字节;有时,我们给对方发送数据,在某一时刻希望取消发送数据,于是发送URG标志位置1的报文,但如果接收方依旧按序接受报文,当读到URG标志位,前面的报文已经处理完毕,又要撤回,相当于前面的工作白做了;因此,tcp提供了某些报文"插队"的做法,一般两个进程,一个正常读取,一个快速读取URG标志位,如果为1,则告知另一个进程取消读取

2.2.5 滑动窗口

前面我们介绍了超时重传机制和流量控制,但仍有问题:

  1. 已经发送的报文,在接受到ACK之前或者超时重传时间之内,不能被丢弃,需要保存起来,保存在哪?
  2. 通过窗口大小已经得知对方的接受能力,如何根据对方的接受能力进行数据的发送呢?

为了解决上述问题,tcp引入滑动窗口的概念,规定,在滑动窗口以内的数据可以直接发送,暂时可以不做应答

所谓的滑动窗口,实际就是发送缓冲区的一段区域,于是,滑动窗口将发送缓冲区分成三部分

到这里,我们可以粗略的将滑动窗口定义为:滑动窗口 = 对方的接受能力(对方接受缓冲区剩余空间的大小)

于是,我们根据滑动窗口的定义,来判断下面的问题:

  1. 滑动窗口能不能向左移动?

    已发送,已应答的数据无需再发送,因此滑动窗口只能向右移动,不能向左

  2. 滑动窗口能不能变大?变小?为0?

    滑动窗口 = 对方的接受能力,对方应用层调用read,接受能力就变大了,滑动窗口也就变大了;同理,滑动窗口也能变小,甚至为0

又根据之前的知识,缓冲区可以认为是一个字符数组,我们进一步将滑动窗口精细化的描述

滑动窗口本质是数组中的两个下标

现在,我们已经知道滑动窗口的具体含义了,可以根据滑动窗口来进行数据的发送了

那么问题来了,如果发送的报文丢了呢?

拿上图举例,如果最左边报文丢失,接收方在对其他报文进行应答时,根据确认序号的定义,只能填0,因为1000的报文没收到,当发送方收到3个以上的相同应答,触发快重传机制,立即补发1000的报文,接收方可以直接ACK4001,表示4000之间的报文已经收到

对应滑动窗口,win_start不动

如果是中间报文丢失,接收方ACK确认需要填1001,win_start右移,此时问题变成最左边报文丢失

最右边报文丢失同理,最终变成最左边报文丢失问题

上面提到的快重传机制与超时重传机制有何不同?

快重传是连续收到3个以上同样的应答所触发;如果在通信末期,可能报文丢失,但收不到3个以上的同样的应答,此时超时重传能进行报文的补发;可以说,超时重传是为报文的可靠性兜底的

如果是ACK丢失呢?这点我们之前说过,允许ACK丢失,只要收到最后的ACK,就能保证前面的数据对方收到了

因此,重新看待最开始的两个问题,收到ACK之前,报文保存在哪?---- 滑动窗口中;如何根据对方的接受能力控制数据的发送---滑动窗口内的数据就是能直接发送给对方的数据

2.2.6 拥塞控制

报文是经过网络传输的,网络有时好,有时坏;因此,有时报文的丢失可能是因为网络的原因

如何得知报文的丢失是因为网络的原因还是对方主机的原因呢?

如果发送100报文,其中小部分丢失,则判断网络状况正常,而如果大部分都丢失了,则认为是网络的原因,网络出现了拥塞问题

一旦网络出现拥塞问题,就要降低发送报文的数量,由于所有主机传输数据都遵循tcp协议,因此当网络出现拥塞,所有主机都能降低报文发送的数量,解决拥塞问题的关键在于,所有主机都有拥塞避免的共识

tcp引入拥塞窗口,它的含义是导致网络拥塞的最大报文数量

因此,在最开始,我们发送报文的数量不能超过导致网络拥塞的报文数量,滑动窗口 = min(对方的接受能力,拥塞窗口)

由于网络是浮动的,拥塞窗口也应当浮动,如何得知当前网络的拥塞窗口?一定需要多次探测

为什么拥塞窗口,tcp采用慢启动机制,采用2^n的数学模型来控制报文数量

采用2^n的好处是,一开始,报文数量少,能慢慢增加报文的数量,等待网络恢复;一旦网络恢复,因其增幅快的特性,能快速进入正常通信

但也不能一味的增长,我们需要准确探测到当前的拥塞窗口,当以2^n增长到某一阈值时,改为线性探测,知道导致网络拥塞,将新的阈值改为拥塞窗口的一半,继续采用慢启动机制...

2.2.7 延迟、捎带应答

当接受方接受到报文时,并不会立马做应答,而是会延迟一段时间,在这段时间内,应用层可能将数据取走,这样就能给发送方一个更大的窗口大小,以提高通信效率,我们把这种策略叫做延迟应答

而捎带应答,我们之间也见过,当给对方应答时,也想发送数据,就能把ACK和数据一并发送,tcp三次握手就是做了捎带应答

2.3 面向字节流

对于udp,发送方发的,就是接收方收的,不能灵活控制发送次数和接受次数

对于tcp,发送方可以多次发送,接收方也能分多次接受 --- 面向字节流

2.4 粘包问题

对于tcp,接收方收到报文将数据放到接受缓冲区,对于应用层来讲,数据全是字符串,也就是各种数据黏在一起,怎么区分一个完整的数据 --- 序列化与反序列化

3. TCP全连接队列

3.1 backlog

为了解释listen的第二个参数backlog具体有何作用,将服务器不对任何连接accept,并设置backlog为2,将3个client与server连接

确实存在三个client->server的连接,三个server->client的连接,同时也证明accept不参与三次握手的过程

当再有client连接时

client->server连接处于SYN_SENT的状态,这表示SYN报文已经发出,但server没同意建立连接,同时没有server->client的连接

当sever来不及accept时,允许client与server继续建立连接,但最多只能有 backlog + 1个连接

在OS中,来不及accept的连接会以队列的方式管理起来,我们把管理这些连接的队列叫做全连接队列

3.2 全连接队列的原理

需要注意的是,不是指服务器只能接受backlog + 1个连接,而是来不及接受的连接最多只能是backlog + 1个

backlog的值不建议太大,也不能为0,如果太大,后面的连接就需要等待较长时间,如果为0,等待server需要accept连接时,还需要继续三次握手

当前server来不及accept,等待空闲了,直接从全连接队列中拿,本质是减少了服务器的闲置率,提高用户的体验

从内核层面,我们理解连接

4. tcpdump

shell 复制代码
sudo tcpdump -i any      # 捕获任意报文
sudo tcpdump -i any tcp  # 捕获任意tcp报文

sudo tcpdump -i eth0 tcp # 捕获指定网络接口的tcp报文

sudo tcpdump src host xxx and tcp # 捕获指定源ip的tcp报文
sudo tcpdump src host xxx and dst host xxx and tcp # 捕获指定源ip和目的ip的tcp报文

sudo tcpdump port xxx and tcp # 捕获指定端口的tcp报文

sudo tcpdump -n port xxx and tcp -w xxx.pcap # 将捕获的tcp报文写到文件中
sudo tcpdump -r xxx.pcap # 将文件中的报文信息显示
相关推荐
乔巴不是狸猫7 分钟前
第11周作业
linux
Bessssss1 小时前
centos权限大集合,覆盖多种权限类型,解惑权限后有“. + t s”问题!
linux·运维·centos
silver6872 小时前
Linux 下的 GPT 和 MBR 分区表详解
linux
R-sz2 小时前
14: curl#6 - “Could not resolve host: mirrorlist.centos.org; 未知的错误“
linux·python·centos
code_abc3 小时前
Shell 脚本编程基础:变量
linux
星如雨落3 小时前
Linux shell脚本对常见图片格式批量转换为PDF文件
linux·shell
冰红茶兑滴水3 小时前
云备份项目--工具类编写
linux·c++
董健正3 小时前
centos制作离线安装包
linux·运维·centos
猫猫的小茶馆3 小时前
【数据结构】数据结构整体大纲
linux·数据结构·算法·ubuntu·嵌入式软件
咏颜4 小时前
Ubuntu离线安装Docker容器
linux·运维·服务器·经验分享·ubuntu·docker