Linux--网络3(传输层)

Posted 水澹澹兮生烟.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux--网络3(传输层)相关的知识,希望对你有一定的参考价值。

目录

一.UDP协议

1.1UDP协议的特性

1.2UDP协议段格式

 1.3UDP缓冲区

1.4UDP常见的应用场景

二.TCP协议

2.1TCP的特性

2.2面向连接

2.2.1针对三次握手的面向连接

        a.双方发送数据包名称

        b.双方连接状态

2.2.2针对四次挥手的面向连接

        a.双方发送数据包名称

        b.双方连接状态

        c.2MSL

        d.端口重用

        e.CLOSE_WAIT状态

2.2.3面向连接之包序管理 

        a.网络抓包

        b.三次握手当中三次的包序号

2.3TCP协议的协议字段

2.4可靠传输

2.4.1保证可靠

        a.确认应答机制

        b.超时重传机制

2.4.2保证传输效率

        a.提高发送方发送的效率&接收方接受的能力

        b.网络的转发能力

 2.5面向字节流


一.UDP协议

1.1UDP协议的特性

特性:无连接,不可靠,面向数据报

  • 无连接:UDP客户端给服务端发送消息的时候,不需要和服务端先建立连接,直接发送(客户端也是不清楚服务端是否正在在线)
  • 不可靠:UDP并不会保证数据是可靠有序到达对端
  • 面向数据报:UDP数据不管是和应用层还是网络层传递,都是整条数据交付的

1.2UDP协议段格式

如下图所示,这里的数据又叫做有效载荷(应用层的数据)

  • 16位源端口:当前的UDP数据从那一个进程产生
  • 16位目的端口:当前的UDP数据想要去往哪一个进程
  • 16位UDP长度:UDP数据的最大传输数据大小。它由两部分组成:UDP协议报头+UDP负载数据(这里指应用层递交给UDP数据);它的最大数据长度是2^16,UDP最大的传输大小是65536字节。
  • 16位UDP校验和:校验数据在传输的过程是否失真。如果失真了,UDP协议就会将该UDP数据报丢弃;如果没有失真,则当应用层调用recvfrom函数的时候,就会将UDP数据报给到应用层。

常见的面试题:

  • UDP传输的数据最大的大小是多少?答:65536字节
  • 如果想要传输要比2^16更大的数据应该这样做呢?答:在应用层针对数据进行分片,将超过65536字节的数据包进行拆分,将拆分后不超过65536字节的数据递交给传输层的UDP协议打上UDP包头,此时在传输层存在多个独立的UDP数据包,因此在应用层需要描述分包出来的UDP片段。所以我们在应用层不仅要对数据进行分片还有对其进行描述,在其前面打上应用层包头,是我们自己定义的里面有标识,标志位和片偏移。

 1.3UDP缓冲区

  • 发送缓冲区:没有真正意义上的发送缓冲区,调用sendto会直接交给内核,内核将该数据传给网络协议进行后续的传输动作,也就是将应用层提交给传输层的应用层数据打上报头之后就提交给网络层继续传输
  • 接受缓冲区:去掉UDP报头之后,将数据递交给应用层。它具有接收缓冲区,但不保证数据的可靠,以及有序。
  • UDP的socket既能读又能写,这个概念叫做全双工。

1.4UDP常见的应用场景

基于UDP的应用层协议:

  • DNS:域名解析
  • DHCP:动态主机分配协议,谁上网给谁分配IP,同一个局域网内部可能使用UDP,因为网络稳定
  • NFS:网络文件系统

二.TCP协议

2.1TCP的特性

特性:面向连接,可靠传输,面向字节流

  • 面向连接:双方在发送网络数据之前必须先建立连接,再进行发送
  • 可靠传输:保证数据是可靠并且有序到达对端(有序是针对应用层而言的,TCP能够确保的是该条数据到达对端的应用层时,一定是有序到达的,到达传输层的时候有可能不是)
  • 面向字节流:多次发送数据在网络传输当中没有明显的数据边界(对于没有明显间隔的数据橙称之为TCP粘包问题)

2.2面向连接

2.2.1针对三次握手的面向连接

