目录
一.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的应用层协议:
特性:面向连接,可靠传输,面向字节流
三次握手时双方发送数据包的名称如下图:
客户端调用connect接口发起三次握手,他会给服务端发送SYN数据包(连接数据包),服务端接收到SYN数据包后,通常会回复一个ACK+SYN数据包(ACK在这里的作用是服务端用来确认客户端发送给服务端的SYN数据包服务端接收到了,SYN在这里反映了服务端也想要和客户端进行连接),此时客户端给服务端发送ACK数据包,用来确认服务端发送个客户端的SYN数据包客户端收到了。
如下图所示:
当客户端发出去SYN数据包时,此客户端处于SYN_SENT状态,这是TCP中的一个状态,说明将SYN发送出去了。当服务端接收到SYN数据包时,服务端状态变成SYN_RCVD。当服务端给客户端发送了一个ACK+SYN数据包时,并且客户端收到了这个数据包,此时客户端的状态变成了ESTEBALISHED表示连接已经建立,此时客户端认为连接已经建立,但是此时服务端并不认为连接是已经建立的,换一句话说当前的连接还在内核的未完成连接队列当中,即使此时服务端去调用accept(),也不会将连接从内核中拿出来,除非当客户端给服务端发送ACK数据包时,服务端的状态变成ESTEBALISHED状态,这时当前连接才会从内核的未完成连接队列中放到已完成连接队列中去。此时在调用accept()时才会获取到连接。
根据上面分析,TCP必须握手三次才能建立连接的原因:
四次挥手并不限定于两者谁先断开连接,双方都有可能先断开连接,在这里我们设置双方为注定断开连接方与被动断开连接方。我们在这里简称a方与b方。
a方在这里会主动发起一个FIN报文,b方在收到FIN报文后会回复一个ACK(表示发送的FIN报文b方收到了),紧接着b方再发送一个FIN报文(回应断开连接),然后a方收到后,在对b方发送一个ACK表示确认。此时四次挥手结束。
当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状态。
在进行理解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。
处于TIME_WAIT状态的程序,还需要将端口占用着,等待2MSL之后,在释放调用端口。占用端口的原因是防止被动断开连接方重传FIN报文。但是在这里如果当服务端作为主断开连接方,并且服务端进程还退出了想要快速启动服务端程序就会出现如下问题。
由于上面的这两点,如果想要快速启动服务端程序时就会报错bind:Address areadly in use.因此我们引入端口重用,直接调用下面端口。
int opt = 1;
setsockopt(listenfd, SOL_SOCKER, SO+REUSEADDR, &opt, sizeof(opt));
只有被动断开连接方才会存在的状态。处于CLOSE_WAIT一方需要调用close函数关闭新连接的套接字。如果被动断开连接方存在大量的CLOSE_WAIT状态,那么需要check代码当中是否有阻塞,循环导致被动断开连接无法调用到close。在CLOSE_WAIT状态到LAST_ACK状态中,需要被动断开连接方调用close函数,且被动断开连接方还可以给主动断开连接放发送一些数据。
上面的命令如果去掉port [抓取数据的端口],则没有使用端口进行过滤,那么抓出来的数据包比较大。一般将抓出来的文件通过wireshark进行分析。
在这里注意:想要看到三次握手后的过程,需要先开启抓包,再启动程序。
在三次握手发送数据包的过程当中,在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在发送数据的时候,数据的每一个字节都会进行标识。
总结:
在这里存在的问题:
三次握手建立之后可以双发可以正常发送数据吗?答:不会。因为在三次握手之后在没有调用accpet函数的时候连接还是处在内核当中的已完成练级队列当中,并没有被调用回来。
回复确认数据包的工作,在网络协议栈的TCP协议自己就完成了,不需要程序员的介入。而程序员只需要关心的是recv函数从接收缓冲区拿数据。
当发送方发送了一个数据之后,就会开启一个重传计时器,当重传计数器记录的时间超过重传时间,则会重传该报文。
假设发送方给接收方发送了一个消息,在发送的同时会开启一个重传计时器,重传计时器超时的两种可能,一种是发送方发送消息时消息丢失;另一种是接收方在确认数据报在网络中丢失了。不管是那种情况,对于发送方而言,都不能确定有没有到达接收方。超时时间之后则会重传消息。
那么超时重传的时间是固定的吗?并不是。因为网络的转发能力不是一成不变的。
RTO:超时重传时间 RTT:报文往返时间 RTO:2*RTT
RTT(预测本次数据包的报文往返时间) = RTT(prev前一段时间)*0.9 + RTT(pprev前前一段时间)*0.1
滑动窗口机制
问题:TCP在双方发送数据的时候,TCP发送维护的窗口大小时一成不变的吗?
首先我们理解滑动窗口是指允许一次性丢到网络当中分组的集合,而分组的个数就是窗口的大小。发送方窗口是动态变化的,取决于接收方通告的窗口大小。
结论1:接收方通过窗口大小,告知发送方字节的接受能力;接收方在发送数据的时候,会按照接收方通告的接受能力动态的调整自己的发送数据量(窗口大小)。这就是在滑动窗口地下的流量控制。TCP流量控制(TCP的流控制),就是TCP通过当前协议字段当中的"窗口大小"字段来控制双方发送数据的数据量。
结论2:如果双方的发送数据的两不加以控制,则可能会导致接收方由于接收能力不足,而将发送方发送的数据丢弃掉,触发超时重传机制。如此往复,网络中就会充斥大量的重传数据包,占用宽带资源。导致整个网络的转发能力变差。
0号窗口
如上图所示, 当发送方接收到"0号窗口"之后,就会将滑动窗口当中的窗口大小调整为0,暂时不发送数据到对端。在这里有两种恢复发送方给接收方发送数据。
滑动窗口丢包
(1)丢失某个分组的ACK
当主机A分了六组发送数据,但是此时主机B给出的确认应答却在传输中丢失了,如上图在确认序号一个是1001号时的确认应答丢失了,但是下一个确认应答达到对端了,那么2001之前的所有序号数据主机B都受到了,就不需要主机A进 行重传了。当窗口当中某个分组的ACK丢失后,可以通过收到的高确认序列号来确认丢失ACK的分组数据是否到达对端。
(2)丢失某个分组数据
结论:如果数据丢失则一定要重传。 如图所示,接收方的接收缓冲区有一个功能,它会将缺失的数据之后的所有数据缓存到接收缓冲区当中,虽然接收方的接收缓冲区已经拿到这些数据,但在recv()进行调用时不能被拿走。不被拿走是因为当前前面缺失了数据,如果他拿走了这些数据,那么TCP就没有保证数据的有序,那么在这里递交给应用测的数据不是有序的,但是TCP是面向字节流的,就会导致应用层不会知道数据的顺序是什么,那么处理的数据就不是唯一的。
拥塞控制机制:慢启动,拥塞避免,快重传,快恢复
拥塞控制+流量控制:作用TCP的双方对发送数据的速率,提高了TCP的发送速率。
问题:一开始就要按照滑动窗口的大小发送数据吗?
否,没有考虑到网络的转发能力是否能够满足一次性传输(转发)窗口的大小。
结论:TCP双方在发送数据的时候,为了不是因为网络原因,频繁的重传,则TCP双方在发送数据的时候,也考量网络转发能力。
拥塞控制机制
他本质也是在控制发送方发送数据的量。因此最终发送的数据量=min(发送方的滑动窗口大小(取决于接收方的接受能力),拥塞控制机制当中的拥塞窗口大小(取决于网络拥塞程度))。
面向字节流服务会导致数据之间没有边界,产生TCP粘包问题。这就引出一个问题,如何解决TCP粘包问题。在应用程自定制协议-->应用层头部+应用层数+间隔符。(完整讲解)