UDP与TCP协议
Posted 且随疾风前行->
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了UDP与TCP协议相关的知识,希望对你有一定的参考价值。
目录
UDP和TCP协议都是传输层协议,他们却大不相同。
UDP协议
协议报头
前四字节:16位源端口号+16位目的端口
次四字节:16位UDP长度+16位UDP校验和
16位源端口号+16位目的端口:表示报文的来源和报文要到哪去
16位UDP长度:表示整个数据报(UDP首部+UDP数据)的最大长度,因为报头长度就是8字节,所以至少是8。
16位UDP校验和:用来校验数据,发送端填写,接收端进行校验,如果校验和出错,就会直接丢弃。 数据 :即有效载荷
UDP协议特点:
无连接:知道对端的IP和端口号就直接发送数据,不用建立连接。
不可靠:即不能保证报文完整按序到达对端,因为没有确认和重传机制。如果网络故障,udp协议层也不会给应用层返回任何错误信息。倘若要实现可靠传输,需要结合场景从TCP中选取策略,在应用层实现策略。
面向数据报:不能灵活控制读写数据都次数和数量,发多少读多少,udp不会拆分或合并报文socket支持全双工:能在socket中同时读写,即发送和接收独立,虽然UDP无发送缓冲区,调用sendto直接交给内核,内核发送到对端,但是UDP具有接收缓冲区,但不能保证收到udp报的顺序和发送udp报的顺序一致 ,而且缓冲区满了再到达的数据被丢弃。
最大传输长度:因为16位UDP报文长度字段,所以最大传输长度=2^16=64K,如果需要传输更多数据,需要在应用层手动分包多次发送,在接收端手动拼装
应用场景:
应用对高速传输和实时性要求较高的通信领域
基于UDP的应用层协议:
NFS: 网络文件系统
TFTP: 简单文件传输协议 DHCP: 动态主机配置协议 BOOTP: 启动协议 ( 用于无盘设备启动 ) DNS: 域名解析协议 我们程序员写 UDP 程序时自定义的应用层协议
TCP
TCP协议报头
16位源端口+16位目的端口:表示报文从哪里来要到哪里去
32位序号:TCP为了保证报文按序到达等,将每个字节都进行了编号
32位确认序号:用于确认序号之前的报文已经都发了
为什么有两组序号?TCP是全双工的,报文通常具有应答(确认序号)和发消息( 序号)
4位首部长度(单位是4字节,故至少是5):表示TCP报头总长度,至少为20字节
6个标记位:
SYN:请求建立链接的报文就标记为 1,同步报文段
FIN : 请求断开链接的报文就标记为 1,结束报文段
ACK:确认标记位,表示对历史报文的确认 ,大部分情况是1
PSH:数据推送标记位,提示接收端应用程序立刻从TCP缓冲区把数据读走
URG:紧急指针标记位,表示数据需要被紧急处理,需要忽略序号的处理顺序
RST :对方要求重新建立连接,复位报文段
16位窗口大小:表示TCP接收缓冲区所剩余的空间,提示对端可以发送的数据长度最大值
为什么需要URG?
报文发送可能乱序到达,这是不可靠传输的一种情况,TCP将收到的报文按序号排序以让报文按序到达,但是有的报文优先级更高,但序号更靠后 ,就无法做到数据被紧急处理,为了表示该数据可以忽略序号,可以被优先读取,就标记该标记位。
16位校验和 +16位紧急指针
校验和:验证数据在网络传输中是否出问题
紧急指针:以偏移量的方式在数据中找到紧急数据,紧急数据主要用来获取服务器状态
确认应答机制
理解可靠性
什么是不可靠:数据包丢包、数据包乱序到达、校验失败
如何确认报文丢包了?只要得到应答就意味对方收到了 。
如何理解确认应答机制?
TCP将每个字节的数据都进行了编号,即为序列号,整个报文首字节的编号就是32位序号。发送方发送报文给接收方,接收方再发送携带ACK的报文,每一个ACK都带有对应的确认序列号,意思是确认序列号以前的报文都收到了,也就是确认应答了。下次发送方发送的时候,则报文的32位序号就从确认序号开始。倘若之前的报文没有被确认应答,则会实行快重传或者超时重传机制来保证TCP的可靠性。
超时重传机制
超时重传顾名思义就是超过一段时间没有收到报文的确认应答消息,就认为之前的报文丢失了,于是就进行重新发送报文。因为要发送方收到接收方发送的报文确认之前的报文都收到了,所以有之前发送的报文丢失了或接收方发送的确认接收的报文丢失了两种情况:
显然在第二种情况下,接收端就会收到重复的报文,因为报头中有32位的序号字段,所以可以根据序号进行去重,将重复的报文丢弃掉。
如何确定超时的时间?
这个时间太短就会导致发送重复的报文,如果时间太长又会导致发送效率低下,显然这个时间的长度收到网络环境的影响。最理想的情况下是找到一个最小的时间,保证确认应答在这个时间内一定能收到,为了保证在各种情况下都能具有高性能通信,TCP将会动态计算这个时间。Linux中这个时间是以500ms为单位,超时时间是500ms的整数倍。倘若重发一次还得不到应答,则超时时间变为2*500ms,倘若仍然得不到应答,超时时间变为4*500ms进行重传,以此类推按指数递增一定次数后,TCP认为网络或对端主机出现异常,强制关闭连接。
连接管理机制
在正常情况下 , TCP 要经过三次握手建立连接 , 四次挥手断开连接三次握手:
起初客户端处于CLOSED状态,服务端处于LISTEN状态,客户端开始发送请求连接服务端的SYN报文,然后进入SYN_SEND状态,服务端接收到SYN报文后,向客户端发送SYN+ACK报文,然后进入SYN_RCVD状态,客户端收到SYN+ACK报文后向服务端发送ACK报文并进入ESTABLISHED状态,服务端收到ACK报文后进入ESTABLISHED状态,至此三次握手过程完成。
注意:当服务器进入listen状态的时候,上层不调用accept接口也会建立链接,这个链接将维护在内核的队列中,这个对称的长度与listen第二个参数backlog=n有关,全连接队列长度=参数n+1,表示在不accept的情况下最多维护链接数,这说明底层建立好的链接是有上限的,因为队列太长影响客户体验,过于占用系统资源,反而可能导致服务器效率低下,这个全链接队列维护的意义在于可以让服务器在有闲置的情况下,从底层获取链接,进行链接处理,是一种池化技术。
如图:
为什么是三次握手?不是两次或者一次?
因为建立链接是需要花时间和空间,也就是需要消耗服务端和客户端的系统资源的。倘若一次握手就建立链接,那么如果有不法分子发送大量SYN请求,也就是SYN泛洪攻击,那么服务器资源很快就被榨干了,如果是两次的话,那也是服务端先进入ESTABLISED也状态就是收到SYN报文后就进入连接状态了,那么和一次握手的道理也是一样的,容易被SYN泛洪攻击导致服务器崩溃。倘若采用3次,就可以让客户端先建立连接,而服务器后建立链接,后建立数据结构,以最小成本的建立,这样子相当于可以让SYN泛洪攻击的成本提高了,相对更安全了。
四次挥手:
如图:
起初服务端与客户端都已经建立连接,要求主动断开连接的一方可能是客户端也可能是服务端,以客户端为例,客户端主动断开连接,向服务端发送FIN报文并进入FIN_WAIT_1状态,服务端收到FIN报文后向客户端发送ACK报文,服务端进入CLOSE_WAIT状态,客户端收到ACK报文后进入FIN_WAIT_2状态,服务端向客户端发送FIN报文并进入LAST_ACK状态,客户端收到FIN报文后向服务端发送ACK报文并进入TIME_WAIT状态,服务端收到ACK报文后进入CLOSED状态,因为最后一个ACK报文是没有应答的所以报文可能丢失,倘若经过两个MSL(maximum segment lifetime)客户端没有回复FIN报文,则客户端认为服务端已经收到了ACK报文,不用超时重传了,然后客户端进入CLOSED状态,至此四次挥手完成。
因为中间两次报文都是由服务端往客户端,一次性发送效率更高,将FIN+ACK报文发送给客户端也是可行的,于是就变成了三次挥手,这是一种特殊情况。
注意:当被动关闭方进入CLOSED_WAIT状态后,这个链接是一种半关闭状态,是占有链接资源的,当被动关闭方上层未调用close函数关闭套接字,就会占用着系统资源。
当我们主动关闭服务器程序的时候,服务器会自动进行四次挥手并且进入TIME_WAIT状态,此时我们无法立即进行重启服务器,需等待其进入CLOSED状态,因为重启服务器会导致bind失败,倘若非得这么干,可以使用setsockopt接口,第三个参数使用SO_REUSEADDR,表示允许创建端口号相同但IP地址不同的多个socket描述符,这样就可以绑定成功了。
TCP是面向连接的:在进行通信之前,服务器和客户端要建立连接,建立连接的本质是OS要在底层维护相应的数据结构来管理连接,而断开连接的本质则是释放底层维护的相应的数据结构,所以对连接的管理具有时间和空间的成本。
滑动窗口
上面提到的应答机制,TCP报文是一收一发的,但是这样一收一发效率很低,倘若一次发送多个报文就能大大提高效率(本质是将多个报文的等待时间重叠在一起):
但是一次多发存在可靠性问题,因为其中报文可能丢包,所以发送出去的数据在没有得到应答的情况下,必须被保留,以便支持超时重传。报文在发送端的发送缓冲区中保存,发送缓冲区由已经发送并且确认应答的报文的区域,已经发送但未得到响应结果的区域,待发送区域组成:
这个中间的区域被称为滑动窗口,比如发送前4个报文不用等待ACK,直接发送,在收到第一个ACK之后滑动窗口右移,继续发送第五个报文,窗口的大小就是无需等待可以继续发送的数据的最大值,这个大小与对方接收缓冲区剩余空间大小有关,同时还与网络状况有关,窗口越大,则网络的吞吐率就越高。
如何理解缓冲区和滑动窗口?
窗口本质就是内核用数据结构数组和两个头尾指针来进行描述的,滑动的本质就是指针右移,因为缓冲区大小有限,这个数组是环形数组,滑动窗口的大小是动态变化的。其中头指针以收到的最大的ACK序号右移,尾指针则=头指针+窗口大小。
倘若出现丢包,怎么办?
数据包到达了但是ACK报文丢了,可以通过后面的ACK报文确认:
倘若数据包丢了,当某一段报文丢了,发送端会收到重复的ACK,提醒发送端报文丢失,当连续三次以上收到同样的报文后,发送端就将相应的数据重新发送,这个机制就是高速重发控制(也称快重传)。
流量控制
倘若发送端发送数据太快,接收端的缓冲区被打满了,这时再发送数据就会出现丢包等问题,为了解决这个问题,TCP报头有衡量自己接收缓冲区剩余空间大小的字段,也就是窗口大小 (自己的)。所以发送端就能根据接收端的窗口大小来决定发送端的发送速度,这个机制就是流量控制。
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过ACK报文通知发送端。 窗口大小字段越大表示网络的吞吐量越高。 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端。 发送端接受到这个窗口之后,就会减慢自己的发送速度。 如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端
当然这个流量控制是双向的,因为发送端和接收端在互相传输数据,同时互相告知对方自己的窗口大小。
拥塞控制
当少量丢包的时候,TCP会触发超时重传机制,而倘若出现大量丢包,则就应考虑网络的问题,这时候的策略就不是立即重传数据包了,因为会加重网络的负担,不能马上超时重传,TCP引入慢启动机制:先发少量数据,摸清当前网络拥塞状态,再决定以多大的速度发送数据。
引入拥塞窗口:
初始时拥塞窗口大小为1,而后拥塞窗口大小为2,4,8...指数增长,直到增长到慢启动阈值,然后进行线性增长:
第一次启动的时候,TCP的慢启动阈值为窗口最大值,第一次超时重发的时候,慢启动阈值就变为原来的一半,拥塞窗口变为1。这种指数增长的方式,会使数据发送速度前期慢后期快,所以前期都可以发少量数据探测网络状态,而后期发送速度增长速度快可以尽快恢复网络通信。这是为了尽快把网络数据传输给对方又要避免给网络太大压力的折中方案。
前面提到滑动窗口大小就是一次给对方主机发送的数据量,现在考虑到网络状况:
一次向目标主机发送的数据量=min(对方接收能力,拥塞窗口)
也即:滑动窗口大小=min(对方窗口大小,拥塞窗口大小)
延迟应答
倘若接收数据的主机在收到数据的时间就立即返回ACK应答,这时可能返回的窗口可能较小,但是倘若等一会,接收端很快就把数据处理掉了,这时再返回ACK应答,返回的窗口大小就更大,窗口越大则网络的吞吐量越大,传输效率越高,这个策略就是延迟应答。
如何延迟应答?
数量限制:每隔N个包就应答一次。(N常取2)
时间限制:超过最大延时时间就应答一次。(一般延时时间取200ms)
捎带应答
前面提到一收一发效率低,如果在应答ACK的时候同时发送数据就可以提高效率,这个策略就是捎带应答:
面向字节流
TCP创建套接字的时候会在内核中创建发送和接收缓冲区,调用write会现将数据写入发送缓冲区,如果发送的字节数太长报文会被拆分成多个报文,倘若发送的字节数太少就现在缓冲区等待,等到缓冲区长度差不多或其他合适的时机再发送出去。
接收数据的时候,数据从网卡程序到达内核的缓冲区,然后应用程序调用read从接收缓冲区拿数据。因为发送端和接收端有两套缓冲区,所以对于TCP连接,既可以读数据,也可以写数据,这个概念叫做全双工:
因为有缓冲区的存在,所以TCP报文读写时不用匹配,可以一次性调用一次read一次读取100个字节,也可以分100一次调用,每次读取1个字节。写入的时候也同理,这种不用关心数据格式的读写特点就叫做面向字节流。
注意:不关心数据格式,意味着应用层需要处理。
粘包问题
上面提到TCP是面向字节流的,也就是读取的时候可能读到一个半的报文这种情况,这种情况被称为粘包问题,这个包指的是应用层的包,因为对于传输层而言,报文字段中有序号,报文是一个个抵达的,而在应用层则是连续的字节数据。
如何避免粘包问题?
即明确报文和报文的边界,比如http协议中读取到空行认为报头读取完毕。
对于定长的包:直接每次按固定长度读取即可
对于变长的包:在报头定义报文长度的字段或者使用分隔符作为报文之间的边界
注意:UDP不存在此问题,UDP报文字段有报文长度,且报文是一个个交给应用层的,不能交付半个报文给上次,UDP是面向数据报的
TCP的异常情况
1.进程终止时,OS会自动四次挥手,自动释放文件描述符,和正常关闭一样
2.机器重启:同进程终止,本质也是终止进程
3.机器掉电、网线断开:客户端掉线了,断开网络了,自然无法进行四次挥手,而服务端认为还在,再写入时发现连接不在会reset,而且TCP内置了保活计时器,会定期询问对方是否存在
查看网络状态的工具
netstat
选项
n :表示拒绝显示别名,能显示数字的都转化成数字
l :尽列出listen状态的服务
t :仅显示tcp相关
p:显示链接相关的程序名 ,高权限启动的程序需要提高权限才能看到
u:仅显示udp相关
应用场景
应用于需要可靠传输的场景,比如文件传输,重要文件更新等场景
基于 TCP 应用层协议HTTP:超文本传输 HTTPS:超文本传输安全协议 SSH:安全外壳协议 Telnet:远程登录协议 FTP:文件传输协议 SMTP:简单邮件传输协议
TCP总结
为什么 TCP 这么复杂 ? 因为要保证可靠性, 同时又尽可能的提高性能。 如何保证可靠性?1.报文中有校验和字段 2.报文中有序列号字段(可以确保报文按序到达) 3.确认应答,报文中有确认序号和ACK字段表示收到了报文 4.超时重发,倘若丢包一定时间段后会进行超时重传 5.连接管理,建立连接数要进行三次握手,断开连接时要进行四次挥手 6.流量控制,使用滑动窗口根据接收方窗口大小来决定发送方的发送速度 7.拥塞控制,大量丢包认为网络状况出问题,引入拥塞窗口和慢启动机制来解决提高性能:
1.滑动窗口=min(拥塞窗口大小,接收方窗口大小),尽可能一次多发数据 2.快速重传,连续收到3个以上相同确认序号的ACK报文就进行重传 3.延迟应答,接收数据后稍作等待,尽可能让上层获取更多的数据,提高剩余接收窗口大小 4.捎带应答,在发送数据的时候让ACK搭顺风车顺便对以前收到的数据进行确认其他: 定时器
超时重传定时器 保活定时器:维持连接 TIME_WAIT定时器
以上是关于UDP与TCP协议的主要内容,如果未能解决你的问题,请参考以下文章