a.双方发送数据包名称

三次握手时双方发送数据包的名称如下图:

 客户端调用connect接口发起三次握手,他会给服务端发送SYN数据包(连接数据包),服务端接收到SYN数据包后,通常会回复一个ACK+SYN数据包(ACK在这里的作用是服务端用来确认客户端发送给服务端的SYN数据包服务端接收到了,SYN在这里反映了服务端也想要和客户端进行连接),此时客户端给服务端发送ACK数据包,用来确认服务端发送个客户端的SYN数据包客户端收到了。

b.双方连接状态

如下图所示:

 当客户端发出去SYN数据包时,此客户端处于SYN_SENT状态,这是TCP中的一个状态,说明将SYN发送出去了。当服务端接收到SYN数据包时,服务端状态变成SYN_RCVD。当服务端给客户端发送了一个ACK+SYN数据包时,并且客户端收到了这个数据包,此时客户端的状态变成了ESTEBALISHED表示连接已经建立,此时客户端认为连接已经建立,但是此时服务端并不认为连接是已经建立的,换一句话说当前的连接还在内核的未完成连接队列当中,即使此时服务端去调用accept(),也不会将连接从内核中拿出来,除非当客户端给服务端发送ACK数据包时,服务端的状态变成ESTEBALISHED状态,这时当前连接才会从内核的未完成连接队列中放到已完成连接队列中去。此时在调用accept()时才会获取到连接。

根据上面分析,TCP必须握手三次才能建立连接的原因:

  • 从双方连接状态的角度,当两次握手时虽然客户端认为连接已经建立,但是服务端此时状态表示连接并未建立,并且该连接此时还在内核当中的未完成连接队列当中,即使调用accept函数页不会获取到连接;
  • 从确认客户端接受能力的角度,当客户端此时发送SYN数据包给服务端,此时服务端收到了,说明他的接受能力是没有问题的;服务端发送ACK_SYN数据包给客户端,表示他的发送能力是没有问题的;而此时客户端收到了这个数据包表示了此时客户端的发送与接收能力是没有问题的,但是如果此时没有客户端发送给服务端ACK数据包,那么服务端并不能确定客户端是否收到了ACK_SYN数据包,它是没有办法检测客户端的接受能力的。那么就服务端就不会认为已经建立连接。

2.2.2针对四次挥手的面向连接

四次挥手并不限定于两者谁先断开连接,双方都有可能先断开连接,在这里我们设置双方为注定断开连接方与被动断开连接方。我们在这里简称a方与b方。

a.双方发送数据包名称

 a方在这里会主动发起一个FIN报文,b方在收到FIN报文后会回复一个ACK(表示发送的FIN报文b方收到了),紧接着b方再发送一个FIN报文(回应断开连接),然后a方收到后,在对b方发送一个ACK表示确认。此时四次挥手结束。

b.双方连接状态

当a方发送FIN报文给b方时,此时a方会从ESTEBALISHED状态变成FIN_WAIT_1状态;当b方收到FIN报文后会变成CLOSE_WAIT状态,与此同时会给我们a方回复一个ACK,当a方接收到后会从FIN_WAIT_1变成FIN_WAIT_2状态。

b方会在主动给a方发送FIN报文,此时b放的状态会从CLOSE_WAIT状态变成LAST_ACK状态;当a方收到FIN报文后,此a方时状态变成了TIME_WAIT状态,并且给b方回复了一个ACK,然后b方的状态会变成CLOSED状态,而a方会等一段时间(这段时间我们称之为2MSL)变成CLOSED状态。

c.2MSL

在进行理解2MSL之前,我们必须知道,只有主动断开连接方才会有TIME_WAIT状态MSL指的是报文最大生存时间,即时自发送方发送出去之后,发送方认为该报文的最大生存时间是MSL。我们还需理解当发送方发送一个数据,需要接收方进行确认的机制叫做确认应答机制。但是为什么要等待2MSL呢?如下图所示:

 在b方位LAST_ACK状态下给a方发送了FIN报文,此时a方会再给b方回复发送ACK,假设此时ACK并未传输到b方,而是在过程中丢失了,那么b方就不清楚FIN报文是否到达a方。如果此时b方等待很久没有等到回复的ACK,那么它就会再向a方发送FIN报文,那么a方就会重置它的状态,会再进行等待一个2MSL。我们将这种机制称之为超时重传机制。

