传输层udp和tcp协议格式

UDP协议

UDP协议端格式

 udp的前八个字节是报头,后面部分就是有效载荷。而目的端口号就保证了udp向应用层交付的问题。

而针对于报头和有效载荷分离是根据固定八字结的报头长度。数据的长度就是取决于报头中16位udp长度字段的大小来确定整个udp报文长度,所以理论上udp协议传输的有效数据长度是有限制的(2^16-1-8字节)实际大小取决于IP层最大传输单元MTU的值。这也恰恰能说明udp是面向数据报的特点。16位udp检验和的功能就是用于确保UDP数据包的完整性。

 UDP的特点

  •  无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接。
  • 不可靠: 没有确认机制,,没有重传机制。 如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
  • 面向数据报: 不能够灵活的控制读写数据的次数和数量。

    对于面向数据报的具体解释:应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并。例如使用udp传输100字节的数据时,发送端调用sendto就一次发送一百字节,接收端recvfrom也是一次性接收一百字节的内容

 UDP报文的管理

当udp服务器收到了不同客户端发送的大量报文时,此时我们的服务器就需要对这些udp报文进行管理,而且udp协议的报文在操作系统看来其实就是一个结构体字段,所以对udp报文的管理其实就是对一个个的结构化字段通过数组的形式或链表的形式组织存储起来。

 所以数据在应用层到传输层的拷贝的时候,操作系统就会先申请一段缓冲区然后构建sk_buff的结构体,并将udphdr结构体的报头字段拷贝进缓冲区中,同时sk_buff中的字段指向发生改变,所以往后就直接处理sk_buff的数据对象就行。

sk_buffer(socket buffer)是Linux操作系统内核为网络系统提供的描述结构体,主要是用于管理控制数据包。但是sk_buffer本身不会直接存储数据,是通过关联的缓冲区来进行 数据的存储管理。

 UDP缓冲区

  • UDP没有真正意义上的发送缓冲区。调用sendto会将数据交给内核,由内核直接将数据传给网络层协议进行后续的数据交付。
  • UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了, 再到达的UDP数据就会被丢弃。

TCP协议 

TCP协议端格式

  首先需要知道tcp报头和有效载荷是如何进行分离的?

首先直接读取20字节的除选项外的报头的长度,而报头中有一个四字节的头部长度可以表示的范围就是0~15,而它的每一个单位是四字节,所以首部长度实际表示的范围就是0~60字节。所以tcp的报文选项的范围就是0~40字节。

其中选项长度中有一个字段是最大消息长度MSS,是 TCP 协议中定义的单个 TCP 报文段能够携带的最大数据量,它不包括 TCP 首部的长度。当tcp在建立连接时,通信双方就会进行MSS协商,选择较小的MSS作为最终MSS。

TCP报头字段的认识

16位窗口大小

16位窗口大小中填充的数据是接收端缓冲区中剩余空间的大小,最大是65535(2^16-1)字节。用于保证tcp通信时,发送端会确保它发送的数据量不超过接收端声明的窗口大小。实际上是通过确认报文(ACK)中的窗口大小字段告诉发送端它可以接收的数据空间大小。 

也就是发送消息的一端中会再tcp报文中的16位窗口大小中标记自己当前接收缓冲区剩余空间大小。即称作流量控制


 32位序号和确认序号

tcp的数据传输需要保证其可靠性,也就是需要保证数据的按序到达。因此tcp报文中就存在序号和确认序号来标记当前发送和确认的数据。而确认序号值等于收到的序号值+1,表示发送方下一次发送数据的起始位置就是该确认序号的位置表示确认序号之前的所有报文都被成功接收到。 

而且还有一点,在报文接收的过程中,是要进行确认应答的,也就是发送ACK并填充窗口大小和确认序号等信息,但如果此时还要进行有效数据的发送的话,那么确认应答和有效数据会合并成一条报文进行发送即捎带应答策略。如下就是捎带应答(其实收到应答就意味着上一条发送出去的数据被成功接收了):


六个标志位和紧急指针

  •  URG: 紧急指针是否有效
  • ACK: 确认号是否有效
  • PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
  • RST: 对方要求重新建立连接,我们把携带RST标识的称为复位报文段
  • SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
  • FIN: 通知对方, 本端要关闭了,识别为结束报文段

 我们发送报文时设置标志位的本质其实就是对应的比特位置为1。而不同的标志位标识着不同报文的类型。

