网络层 UDP协议与TCP协议详解
1. UDP协议
UDP(User Datagram Protocol 用户数据报协议,是不可靠的数据报传输协议,不确保数据安全有序的到达对端。
特点:
- 传输层协议
- 无连接:知道对端的IP和端口号就直接进行传输, 不需要建立连接。
- 不可靠传输:没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报:不能够灵活的控制读写数据的次数和数量。
1. 协议格式
-
16位源端口号:数据从哪个端口发送出来的,换一句话说,数据是从哪一个进程中被发送出来的。 -
16位目的端口号:数据想要到哪一个端口去。 -
16位数据报长度:UDP所能支持的数据长度,即UDP数据最大支持65536的长度。 其中报头长度也包含在数据报长度中,因此报文中数据的大小必须小于65535 - 8 个字节的大小,否则就会丢弃数据报错。 -
16位校验和:校验数据在传输过程中是否失真。采用二进制反码求和算法,验证收到的数据与对方发送的数据是否完全一致,不一致则丢弃。 这里也能体现出UDP不可靠的特性:若接收方检验到数据失真则就直接丢弃不用,若是数据在转发过程中丢失,也直接丢弃。
2. 协议特性
- 无连接:UDP通信,知道对端的IP和端口号就直接进行传输, 不需要建立连接。
- 不可靠传输:UDP没有任何的丢包检测机制,数据包丢了也没有重传机制,并且也没有包序管理机制,因此UDP传输既不保证数据能安全的到达对端,也不能保证数据有序的到达对端。
- 面向数据报:UDP是一种有最大长度限制的数据块传输方式,因为协议格式中限制了一个一个UDP报文必须小于64k,其中数据必须小于64k - 8字节的大小。
- 数据块传输方式:**UDP通信在报头中确定了数据报长度,因此udp的数据的传输时是整条收发的。**在发送端,sendto接口给予的数据会放到发送缓冲区后直接封装头部,然后发送给接收端;在接收端,recvfrom总是只能接收一条完整的数据,而不会出现接收半条或者接收多条的情况。因此recvform给予的缓冲区一定要足够大,若给予的缓冲区大小 小于一条数据的长度,则recvform就会报错。
2. TCP协议
1. 协议格式
-
16位源端口和16位目的端口:描述通信两端之间是哪两个进程在通信。 -
32位序号和32位确认序号:实现TCP的包序管理,以及确认应答机制(丢包检测)。 -
4位数据偏移,描述TCP报头长度(最小是20字节,最大是60字节),在解析TCP报头长度的时候,先取出固定长度20字节,然后根据数据偏移去除指定长度的选项内容。 -
6位保留:暂时没有用处。 -
6位标志位:用于识别报文类型
-
URG:紧急指针有效位。 -
ACK:确认应答: -
PSH:Push标志位,指示接收方接收到带有Push标志的数据时,应尽快将报文段交给应用程序。 -
RST:重置连接标志位,为1时表示tcp连接出现错误,需要释放连接,然后重新建立传输连接。 -
SYN:建立连接请求标志位,为1时表示是一个连接请求或连接接收报文。 -
FIN:释放连接标志位,为1时表示发送方已经没有数据发送了,应释放连接。 -
16位窗口:用于实现滑动窗口机制,进行流量控制,防止发送方数据发送过多。 -
16位紧急指针:是一个带外数据,是与普通数据不同的通道独立传送给用户,描述的是外带数据的结束位置,和正常数据的起始位置。 -
0-40位的选项数据:通常是协商一些信息或者保存一些额外的数据。
2. 协议特性
TCP协议是面向连接、可靠传输、面向字节流的。
1. 面向连接
TCP连接是一个全双工通信,在通信前必须保持双方都具有数据收发的能力,并且TCP是一个有状态的协议,在特定的状态下只能做特定的事情。
三次握手
- 服务端监听套接字首先进入LISTEN状态。
- 客户端新建套接字绑定地址信息后调用connect,发送连接请求SYN,并进入SYN_SENT状态,等待服务器的确认。
- 服务器收到后,新建套接字与之进行连接,新建套接字进行回复,并进入SYN_RCVD状态。
- 客户端收到回复进入ESTABLISHED状态,并回复服务端新建套接字。
- 服务端新建套接字收到回复,进入ESTABLISHED状态,三次握手完成。
常见面试题
1.握手为什么是三次?
- 因为握手两次不安全,四次没必要。tcp通信需要确保双方都具有数据收发的能力,得到ACK响应则认为对方具有数据收发的能力,因此双方都要发送SYN确保对方具有通信的能力。第一次握手是客户端发送SYN,服务端接收,服务端得出客户端的发送能力和服务端的接收能力都正常;第二次握手是服务端发送SYN+ACK,客户端接收,客户端得出客户端发送接收能力正常,服务端发送接收能力也都正常,但是此时服务器并不能确认客户端的接收能力是否正常;第三次握手客户端发送ACK,服务器接收,服务端才能得出客户端发送接收能力正常,服务端自己发送接收能力也都正常。
2.三次握手失败是怎么处理的?
- 客户端发送SYN失败:服务端什么都没收到,就什么都不会做,客户端就会重新发送SYN请求。
- 服务端收到SYN回复后,发送SYN+ACK失败:服务端并不会重新传送SYN+ACK,客户端等待超时后,就会认为SYN丢包了,并重新传送SYN。
- 客户端最后发送ACK失败:服务端等待超时,则会回复RST报文,然后释放新建连接。
四次挥手
- 客户端主动调用close时,向服务端发送结束报文段FIN报,同时进入FIN_WAIT1状态。
- 服务器会收到结束报文段FIN报,服务器返回确认报文段ACK并进入CLOSE_WAIT状态,此时如果服务端有数据要发送的话,客户端依然需要接收。客户端收到服务器对结束报文段的确认,就会进入到FIN_WAIT2状态,开始等待服务器的结束报文段。
- 服务器端数据发送完毕后,当服务器真正调用close关闭连接时,会向客户端发送结束报文段FIN包,此时服务器进入LAST_ACK状态,等待客户端最后一个ACK的到来。
- 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出送确认报文段ACK;服务器收到了对结束报文段确认的ACK,进入CLOSED状态,释放新建套接字。而客户端要等待2MSL的时间,才会进入到CLOSED状态。
常见面试题
1.挥手为什么是四次?
- 在TCP建立连接的时候只需要三次握手,是因为服务端将SYN和ACK合并到一起进行了发送。对于四次挥手来说,主动断开方发送FIN包后只能表示自己不会再主动发送数据,并不代表不能接收数据,被动关闭方如果有数据还要发送,自己还可以接收,因此就不能立即关闭服务器端到客户端的数据通道,所以就不能将服务端的FIN包和对客户端的ACK包合并发送,只能先确认ACK,等别动关闭方无需发送数据时在发送FIN包,所以四次挥手时需要四次数据包的交互。
2.一台主机上出现大量的CLOSE_WAIT是什么原因?应该如何处理?
- CLOSE_WAIT是被动关闭方收到FIN请求进行回复之后的状态,等待上层程序进一步处理,若出现大量CLOSE_WAIT,有可能是被动关闭方主机程序中忘了最后一步断开连接后调用close释放资源,也就是回复了第一个ACK后,四次挥手并没有继续向下进行。只需要加上对应的 close 即可解决。
3.TIME_WAIT状态有什么作用,为什么主动关闭方没有直接进入CLOSED状态释放资源,而是要等待一会儿?
- 如果主动断开方发送ACK后立即进入CLOSED状态,释放套接字,但是这时回复的ACK丢失了,被动关闭方发送FIN包后没有得到ACK确认,超时后就会重传一个FIN包。由于这个时候主动断开方已经释放套接字了,所以并不能就收到这个ACK。如果客户端没有TIME_WAIT状态而直接进入CLOSED状态释放资源,下次启动新的客户端就可能使用了与之前客户端相同的地址信息,就会有两个危害,第一种是这个刚启动的新的客户端绑定地址成功时,就会收到了一个重传的FIN包,对新连接就会造成影响。第二种是如果该新客户端向相同的服务端发送SYN连接请求,但是此时服务端处于LAST_ACK状态,要求收到的是ACK而不是SYN,因此就会发送RST重新建立请求。
- 总结来说就是如果没有TIME_WAIT,直接释放套接字,有可能由于最后一次ACK丢失导致的重传对连接造成影响。
4.为什么TIME_WAIT状态需要经过2MSL才能进入CLOASE状态?
- **MSL指的是报文在网络中最大生存时间,默认一个MSL是60秒。**在客户端发送对服务端的FIN确认包ACK后,这个ACK包有可能到达不了,服务器端如果接收不到ACK包就会重新发送FIN包。所以客户端发送ACK后需要留出2MSL时间(ACK到达服务器器+服务器发送FIN重传包,一来一回)等待确认服务器端缺失收到了ACK包。也就是说客户端如果等待2MSL时间也没收到服务器端重传的FIN包,则就可以确认服务器已经收到客户端发送的ACK包。
5.一台主机上出现大量的TIME_WAIT是什么原因?应该如何处理?
- TIME_WAIT状态是主动关闭方,在进行最后一次ACK发送之后进入的状态,如果一台主机上出现了大量的TIME_WAIT,就意味着这台主机上大量的主动关闭了套接字,常见于爬虫服务器。有两种解决办法,第一种是可以将TIME_WAIT等待的时间调短一些。第二种是使用套接字选项,启用端口服用的功能。
- TIME_WAIT其实更多的是为了保护客户端,因为服务器通常需要绑定固定的地址端口,反而因为TIME_WAIT的原因,主动关闭后无法立即重新启动。
TCP报货机制
服务端与客户端连接成功后长时间没有进行数据发送,服务端并不知道自己是否还与客户端保持着连接,并且不能随便的断开连接。然后在这个过程里面就**有一个保活计时器来进行计时, 当连接上之后客户端没有发送数据,就开始计时,如果长时间没有进行通信(7200秒),每当过一段时间(75秒),如果还没发送数据,则服务端就会给对应客户端发送一个探测数据包,探测客户端目前是否正常,如果多次没有收到相应回复(9次),就认为连接已经断开,释放资源。**这些默认数据都是可以配置的的,并且通过套接字选项可以进行单独设置(setsockopt())。
如果断开连接,在程序中是如何体现的?
-
断开连接,上层recv接收完数据后,继续recv就不再阻塞而是返回0,代表断开连接。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LvjPaCiR-1645599310062)(C:\Users\han\AppData\Roaming\Typora\typora-user-images\image-20220218155519695.png)] -
如果是send,则在断开连接后,会触发异常(SIGPIPE),导致进程退出。如果不想因为连接断开而导致发送数据的时候程序异常退出,则需要对SIGPIPE信号进行自定义或者忽略处理。
2.可靠传输
安全有序的进行传输:
-
面向连接:先确保双方都具有数据收发的能力。 -
丢包检测:确认应答机制,发送的每一条数据都要求对端收到之后进行确认应答。TCP通过肯定的确认应答(ACK)实现可靠的数据传输。当发送端将数据发出之后会等待对端的确认应答。如果有确认应答,说明数据已经成功到达对端。反之,则数据丢失的可能性很大。在一定时间内没有等到确认应答,发送端就可以认为数据已经丢失,并进行重发。 在连续发送中如果先收到了后面的数据,这时候不能进行回复,因为每个确认序号都要保证之前的所有数据都已经收到了,这是为了避免因为确认应答丢失而导致重传。 -
超时重传机制:等待确认应答的时候超时了都没有收到应答,则认为数据丢失,则对数据进行重传,如果多次数据都没有收到应答,则认为连接断开。 -
序号 + 确认序号字段:进行数据包序管理,保证数据有序发送,有序交付。 -
校验和字段:二进制反码求和算法,检验收到的数据与发送的数据是否一致,不一致则丢弃要求对方重传。
避免额外丢包:
-
滑动窗口机制:由于发送方发送数据过多,接收方获取的又太慢,导致接收方的接收缓冲区满了,**将本应该接收的数据丢弃,就又会触发重发机制,从而导致网络流量的无端浪费,**主要是基于协议字段中窗口大小字段来实现流量控制,接收方每接收一条数据就会进行确认应答,在确认应答的时候就会通过窗口大小字段告诉对方,最多在发送多少数据就不要发送了(这个窗口大小不会大于接收缓冲区的剩余空间大小),这样就可以避免因为发送数据过多而导致丢包的情况。 MSS:最大数据段大小,通过下层的数据报大小限制所计算出来一个最大传输数据长度。接收方每接收一条数据就会进行确认应答,在确认应答的时候就会通过窗口大小字段告诉对方,最多在发送多少数据就不要发送了(这个窗口大小不会大于接收缓冲区的剩余空间大小),这样就可以避免因为发送数据过多而导致丢包的情况。 MSS:最大数据段大小,通过下层的数据报大小限制所计算出来一个最大传输数据长度。tcp在发送数据的时候,每个报文都不会大于mss大小,而是在发送缓冲区中截取合适长度数据发送。三次握手的时候就会在协议中填充窗口大小和协商MSS的大小。
- 窗口大小就是指无需等待确认应答而可以继续发送数据的最大值。图中,窗口大小为4个段,也就是4000个字节。
- 发送前四个段的时候, 不需要等待任何ACK, 直接发送。
- 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推。
- 操作系统内核为了维护这个滑动窗口, 需要开辟发送缓冲区来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉。
-
拥塞窗口机制:在数据发送的过程中,如果网络环境突然变差,这时候发送的数据越多越快,则丢失的数据也就越多,导致大量的重传降低效率。TCP引入 慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据。 拥塞窗口一开始很小,但涨幅非常快,以这种形式进行网络试探,当然最大不会超过窗口的大小,一旦传输过程中出现丢包,就会重新开始拥塞。
性能挽回
-
延迟发送:1.延迟发送:发送方延迟发送数据,因为每次发送数据都会涉及到硬件的操作,效率较低,如果在发送大量短小数据的时候就很不划算,因此延迟发送会把多个小数据在发送缓冲区中堆积成为一个大数据进行一次发送。 -
延迟应答:延迟应答机制:因为接收方接收到数据后都会进行确认回复,如果立即进行回复,不可避免大概率窗口都会变小,则发送方的发送数据量就会变小,吞吐量小了,传输性能就低了,因此采用延迟应答,收到数据后不立即进行回复,而是等待一段时间,而这段时间内,上层就有可能将数据从缓冲区取出,则窗口大小会不变甚至变大,保持吞吐量。 -
捎带应答:捎带应答机制:接收方会为发送方发送的每个数据进行确认回复,而一个确认回复就是一个空报头号,而空报头的传输会导致占用带宽,如果这时候刚好要给对方发送数据,那就将这个确认回复和要发送的数据合在一起进行发送,毕竟确认回复只是一个头部信息,而这样可以提高传输效率。 接收数据以后如果立刻返回确认应答,就无法实现捎带应答。而是将所接收的数据传给应用处理生成返回数据以后进再进行发送请求为止,必须一直等待确认应答的发送。也就是说,如果没有启用延迟确认应答就无法实现捎带应答。延迟确认应答是能够提高网络利用率从而降低计算机处理负荷的一种较优的处理机制。 -
快速重传:如果发送的一个数据包丢了,发送方如果总是要等到超时才能重传,效率就较低。这时候采用快速重传机制效率就能得到提高。 接收方接收数据的时候,接收到的数据不是从预期起始开始的,则认为前边的数据可能丢失了,则这时候回复前边预期起始序号的确认应答作为重传请求,并且间隔连续发送三次。 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送。 之后接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中。
总结:
- 安全有序传输:面向连接,确认应答,超时重传,序号+确认序号,校验和。
- 避免额外丢包:滑动窗口机制,拥塞机制。
- 性能挽回:延迟发送,延迟应答,捎带应答,快速重传
3.面向字节流
TCP协议是可靠,有序,安全,双向,基于链接的以字节为单位的传输。不管是发送还是交付,都以字节为单位,不想UDP那样只能以数据报的格式进行整条传输整条交付。如果发送了1000个字节,接收端可以一次性全部接受,也可以分成多次进行接收。
字节流传输比较灵活,数据可以在缓冲区堆积,想要多少给多少,但是这样虽然灵活,但是又存在其他缺陷: tcp粘包–有可能将多条数据当做一条数据进行处理。
粘包问题解决方案:
-
以特殊字符作为数据头或者数据尾,间隔多条数据(比如http的做法,头部以\r八n\r\n作为结尾),缺陷是如果数据本身就有特殊字符,则需要做转义处理。 -
固定数据长度,设置足够长度,数据不够则补位。缺陷是有补位则性能低,因为传输的数据多了,如果有超长数据比较麻烦。 -
TLV格式数据,每个数据有个固定长度的应用层头部,头部中定义了数据长度(udp、http的做法),先取出头部,然后根据头部中的数据长度,取出剩余的数据。 注意:UDP根本不会产生粘包问题,本身就有边界管理,进行整条交付。 \r\n作为结尾),缺陷是如果数据本身就有特殊字符,则需要做转义处理。 -
固定数据长度,设置足够长度,数据不够则补位。缺陷是有补位则性能低,因为传输的数据多了,如果有超长数据比较麻烦。 -
TLV格式数据,每个数据有个固定长度的应用层头部,头部中定义了数据长度(udp、http的做法),先取出头部,然后根据头部中的数据长度,取出剩余的数据。 注意:UDP根本不会产生粘包问题,本身就有边界管理,进行整条交付。
|