2MSL由两部分组成一部分是ack数据的MSL+被动断开连接方重传的FIN报文(两种情形,FIN还未到达主动断开连接方就丢失主动断开连接放回复的ACK丢失了);如果说等待了2MSL的时间,也没有等到重传的FIN报文,则说明ACK数据一定到达了被动断开连接方,那么主动断开连接方状态变成CLOSED。

d.端口重用

处于TIME_WAIT状态的程序,还需要将端口占用着,等待2MSL之后,在释放调用端口。占用端口的原因是防止被动断开连接方重传FIN报文。但是在这里如果当服务端作为主断开连接方,并且服务端进程还退出了想要快速启动服务端程序就会出现如下问题。

  • 服务端程序当中端口是写死的27878
  • 服务端有可能就陷入到TIME_WAIT状态,等待2MSL才能将端口释放(进程退出了,但是当前的端口还在被网络协议栈占着)

由于上面的这两点,如果想要快速启动服务端程序时就会报错bind:Address areadly in use.因此我们引入端口重用,直接调用下面端口。

int opt = 1;
setsockopt(listenfd, SOL_SOCKER, SO+REUSEADDR, &opt, sizeof(opt));
  •  listenfd:套接字描述符
  • SOL_SOCKER:套接字选项
  •  SO+REUSEADDR:重用端口
  •  opt: opt=1
  •  sizeof(opt):长度

e.CLOSE_WAIT状态

只有被动断开连接方才会存在的状态。处于CLOSE_WAIT一方需要调用close函数关闭新连接的套接字。如果被动断开连接方存在大量的CLOSE_WAIT状态,那么需要check代码当中是否有阻塞,循环导致被动断开连接无法调用到close。在CLOSE_WAIT状态到LAST_ACK状态中,需要被动断开连接方调用close函数,且被动断开连接方还可以给主动断开连接放发送一些数据。

2.2.3面向连接之包序管理 

a.网络抓包

  • 在win操作系统下:wireshack,针对网卡进行抓包
  • 在linux操作系统下:可以抓任意协议下的数据包,并不只能抓TCP下的数据包。命令范式:tcpdump -i any port [抓取数据的端口] -s 0 -w [将抓取的端口写到的文件]

上面的命令如果去掉port [抓取数据的端口],则没有使用端口进行过滤,那么抓出来的数据包比较大。一般将抓出来的文件通过wireshark进行分析。

在这里注意:想要看到三次握手后的过程,需要先开启抓包,再启动程序

b.三次握手当中三次的包序号

在三次握手发送数据包的过程当中,在TCP中分别维护了两套序号,一套是客户端在维护,一套是服务端在维护。我们现在来分析一下三次握手当中三次的包序号。如下图所示:

 客户端个服务端发送SYN数据包,数据包中携带的序号Seq=0;服务端收到后给客户端回复一个ACK+SYN数据包,包中携带的序号ACK=1+seq=0,此时ACK=1服务端就告诉客户端期望下次发送数据是从1号序号开始的,seq=0服务端之后给客户端发送数据的起始序号是从0开始的。紧接着客户端给服务端发送一ACK,携带的序号为seq=1,ack=1,此时的ack=1是用来确认服务端个客户端发送的seq=0,在这里客户端确认了收到了0号数据包且期望下次数据是从1号序号开始的。

紧接着上面的图,我们再对下图进行分析:

 在红色标记部分我们发现客户端给服端发送了两次数据,但是他们的seq都是为1,原因时纯的ACK数据包时不消耗序号的,不消耗序号即意味着不需要对方进行确认。也就是说,在三次握手的时候,最后客户端给服务端发送的Seq=1的序号并没有真正被消耗掉,而是在建立连接之后客户端给服务端发送Seq=1的时候,1号序号才被消耗掉,此时需要服务端确认是否收到该标识符。

我们再分析下图:

 结论:在这里我们也要注意TCP在发送数据的时候,数据的每一个字节都会进行标识。