tcp的通信是有序号和确认序号的,所以信息的通信是可以按照序号大小顺序进行按序处理和响应的。但是客户端与服务器通信时会有紧急任务的,紧急任务是不按照大小序号进行排队的,而应该直接“插队”,优先进行处理。而紧急任务的标识就是URG字段 ,16位紧急指针指向的紧急数据就是在当前报文中效数据的实际偏移量,而该偏移量处的一个字节就是紧急数据的内容。这一个字节的数值都表示着具体的紧急处理,一般都是对服务的健康状态和监控的检测处理。


ACK一般就是接收方成功的接受到数据时设定的标志位,一般都是伴随着下一次与有效数据一起发送回去(捎带应答)。

PSH的具体的一个应用场景就是:当接收方收到大量发送方的数据未及时处理时,这些数据会按照序号按顺序先存在接收缓冲区当中,当接收方的缓冲区填满时,此时应答报文的窗口大小就被设置为0,发送方会暂停有效数据的发送即进行阻塞,但是此时发送方会定期的给接收方发送询问报文(不带有效数据的报文,PSH标志位置为1),同时接收方在缓冲区有数据时立即进行应答报文的窗口字段更新,此时发送方可以根据接收方的窗口大小进行流量控制进而发送有效信息。一般用于数据需要尽快被交付的场景。

RST其中一个场景就是当tcp建立连接的三次握手的第三次ACK失败时设置的报文标志位(双方没有都成功建立好连接)。我们首先要知道对于发送方而言,如果接收到了应答则表明上一次发送的数据报文被对方成功接收到了。所以在进行tcp三次握手时,如果第三次的ACK未被服务端成功接收的话,此时客户端由于在第二次收到ACK即判定为成功建立连接,而服务端没有收到ACK报文即未建立连接。所以当下次客户端向服务端发送数据时,服务端由于未和该客户端建立连接就会给客户端回显一个tcp报文,将RST标记置为1,所以此时客户端收到信息后就会重新开始与服务器建立连接

 

16位检验和

确保数据完整性:TCP校验和的目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。

TCP面向字节流的理解(缓冲区)

我们知道tcp数据发送是面向字节流的,而且tcp报文不像udp报文一次发送对应着一次接收。接收方在收到tcp报文时就是将报头和有效载荷分离,然后通过报头中的序号将有效载荷按序放进接收缓冲区中,上层就不断从缓冲区中拿数据,而这缓冲区就像队列一样从一端被拿数据,另一端放入数据,呈现流的状态

而且tcp的缓冲区可以看作是char budder[] 的数组,因为我们的sk_buff是tcp缓冲区中数据存储的实际结构,当我们调用send进行发送数据时会先将数据拷贝到发送缓冲区中,实际就是拷贝到sk_buff中,然后进行分段发送。我们报头中的序号数值其实就是从发送缓冲区中需要传输的字节流中的每一个字节进行顺序编号。这并不一定是一次性发送完缓冲区中的所有数据,是多次发送,流式发送,并将要发送的数据编号,发到哪个字节就将报头中的序号设置成具体的字节序。

代码层面的理解

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区。

  • 调用write时, 数据会先写入发送缓冲区中,如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去。
  • 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区。然后应用程序可以调用read从接收缓冲区拿数据。
  • 另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个就叫做全双工。

 超时重传机制

超时重传一般都是发送方发送数据报文以后在特定的时间间隔内没有收到接收方的相应报文的响应。而这一般有两种:发送方报文丢包或者接收方响应报文丢包

而对于第一种, 直接重传报文就行,而对于第二种情况就会导致接收方收到两次一样的报文,而接收方会在接收缓冲区中通过两次收到的报文序号值来判定是不是重复报文,如果序号值相同,则会自动进行报文丢弃。

而还有一点:发送方一旦报数据发送出去是不会立马将发送的数据从发送缓冲区(滑动窗口)中移除,而是要等到接收到接收方的ACK报文以后才会移除,因为发送出去的数据并不是一定会接收成功,可能会发生重传的情况,所以数据理应暂时被保留(应答可以确认序号以前的数据都发送成功了)。

 超时重传时间

  • Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
  • 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
  • 如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
  • 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接

连接管理机制 

