UDP协议
UDP协议端格式
udp的前八个字节是报头,后面部分就是有效载荷。而目的端口号就保证了udp向应用层交付的问题。
而针对于报头和有效载荷分离是根据固定八字结的报头长度。数据的长度就是取决于报头中udp长度字段的大小来确定udp报文长度,因此也可以说明udp传输过程中的数据长度是有限制的。这也恰恰能说明udp是面向数据报的特点。16位udp检验和的功能就是用于确保UDP数据包的完整性。
UDP的特点
- 无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接。
- 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报: 不能够灵活的控制读写数据的次数和数量。
对于面向数据报的具体解释:应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并。例如使用udp传输100字节的数据时,发送端调用sendto就一次发送一百字节,接收端recvfrom也是一次性接收一百字节的内容。
UDP报文的管理
当udp服务器收到了不同客户端发送的大量报文时,此时我们的客户端和服务器的操作系统就需要对这些udp报文进行管理,而且udp协议在操作系统的眼里就是一个个的结构化字段,所以只需要描述这一个个的结构体并组织成链表的形式进行操作系统的管理就行。
所以数据在应用层到传输层的拷贝的时候,操作系统就会先申请一段缓冲区然后构建sk_buff的结构体,并将udphdr结构体的报头字段拷贝进缓冲区中,同时sk_buff中的字段指向发生改变,所以往后就直接处理sk_buff的数据对象就行。
UDP缓冲区
- UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作。
- UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃。
TCP协议
TCP协议端格式
首先需要知道tcp报头和有效载荷是如何进行分离的?
通过头部长度可以推算出。首先可以知道tcp的标准报头长度(除选项字段)的大小是二十字节,而头部长度的大小是小于15(2的四次方-1)的,单位是4字节,所以报头的最大取值是15*4字节大小。所以tcp报头的长度就是头部长度*4字节。同时也可以减20得到选项长度。
TCP报头字段的认识
16位窗口大小
16位窗口大小中填充的数据是接收端缓冲区中剩余空间的大小,最大是65535字节。用于保证tcp通信时,发送端会确保它发送的数据量不超过接收端声明的窗口大小。它通过确认报文(ACK)中的窗口大小字段告诉发送端它可以接收的数据空间大小。
也就是发送消息的一端中16窗口大小的报文字段都会填充自己当前缓冲区剩余空间大小。即流量控制。
序号和确认序号
tcp的数据传输需要保证其可靠性,也就是需要保证数据的按序到达。因此tcp数据传输的过程中是要有序号字段数据的,而数据成功接收应答时要有确认序号字段内容。而确认序号值等于收到序号值+1,表示发送方下一次发送数据的起始位置就是该确认序号的值。表示确认序号之前的所有报文都被成功接收到。
而且还有一点,在报文发送的过程中,报文中序号和确认序号的数值都被设置了,这也就是说明一端在收到另一端的消息时如果还要发送数据的话,可以确认应答和有效数据一起发送(捎带应答策略),所以此时不仅仅要设置序号还要设置确认序号。同时也说明tcp报头中既要有序号字段也要有确认序号字段。
六个标志位和紧急指针
- URG: 紧急指针是否有效
- ACK: 确认号是否有效
- PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
- RST: 对方要求重新建立连接,我们把携带RST标识的称为复位报文段
- SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
- FIN: 通知对方, 本端要关闭了,识别为结束报文段
我们发送报文时设置标志位的本质其实就是对应的比特位置为1。而不同的标志位标识的是不同报文的类型。
tcp的通信是有序号和确认序号的,所以信息的通信是可以按照顺序进行处理和响应的。但是客户端与服务器通信时会有紧急任务的,紧急任务是不按照大小序号进行排队的,而应该直接“插队”,优先进行处理。而紧急任务的标识就是URG字段置1 ,16位紧急指针指向的紧急数据就是在当前报文的有效载荷中的偏移量,而紧急数据实际只有一个字节。这一个字节的数值都表示着具体的紧急处理,一般都是对服务的健康状态和监控的检测处理。
ACK一般就是接收方成功的接受到数据时设定的标志位,一般都是伴随着下一次与有效数据一起发送回去。
PSH的具体的一个应用场景就是:当接收方收到大量发送方的数据未及时处理时,这些数据会按照序号按顺序先存在接收缓冲区当中,当接收方的缓冲区填满时,此时应答报文的窗口大小就被设置为0,发送方通知数据发送,此时发送方会定期的给接收方发送询问报文(不带有效载荷的报文,PSH标志位置为1),同时接收方也会当缓冲区有数据时进行窗口报文更新,哪种方式一旦被收到就会进行后续正常通信。一般用于数据需要尽快被交付的场景。
RST其中一个场景就是当tcp建立连接的三次握手的第三次ACK失败时设置的报文标志位(双方没有都成功建立好连接)。我们首先要知道对于发送方而言,如果接收到了应答则表明上一次发送的数据报文被对方成功接收到了。所以在进行tcp三次握手时,如果第三次的ACK未被服务端成功接收的话,此时客户端会判定为成功建立连接,而服务端却未建立连接。所以当下次客户端向服务端发送数据时,服务端就会给客户端回显一个tcp报文,将RST连接重置标记为置为1,所以此时客户端收到信息后就会重新开始建立连接。
16位检验和
确保数据完整性:TCP校验和的目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。
TCP面向字节流的理解(缓冲区)
我们知道tcp数据发送是面向字节流的,而且tcp报文不像udp报文,并没有数据长度的标识符。接收方在收到tcp报文时就是将报头和有效载荷分离,然后通过报头中的序号将有效载荷按序放进接收缓冲区中,上层就不断从缓冲区中拿数据,而这缓冲区就像队列一样从一端被拿数据,另一端放入数据,呈现流的状态。
而且tcp的缓冲区可以看作是char budder[] 的数组,因为我们报头中的序号数值其实就是从发送缓冲区中需要传输的字节流中的每一个字节进行顺序编号。这并不一定是一次性发送完缓冲区中的所有数据,是多次发送,流式发送,并将要发送的数据编号,发到哪个字节就将报头中的序号设置成具体的字节序。
超时重传机制
超时重传一般都是发送方发送数据报文以后在特定的时间间隔内没有收到接收方的相应报文的情况,而这一般有两种:发送方报文丢包或者接收方响应报文丢包
而对于第一种, 直接重传报文就行,而对于第二种情况就会导致接收方收到两次一样的报文,而接收方会通过两次报文的序号值来判定是不是重复报文,如果序号值相同,则会进行去重。
而还有一点:发送方一旦报数据发送出去是不会立马将发送的数据从发送缓冲区(滑动窗口)中移除,而是要等到接收到接收方的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的话同样会造成同样的问题。而只有三次握手才能保证双方两个朝向的信道是通畅的,因为三次握手的能最小成本的保证双方既能发数据也能收数据而且三次握手还可以保证建立链接的确定性,也就是双方都可以知道建立连接的结果。
其实三次握手也可以拆分成四次,之所以三次握手是因为一般应答和响应几乎是同一时间发出去的,而且捎带应答有降低成本提高效率的特点。
四次挥手的理解
同样四次挥手也可以确保双方都断开连接终止通信,也就是双方既要知道对方要断开连接,也要发送FIN断开连接,tcp通信双方都是对等的。
但是有一点,四次挥手几乎不会捎带成三次挥手,因为客户端断开连接之后,服务端并不一定会紧接着也断开连接,这之中可能会有相关等待网络数据送到处理或者向客户端单向的发送数据(因为有shutdown系统调用选择性的关闭读写端),所以几乎不会进行三次挥手(但是也有可能)。
四次挥手的状态变化
当客户端主动断开连接发送FIN报文后,状态就会设置成FIN_WAIT1,而服务端一旦受到并发送ACK后,状态就被设为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状态时,表明客户端连接尚未被彻底释放,相应的客户端端口是正在使用的状态,因此客户端无法立即在同一个端口上创建新的连接。
解决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状态的设置是为了防止旧的连接状态(可能由于网络延迟或重传)与新的连接状态混淆,从而导致数据错误。
三次握手传递起始序号
而且还有一点就是在三次握手的过程中会提前将起始序号传递给对方,因为我们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位窗口大小字段的值。窗口大小也是实时更新的。
报文丢失时的窗口情况
当发送方发送窗口内的所有数据时,此时序号依次是:2000、3000、4000、5000。而1此时接收方未收到序号为2000的报文,收到了剩余的报文时,此时接收方通过缓冲区中序号的值就会发现有一段报文未到达,所以就不能将数据放到缓冲区当中,所以响应报文的确认序号都为1001,(确认序号的规定就是当前确认序号之前的所有发送方的数据都已被接收方接收)相当于告诉发送方重发滑动窗口内从序号为2000的开始重新发送数据,即滑动窗口不移动,大小也不变。这也叫做快重传机制(一旦发送方检测到三重重复确认)。
为什么滑动窗口要分段发送
TCP 使用序列号来跟踪每个数据段。如果发送方一次性发送所有数据,并在某个数据段出现错误时收到否定确认(NACK),则可能需要重传整个窗口内的数据,这会导致不必要的网络流量和延迟。通过逐步发送数据并等待确认,发送方可以只重传出现错误的数据段,从而提高效率。
拥塞控制机制
在tcp报文数据发送的过程中会受到网络的影响,如果发送方的报文数据出现了大面积的丢包,则发送方就会判定是网络出了问题,从而采用网络拥塞控制机制。
我们知道发送方发送的数据报文少量丢包时会发送重传机制,而重传的数据是通过滑动窗口的大小和确认序号来确定的。而大面积丢包认定为网络故障,如果此时该网络下的所有机器都进行重传的话,就会造成拥塞加剧至网络瘫痪。
拥塞避免算法
- 当TCP连接开始时,发送方会先发送一个小的数据段(称为报文段或段),并等待接收方的确认。
- 每次收到一个确认,发送方的拥塞窗口(congestion window)就会指数增加(2^n),允许发送更多的数据。
- 慢启动的初始窗口大小通常设置为一个较小的值,然后呈指数增长。
- 这种增长受到一个叫做“慢启动阈值”(ssthresh)的限制,当拥塞窗口达到这个阈值时,TCP会切换到拥塞避免模式(拥塞窗口每次只增加一个MSS)。
所以说对于发送方而言,发送数据的多少并不仅仅取决于 接收方的接受能力,而且还收到网络拥塞的影响,所以就有:发送方滑动窗口的大小=min(拥塞窗口大小,接受方接收能力大小)。
延迟应答
延迟应答是TCP协议中用于提高网络传输效率的一种机制。
原理:
- 当接收端收到发送端发送的数据后,它并不立即返回ACK应答报文,而是等待一段时间。
- 在这段等待时间内,接收端可能会处理接收到的数据,并释放一部分接收缓冲区的空间。
- 当接收端决定发送ACK应答报文时,它会将接收缓冲区当前的剩余空间大小包含在ACK报文中发送给发送端。
优点:有利于接收窗口的提高,从而提高传输效率。并且减少了网络中ACK报文的数量,从而减少了网络拥塞的可能性。
认识listen的第二个参数
int listen(int fd, int backlog);
backlog参数的意义
- 连接请求队列的最大长度:
backlog
参数+1指定了TCP连接全连接队列的最大长度。这个队列用于存放已完成三次握手但尚未被accept
函数处理的连接请求(可以长时间存在该队列),处于ESTABLISHED。 - 不是限制程序的最大连接数:重要的是要理解,
backlog
并不是限制服务器程序能够处理的最大连接数,而是限制了在特定时刻等待accept
处理的连接请求数量。 - 队列满后的行为:全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了,而是会短暂的在半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)处于SYN_RECV状态,即服务器收到客户端的SYN请求但是未响应,直到在限定的时间内全连接队列中的连接被accept。
- 默认值与推荐值:
backlog
的经典值为5,但实际应用中,这个值可能会根据服务器的负载和性能进行调整。在Linux中,还可以使用SOMAXCONN
作为backlog
的值,以获取系统给出的最大值。
TCP的特点
可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)
TCP粘包问题
定义
TCP粘包是指在网络通信中,特别是基于TCP协议进行数据传输时,发送方发送的多个数据包在接收方接收时,由于某些原因被合并成一个数据包,导致接收方无法正确区分数据包的边界。这一般都是自定义协议的过程中需要考虑的。
解决方法
- 定长发送法:发送端在发送数据时都以固定长度进行分包,接收方也以固定的长度进行接收。这种方法在数据包长度较为稳定的情况下效果较好。
- 尾部标记序列法:在每个要发送的数据包的尾部设置一个特殊的字节序列,用来标示这个数据包的末尾。接收方可对接收的数据进行分析,通过尾部序列确认数据包的边界。
- 头部标记+自描述字段:定义一个用户报头,在报头中注明每次发送的数据包大小。接收端在接收到数据后,先读取报头中的长度字段,然后根据长度字段来接收对应长度的数据。
TCP与UDP使用场景
- TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
- UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播;