总结:

  • TCP连接当中维护了两套序号,客户端维护了一套,服务端维护了一套
  • 双方再给对方发送数据的时候,是消耗自己维护的序号
  • 接收方再确认发送的数据的时候,是以期望下一个序号的方式进行确认
  • 双方在三次握手当中协商各自序号的起始位置。需要注意的是,双方的起始位置不一定从0开始,可以从任意位置开始,只需要后续遵守起始位置进行发送即可
  • 服务端发送个客户端的数据是由客户端进行确认,客户端发送给服务端的数据是由服务端进行确认

在这里存在的问题:

三次握手建立之后可以双发可以正常发送数据吗?答:不会。因为在三次握手之后在没有调用accpet函数的时候连接还是处在内核当中的已完成练级队列当中,并没有被调用回来。

2.3TCP协议的协议字段

  • 16位源端口,16位目的端口
  • 32位序号:用来表示TCP数据的起始序号
  • 32位确认序号:ack告知对端,自己期望对端下一次发送数据的时候从哪一个序号开始
  • 4位首部长度(1111=>15),他计算出来的是一个数值而并非是字节,首部长度占用字节数量=4位首部长度计算出来的数值*4(1111=>15*4=60字节),因此TCP头部最大为60位字节最小为20位字节。
  • 标志位:URG:紧急标志位(MSG_OOB紧急数据,带外数据);ACK:确认标志位;PSH:发送数据标志位;RST:重置连接标志位;SYN:发起连接标志位;FIN:断开连接标志位
  • 16位窗口大小:牵扯到滑动窗口机制
  • 16位紧急指针:配和URG标志位一起来使用,传输紧急数据
  • 选项:MSS:最大报文段长度,他是TCP双方在三次握手过程中协商最大报文长度的大小。客户端会告诉服务端有MSS_cli,服务端会告诉客户端MSS_svr。双方在三次握手的过程中都会发送他们最大报文的长度,而双方采用以最小的MSS作为后续传输数据的时候的最大报文长度,min(MSS_cli,MSS_svr)。它的作用是TCP在传输数据的时候,严格按照MSS进行传输,MSS的大小与本地的网卡息息相关。即MSS+ipheader+tcpheader<=MTU;MTU是最大传输单元(数据链路层对网络数据的限制),每一个网卡都有最大的传输单元他是数据链路层对网络数据的限制他是依照传输电器的特性决定的。因此MSS一定小于MTU。TCP在每次发送数据的时候,数据的大小都不会超过MSS。只用TCP有MSS的限制,UDP是没有的(uint_16)

2.4可靠传输

2.4.1保证可靠

a.确认应答机制

回复确认数据包的工作,在网络协议栈的TCP协议自己就完成了,不需要程序员的介入。而程序员只需要关心的是recv函数从接收缓冲区拿数据。

b.超时重传机制

当发送方发送了一个数据之后,就会开启一个重传计时器,当重传计数器记录的时间超过重传时间,则会重传该报文。

假设发送方给接收方发送了一个消息,在发送的同时会开启一个重传计时器,重传计时器超时的两种可能,一种是发送方发送消息时消息丢失;另一种是接收方在确认数据报在网络中丢失了。不管是那种情况,对于发送方而言,都不能确定有没有到达接收方。超时时间之后则会重传消息。

那么超时重传的时间是固定的吗?并不是。因为网络的转发能力不是一成不变的。

RTO:超时重传时间        RTT:报文往返时间        RTO:2*RTT

RTT(预测本次数据包的报文往返时间) = RTT(prev前一段时间)*0.9 + RTT(pprev前前一段时间)*0.1

2.4.2保证传输效率

a.提高发送方发送的效率&接收方接受的能力

滑动窗口机制

  1. 前提:TCP在确认应答机制和超时重传机制下,已经可以保证数据时可靠有序到达对端。但是如果单单只有该两个机制,就会导致TCP的发送发在发送下一个数据之前,会等待上一个数据的确认应答,这样的整体发送效率会比较低。
  2. 机制:滑动窗口机制允许一次性发送多个分组(每个分组的大小不会超过MSS)到网络当中进行传输,当发送方接收到最早分组的确认应答之后,窗口可以向后滑动,包括下一个可以发送的分组。
  3. 实现效果:滑动窗口机制提高发送方和接收方的吞吐能力,提高了双方传输的效率。