服务端与客户端建立连接进行通信的时候是有描述当前服务端与客户端各自状态的描述符字段,从而系统可以更好的对双方连接进行管理。而且服务端一般不仅仅与一个客户端建立连接,会有多个连接方,从而服务端操作系统需要对所有的连接创建struct结构体进行描述客户端的连接信息,其中就有所建立连接的客户端状态字段。所以连接本质就是双方所维护的数据结构。

而对于客户端与服务器的状态描述符字段本质就是系统所维护#definde定义的宏值,如1,2,3……

例如在三次握手时的状态变化过程:服务端起初是LISTEN监听状态,当客户端向服务端建立连接时发送SYN标志位的报文时,就会将状态从CLOSED状态转变成为SYN_SENT而服务端接收到客户端的连接请求后就会设置当前状态为SYN_RESD,而此时客户端会返回SYN+ACK标志位字段的报文给客户端,所以此时客户端成功收到后就会设置为ESTABUSHED状态,最终客户端返回ACK被服务端接收后,服务端也会设置状态为ESTABUSHED状态,至此双方算是成功建立好连接。

 为什么要进行三次握手

我们知道对于发送方而言,如果接收到了应答才能表明上一次的发送是被成功接收到的。这就保证了可靠性。

如果说是一次握手就成功建立连接的话,那么如果客户端恶意的不断向接收方发送连接,那么就会造成SYN洪水,而且还会占用空间,因为服务端要创建结构体管理连接。其实三次握手也会存在SYN洪水的问题,就如大量客户端去访问服务器,但是服务器未响应的情况。但是相较于一次握手要好很多,也不会那么容易出问题。如果两次握手其实也是不行的,因为第二次握手成功后只能保证客户端建立连接成功(知道对方服务器可以进行收发),但是服务器并不能保障对方客户端可以进行收消息。而只有三次握手才能保证双方两个朝向的信道是通畅的,因为三次握手的能最小成本的保证双方既能发数据也能收数据。同时三次握手在进行SYN+ACK的时候客户端就开始建立连接,而服务端最后收到ACK时才会进行建立连接,所以此时尽管最后一个ACK丢失了也不会对服务器造成任何损耗而且三次握手也防止了旧连接请求的干扰。

其实三次握手也可以拆分成四次,之所以三次握手是因为一般应答和响应几乎是同一时间发出去的,而且捎带应答有降低成本提高效率的特点。

四次挥手的理解

同样四次挥手也可以确保双方都断开连接终止通信,也就是双方既要知道对方要断开连接,也要发送FIN断开连接,tcp通信双方都是对等的。

但是有一点,四次挥手几乎不会捎带成三次挥手,因为客户端断开连接之后,服务端并不一定会紧接着也断开连接,这之中可能会有相关等待网络数据送到处理或者向客户端单向的发送数据(因为有shutdown系统调用选择性的关闭读写端),所以几乎不会进行三次挥手(但是也有可能)。


四次挥手的状态变化

当客户端主动断开连接发送FIN报文后,状态就会设置成FIN_WAIT1,如果此时服务端不关闭连接的话,其状态就被设为CLOSW_WAIT。后续客户端收到ACK后状态就转变成FIN_WAIT2。

最终当服务端发起连接断开后状态就被设为LAST_ACK,而客户端收到并确认ACK后就变成TIME_WAIT状态,最终服务端会进入CLOSED状态,而客户端会等TIME_WAIT状态的时间结束后才会进入CLOSED状态。


CLOSED_WAITE状态

该状态一般会维持较长时间,所以我们客户端关闭连接以后,服务端也要close关闭套接字描述符。


TIME_WAIT状态

主动断开连接的一方会进入TIME_WAIT状态,需要等待30或60秒(通常是2倍的MSL,即两倍的报文最大生存时间)才能重新建立连接。

 当客户端处于TIME_WAIT状态时,表明客户端连接尚未被彻底释放(虽然客户端调用close关闭连接,但是操作系统内部还是会维护这个连接TIME_WAIT时间),相应的客户端端口是正在使用的状态,因此客户端无法立即在同一个端口上继续和服务器进行连接(客户端一般采用随机端口)。

解决TIME_WAIT状态引起的bind失败方法:

setsockopt系统调用可以实现端口和地址的复用。相当于是无视TIME_WAIT状态:

#include <sys/types.h> 
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

设置该状态的意义:TIME_WAIT状态的主要目的是确保客户端在关闭连接后,有足够的时间等待来自服务器的可能存在的延迟报文段(例如服务端曾经向客户端发送的报文数据阻塞在路由器进行超时重传,重传的数据被接受,但是当连接关闭后缓冲区被清空,再次建立连接时,阻塞在路由器的数据姗姗来迟)没有TIME_WAIT状态的话最终在下一次立即重新建立相同四元组的连接时会导致接受的数据是源于上一次连接中阻塞在路由器的数据,从而干扰到新连接的服务器。所以TIME_WAIT状态的设置是为了防止旧的连接状态与新的连接状态混淆,从而导致数据错误

设置该状态的时间:TIME_WAIT的时间通常是两倍的报文最大生存时间,这个时间长度确保了即使在网络延迟较高的情况下,旧连接的报文丢失,不会对新连接造成干扰。


三次握手同步起始序号

三次握手的过程中会提前将起始序号传递给对方,因为我们tcp报文通信的时候并不是将起始序号设为0的,而是随机的。这是在TCP连接建立时,客户端和服务器都会将各自的起始序列号进行交换。这个起始序列号的主要目的是增加TCP的安全性,防止恶意攻击者猜测或伪造TCP报文段

滑动窗口机制

 TCP滑动窗口机制是TCP协议中用于流量控制和拥塞控制的关键技术之一(维护在发送缓冲区中)。通过滑动窗口,TCP可以确保发送方不会发送过多的数据,从而避免接收方缓冲区溢出,同时也允许发送方在不需要等待每个数据包的确认(ACK)的情况下继续发送数据。

我们在发送tcp报文的时候是有序号和确认序号的,标识着发送的数据段的结尾位置,和下一次发送数据的起始位置。在tcp的三次握手的过程中,我们tcp报文中有16位窗口大小字段标识着当前这一方的接收数据的能力。而对于发送方而言每一次发送数据时滑动窗口的大小就取决于接收方的缓冲区剩余空间大小(接收方的接受能力),而滑动窗口内的数据就是已发送但未被ACK应答的数据和正要准备发送的数据,当滑动窗口中已发送的数据接收到ACK应答以后,滑动窗口的大小保持不变,并后移。

 对于滑动窗口的本质其实就是两个指针维护的一段区间,因为我的tcp发送缓冲区是看作一段char outbuffer[]的数组,对我们发送缓冲区中需要传输的字节流中的每一个字节进行顺序编号,其中就可以用两个指针模拟发送窗口,而对于窗口的偏移其实就是指针的移动。所以滑动窗口的大小就可以理解为:发送方发送携带序号的tcp报文数据,接收方ACK后响应确认序号并携带窗口大小,所以对于发送方而言,滑动窗口的大小就是win_start=确认序号;win_end=win_start+接收方的窗口大小,所发送方每次的窗口大小其实就取决于接收方ACK报文的16位窗口大小字段的值。窗口大小也是实时更新的(可以为0,可以变大,可以变小)。

 报文丢失时的窗口情况

当发送方发送窗口内的所有数据时,此时序号依次是:2000、3000、4000、5000。而此时接收方未收到序号为2000的报文,收到了剩余的报文时,此时接收方通过缓冲区中序号的值就会发现有一段报文未到达,所以数据就不能被取走,所以B、C、D响应报文的确认序号都为1001,(确认序号的规定就是当前确认序号之前的所有发送方的数据都已被接收方接收)相当于告诉发送方立即补发滑动窗口内序号为1001的数据,即滑动窗口不移动,大小也不变。这也叫做快重传机制(而不是等到超时了继续进行重传)(一旦发送方检测到三次重复确认同一个确认序号的值时)。


如果A序号的应答丢失了的话,其实是不会造成任何影响的,因为接收缓冲区中已经拿到了这些数据了,所以BCD的响应报文依次就是3001、4001、5001,此时发送方收到响应时就会知道下一次从窗口中序号为5001的报文开始发送。因为确认序号表示的是再次之前的所有报文都被成功接收。


为什么滑动窗口要分段发送