问题:TCP在双方发送数据的时候,TCP发送维护的窗口大小时一成不变的吗?

 首先我们理解滑动窗口是指允许一次性丢到网络当中分组的集合,而分组的个数就是窗口的大小。发送方窗口是动态变化的,取决于接收方通告的窗口大小。

  • 前提:TCP协议字段当中"16位窗口大小";消息发送方告知消息发送方,消息接收方的接受能力的字段。

  • 当TCP的接收方接收到数据之后,会将给数据先缓存在TCP的接收缓冲区当中。TCP的接收缓冲区的大小并不是无限制的,一旦应用层不调用recv从接收缓冲区当中获取数据时,就会导致TCP接受缓冲区随着一直接收发送方发送的数据而接受缓冲区的大小逐渐变小

结论1:接收方通过窗口大小,告知发送方字节的接受能力;接收方在发送数据的时候,会按照接收方通告的接受能力动态的调整自己的发送数据量(窗口大小)。这就是在滑动窗口地下的流量控制。TCP流量控制(TCP的流控制),就是TCP通过当前协议字段当中的"窗口大小"字段来控制双方发送数据的数据量。

结论2:如果双方的发送数据的两不加以控制,则可能会导致接收方由于接收能力不足,而将发送方发送的数据丢弃掉,触发超时重传机制。如此往复,网络中就会充斥大量的重传数据包,占用宽带资源。导致整个网络的转发能力变差

0号窗口

如上图所示, 当发送方接收到"0号窗口"之后,就会将滑动窗口当中的窗口大小调整为0,暂时不发送数据到对端。在这里有两种恢复发送方给接收方发送数据。

  • 发送方主动给接收方发送窗口探测包,询问接收方的接受能力。注意,窗口探测包的数据的大小位固定的1字节
  • 接收方主动给发送方发送窗口更新通知。

滑动窗口丢包

(1)丢失某个分组的ACK

 当主机A分了六组发送数据,但是此时主机B给出的确认应答却在传输中丢失了,如上图在确认序号一个是1001号时的确认应答丢失了,但是下一个确认应答达到对端了,那么2001之前的所有序号数据主机B都受到了,就不需要主机A进 行重传了。当窗口当中某个分组的ACK丢失后,可以通过收到的高确认序列号来确认丢失ACK的分组数据是否到达对端。

 (2)丢失某个分组数据

结论:如果数据丢失则一定要重传。 如图所示,接收方的接收缓冲区有一个功能,它会将缺失的数据之后的所有数据缓存到接收缓冲区当中,虽然接收方的接收缓冲区已经拿到这些数据,但在recv()进行调用时不能被拿走。不被拿走是因为当前前面缺失了数据,如果他拿走了这些数据,那么TCP就没有保证数据的有序,那么在这里递交给应用测的数据不是有序的,但是TCP是面向字节流的,就会导致应用层不会知道数据的顺序是什么,那么处理的数据就不是唯一的。

拥塞控制机制:慢启动,拥塞避免,快重传,快恢复

 拥塞控制+流量控制:作用TCP的双方对发送数据的速率,提高了TCP的发送速率。

问题:一开始就要按照滑动窗口的大小发送数据吗?

否,没有考虑到网络的转发能力是否能够满足一次性传输(转发)窗口的大小。

  • 网络传输的时候,数据实在共享网络当中进行传输的。换句话数,网络被多个网络收发双方所使用。同时在转发多个人数据。
  • 网络链路当中的路由器/交换机,转发能力是有上限的。
  • 网络链路当中的传输介质(光纤/双绞线)是有传输上线的。

结论:TCP双方在发送数据的时候,为了不是因为网络原因,频繁的重传,则TCP双方在发送数据的时候,也考量网络转发能力

拥塞控制机制