TCP 使用序列号来跟踪每个数据段。如果发送方一次性发送所有数据,并在某个数据段出现错误时收到否定确认(NACK),则可能需要重传整个窗口内的数据,这会导致不必要的网络流量和延迟。通过逐步发送数据并等待确认,发送方可以只重传出现错误的数据段,从而提高效率。

拥塞控制机制

在tcp报文数据发送的过程中会受到网络的影响,如果发送方的报文数据出现了大面积的丢包,则发送方就会判定是网络出了问题,从而采用网络拥塞控制机制。

我们知道发送方发送的数据报文少量丢包时会发送重传机制,而重传的数据是通过滑动窗口的大小和确认序号来确定的。而大面积丢包认定为网络故障,如果此时该网络下的所有机器都进行重传的话,就会造成拥塞加剧至网络瘫痪。

拥塞控制算法流程

  1. 当TCP连接开始时,发送方会先发送一个小的数据段1个MSS(最大报文段长度),每收到一个ACK,拥塞窗口加倍(乘以2)。
  2. 当拥塞窗口达到阈值(ssthresh)后,进入拥塞避免阶段,窗口以线性速度增长。每收到一个ACK,拥塞窗口增加1。
  3. 当报文超时重发的时候,慢启动阈值会变成原来拥塞窗口值的一半,同时拥塞窗口置回1,继续指数增长循环上一次的处理流程。所以说拥塞窗口其实就是用来衡量网络健康状态的。

所以说对于发送方而言,发送数据的多少并不仅仅取决于 接收方的接受能力,而且还收到网络拥塞的影响,所以就有:发送方滑动窗口的大小=min(拥塞窗口大小,接受方接收能力大小)。

延迟应答 

 延迟应答是TCP协议中用于提高网络传输效率的一种机制。

原理:

  • 当接收端收到发送端发送的数据后,它并不立即返回ACK应答报文,而是等待一段时间。
  • 在这段等待时间内,接收端可能会处理接收到的数据,并释放一部分接收缓冲区的空间。
  • 当接收端决定发送ACK应答报文时,它会将接收缓冲区当前的剩余空间大小包含在ACK报文中发送给发送端。

优点:有利于报文接收窗口字段的同步,从而提高传输效率。并且减少了网络中ACK报文的数量,从而减少了网络拥塞的可能性。 

 认识listen的第二个参数

int listen(int fd, int backlog);

backlog参数的意义

  • 连接请求队列的最大长度backlog参数+1指定了TCP连接全连接队列的最大长度。这个队列用于存放已完成三次握手(处于established状态)但尚未被accept函数处理的连接请求(可以长时间存在该队列)。
  • 不是限制程序的最大连接数:重要的是要理解,backlog并不是限制服务器程序能够处理的最大连接数,而是限制了在特定时刻等待accept处理的连接请求数量。
  • 队列满后的行为:全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态。新连接会处于SYN_RECV 状态,也就是说三次握手不会成功。
  • 默认值与推荐值backlog的经典值为5,但实际应用中,这个值可能会根据服务器的负载和性能进行调整。在Linux中,还可以使用SOMAXCONN作为backlog的值,以获取系统给出的最大值。

TCP的特点 

可靠性:

  • 校验和
  • 序列号(按序到达)
  • 确认应答
  • 超时重传
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能:

  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

TCP粘包问题 

定义

TCP粘包是指在网络通信中,特别是基于TCP协议(流式套接)进行数据传输时,所以没有像UDP一样的报文长度标记字段。所以TCP方式发送的数据在接收方接收时,按照序号排好序放在缓冲区中,站在应用层的角度, 看到的只是一串连续的字节数据,导致接收方无法正确区分数据包的边界。这一般都是用户层协议的过程中需要考虑的。

解决方法(可以参考http协议字段)

  1. 定长发送法:发送端在发送数据时都以固定长度进行分包,接收方也以固定的长度进行接收。这种方法在数据包长度较为稳定的情况下效果较好。
  2. 尾部标记序列法:在每个要发送的数据包的尾部设置一个特殊的字节序列,用来标示这个数据包的末尾。接收方可对接收的数据进行分析,通过尾部序列确认数据包的边界。
  3. 头部标记+自描述字段:定义一个用户报头,在报头中注明每次发送的数据包大小。接收端在接收到数据后,先读取报头中的长度字段,然后根据长度字段来接收对应长度的数据。

 TCP与UDP使用场景

  • TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
  • UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CR0712

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值