他本质也是在控制发送方发送数据的量。因此最终发送的数据量=min(发送方的滑动窗口大小(取决于接收方的接受能力),拥塞控制机制当中的拥塞窗口大小(取决于网络拥塞程度))

  • 慢启动:TCP通信双方在建立连接之后,先发送少量的数据(拥塞窗口数据),探测网络的转发能力(根据TCP数据包的往返时间),根据网络转发能力,主动调节拥塞窗口大小。(拥塞窗口的大小位1个分组(1个MSS))。拥塞窗口的大小随着传输轮次的增加呈指数增长。慢开始门限时在拥塞窗口大小小于慢开始门限的时候,是呈指数增长。当拥塞窗口大小超过慢开始门限,则切换为拥塞避免算法。拥塞窗口大小从指数增长到线性增长的原因,往网络中丢入的数据是依次加大的,但是网络的转发能力是有限的,就会导致TCP数据丢包(一旦发现有网络数据丢包了,发送方就会认为网络拥塞了,立即下调拥塞窗口大小)。
  • 拥塞避免:随着轮次的增加,拥塞窗口大小呈线性增长(一次交1个分组)
  • 快重传:当发送方发送的网络数据包丢失后,发送方还没有触发超时重传机制的时候,由接收方快速的确认丢失报文的起始序号,告知发送方该报文丢失,则发送方不需要等到超时之后再重传。它不会阻塞发送方的发送窗口的向后滑动的过程,也就是不会阻塞发送方发送的速率。
  • 快恢复:早期TCP发现网络拥塞之后,是将拥塞窗口调整为1(即一个分组),从慢开始重新增长拥塞窗口的大小,重新调节面开始门限(新的慢开始门限 = 拥塞窗口发生时,拥塞窗口的一半);现在的做法是快恢复,一旦发现网络拥塞,则计算机的慢开始门限从新的慢开始门限执行拥塞避免算法

b.网络的转发能力

  • 延时发送机制:NAGLE算法:如果当前发送方存在少量数据需要发送,则稍微等一下,等待数据量变大,在统一进行发送。但是工业场景中都是禁止使用演示发送的,比如说机械制造业,医疗领域,金融领域。
  • 捎带应答机制:运用于双方都想给对方发送数据的场景,快速的进行交互,就将ACK放到PSH数据包当中携带给对方
  • 延时应答机制:接收方收到了数据,为了给消息发送通告更大的窗口大小,等待一段时间,如果在这段时间内,应用层将数据从(recv函数)接收缓冲区当中接收走后,接收方在恢复确认的时候,就可以给发送方通告更大的窗口大小了。
  • 心跳机制(TCP保活机制):判断空闲连接的双方是否是正常状态。如下图,在连接空闲的时候,就会启动一个保活计时器,记录连接空闲的时间,一旦当连接的空闲时间超过2小时,则则主机BDE TCP协议会主动给主机A发送保活探测包(心跳包),如果主机B一直没有收到探测包的应答,则会每75s发送一次,总共发送10次。如果10次探测包都没有应答,则认为对端已经处于不正常状态,则主动断开连接。如果有应答,则认为连接正常。重新启动保活计时器进行计时。

     2.5面向字节流

  • 从发送数据的角度理解:应用层的应用程序在发送数据的时候,时调用send函数将应用层数据递交给传输层的TCP协议,掉交给TCP协议之后,数据是暂时保存在发送缓冲区当中。但是一定不要认为TCP在发送数据的时候,是按照应用程序调用send函数的规律来进行发送的。TCP发送的数据一定是小于MSS,有自己发送数据的规律、
  • 从接收的角度理解:但数据到达接收方的接收缓冲区之后,接收方的应用程序调用recv函数可以接收任意字节。

面向字节流服务会导致数据之间没有边界,产生TCP粘包问题。这就引出一个问题,如何解决TCP粘包问题。在应用程自定制协议-->应用层头部+应用层数+间隔符。(完整讲解

以上是关于Linux--网络3(传输层)的主要内容,如果未能解决你的问题,请参考以下文章

计算机网络3.1 运输层概述 与 多路复用/分解

Linux 系统的网络基础_all

11linux网络管理介绍

Linux--网络(网络协议,传输及地址管理)

linux socket编程系统调用栈

[Linux] Linux网络之网络层协议详解