TCP和UDP是传输层的两个重要协议,也是面试中经常会被问到的,属于面试高频点。今天,我们来学习这两个协议。
1.区别
1.1 概括
TCP:有连接,可靠传输,面向字节流,全双工
UDP:无连接,不可靠,面向数据报,全双工
1.2 详解
1.TCP是有连接的,UDP是无连接的
连接是抽象的概念,本质上是建立连接的双方,各自保存对方的信息
两台计算机建立连接,就是彼此保存了对方的关键信息
TCP想要通信,就需要建立连接,做完之后,才能后续通信。至于连接如何建立,不需要代码干预,是系统内核自动负责完成的。
对于应用程序来说,客户端这边,主要是要发起“建立连接”动作;服务器这边,主要是把建立好的连接从内核中拿到应用程序里~
如果有客户端,和服务器建立连接,这个时候服务器的应用程序是不需要做出任何操作(也没有任何感知的),内核直接就完成了,建立连接的过程(三次握手),完成流程之后,就会在内核的队列中(这个队列是每个serverSocket都有一个这样的队列)排队。
应用程序要想和这个客户端进行通信,就需要通过一个accept方法,把内核队列已经建立好的连接对象,拿到应用程序中。
即一个典型的生产者-消费者模型。
UDP想要通信,直接发送数据即可,不需要征得对方同意,UDP自身也不会保存对方的信息
2.TCP是可靠传输,UDP是不可靠传输的
网络上进行通信,A-> B发送一个消息,这个消息是不可能做到100%送到的
TCP就内置了可靠传输机制,UDP就没有内置可靠传输。
A -> B 发消息,消息是不是到达B这一方,A自己能感知到,进一步的,就可以在发送失败的时候采取一定的措施(尝试重传之类的)
可靠传输需要付出代价:a.机制更复杂 b.传输效率会降低
3.TCP是面向字节流的,UDP是面向数据报的
TCP和文件一样,以字节位单位来进行传输
UDP则是按照数据报为单位,来进行传输的(UDP数据报是有严格的格式的)
4.TCP和UDP都是全双工的
一个信道,允许双向通信,就是全双工
一个信道,允许单向通信,就是半双工
2.TCP
2.1 内容
2.2 可靠传输
常见面试题:
TCP是如何保证可靠传输的?
通过确认应答为核心,借助其他机制辅助,最终完成可靠传输
2.2.1 确认应答
发送方,把数据发给接收方之后,接收方收到数据就会给发送方返回一个应答报文
发送方,如果收到这个应答报文了,就知道自己的数据是否发送成功
2.2.2 超时重传
如果网络传输过程中,出现丢包了,发送包,势必就无法收到ACK了
由于丢包是一个“随机”的事件,因此在TCP传输过程中,丢包就存在两种情况:
1.传输的数据丢了
2.返回的ACK丢了
无论出现上述两种情况,发送方都会采取统一的措施,就是“重传”
发送方,何时重传?等待时间
发送方,发出数据之后,会等待一段时间,如果这个时间之内,ack来了,此时就自然视为数据到达
如果达到这个时间之后,数据还没到,就会触发重传机制~
超过等待时间,再重传~
第二次重传后的等待时间会比第一次时间延长,但延长也不是无限制延长,重传若干次后,时间拉长到一定程度,认为数据再重传也没有用了,就放弃tcp连接(准确来说就是会触发tcp的重置连接操作)
TCP会有一个“接收缓冲区”就是一个内存空间,会保存当前已经收到的数据,以及数据的序号
接收方如果发现,当前发送方发来的数据,是已经再接收缓冲区中存在的(收到过的重复数据了),接收方就会直接把这个后来的数据给丢弃掉,确保应用程序进行read的时候,读到的是只有一个数据
2.2.3 连接管理
建立连接 + 断开连接
面试中最经典的问题:三次握手 + 四次挥手
建立连接
三次握手
tcp这里的握手,也就是给对方传输一个简短的,没有业务数据的数据包,通过这个数据包,来唤起对方的注意,从而触发后续的操作(握手这个操作,不是TCP独有的,甚至不是网络通信独有的,计算机中很多的操作,都会涉及到握手)
TCP的三次握手,TCP在建立连接的过程中,需要通信双方进行“打三次招呼”才能够完成连接建立的
问:三次握手是要解决什么问题?
答:三次握手核心作用一:
投石问路,确认当前网络是否是畅通的
三次握手核心作用二:
能够发送方和接受党都能确认自己的接收能力和发送能力均正常
三次握手核心作用三:
让通信双方,在通信过程中,针对一些重要的参数,进行协商
问:四次握手可以吗?两次握手可以吗?
答:四次:可以,但没必要
两次:不可以,不能达到双方都知道信息的过程
断开连接
四次挥手
为什么中间两次不能像建立连接一样合并为一步?
不一定
不能合并的原因:ACK和第二个FIN的触发时机是不同的
ACK是内核响应的,B收到FIN,就会立即返回ACK
第二个FIN是应用程序的代码触发,B这边调用了close方法,才会触发FIN
是否意味着,如果这这边代码close没写/没执行到,是不是第二个FIN就一直发不出去?(有可能的)
如果是正常的四次挥手,正常的流程断开的连接
如果是不正常的挥手(没有挥完四次),异常的流程断开连接(也是存在的)
三次握手ACK和第二个syn都是内核触发的,同一个时机,可以合并
可以合并的情况:TCP还有一个机制,延迟应答,能够拖延ACK的回应时间,一旦ACK滞后了,就有机会和下一个FIN一起合并
哪一方,主动断开连接,哪一方就会进入TIME_WAIT
TIME_WAIT状态的主要存在的意义,就是为了防止最后一个ACK丢失,留下的后手
如果最后一个ACK丢了,站在B的角度,B就会触发超时重传,重新把刚才的FIN给传一遍
如果刚才A没有TIME_WAIT状态,就意味着A这个时候就已经真的释放连接了,此时重传的FIN也就没人能处理,没人能返回ACK了,B也就永远收不到ACK了
A这边使用TIME_WAIT状态进行等待,等待的这个时间,就会为了处理后续B重传的FIN
此时如果有重传的FIN来了,就可以继续返回ACK了,B这边的重传才有意义
TIME_WAIT等待多久呢?
假设网络上两个节点通信消耗的最大等待时间为MSL,此时的TIME_WAIT的时间就是2MSL (已经是上限了,绝大部分的数据包不会达)
2.2.4 滑动窗口
提高效率
TCP的可靠传输,是会影响传输的效率的
滑动窗口,就让可靠传输性能的影响,更小一些
TCP只要引入了可靠传输,传输效率是不可能超过没有可靠性的UDP的
TCP这里的“效率机制”都是为了让影响更小,缩小和UDP的差距
批量传输数据,不等ack回来,直接再发下一个数据
批量传输,也不是“无限的”传输
批量传输也是存在一定的上限的,达到上限之后,再统一等待ack
不等待的情况,批量最所发多少数据,这个数据量,称为“窗口大小”
当前A->B是批量的发了四份数据
此时B也要给A回应四组ACK,此时A已经达到窗口大小,再收到ACK之前,不能继续往下发了
需要等待有ACK回来了之后,才能继续往下发。
可以不需要一次等待四个ACK全部回来之后才能继续发,而是回来一个ack,就立即继续发一个
窗口越大,等待的ack越多,此时传输效率也就越高
情况一:ack丢了
这种情况,不需要任何重传,没事了
确认信号,表示的含义是,当前序号之前的数据,已经确认收到了
下一个你应该从确认序号这里,继续发送
例如:如果1001这个ACK丢了,但是2001ACK到了,则证明2001之前的数据都已经确认传输成功了,涵盖了1001的情况
情况二:数据包丢了
主机A就需要知道是哪个数据丢了,主机B也就得告诉A是哪个数据丢了。
主机A看到了B这边连续的几个ack,都是再索要1001,A就知道了,1001这个数据就是丢了,就重传了1001
1001-2000重传之后,顺利到达B索要的就是7001
上述的重传过程,并没有额外的冗余操作,哪个数据丢了,就重传哪个,没丢的数据就不需要重传,整个过程都是比较快速的
如果通信双方,传输数据的量比较小,也不频繁,就仍然是普通的确认应答和超时重传。
如果通信双方,传输数据量更大,也比较频繁,就会进入到滑动窗口模式,按照快速重传的方式处理。
通过滑动窗口的方式传输数据,效率是会提升的
窗口越大,传输效率就越大(一份时间,等待的ack更多了,总的等待时间更少了)
当然,滑动窗口也不是设置越大越好
如果传输的速度太快,就可能会使接收方,处理不过来了,此时,接收方也会出现丢包,发送方还得重传。
2.2.5 流量控制
站在接收方的角度,反向制约发送方的传输效率
考虑接收方的处理能力
发送方发送的速率,不应该超过接收方的处理能力
可以根据缓冲区剩余空间大小来判断消费者处理速度
把剩余缓冲区返回给发送方,发送方根据改缓冲区大小发送数据
窗口探测包:并不携带具体的业务数据,只是为了触发ack,为了查询当前接收方这边的接收缓冲区剩余空间。
2.2.6 拥塞控制
不仅仅是接收方,还有整个通信的路径
关键问题:接收方的处理能力,很方便进行量化
但是中间节点,结构更复杂,更难以直接的进行量化,因此可以通过“实验”的方式,来找到一个合适的值
让A先按照比较低的速度(小的窗口)来发送数据
如果数据传输的过程非常顺利,没有丢包,再尝试使用更大的窗口,更高的速度进行发送(一点一点变化)
随着窗口大小不停的增大,达到一定的程度,可能中间节点就会出现问题了,此时这个节点就可能出现丢包
发送方发现丢包了,就把窗口大小调整小,此时如果还是继续丢包,就继续缩小,如果不丢包了,就继续尝试变大
在这个过程中,发送发不断调整窗口大小,逐渐达到“动态平衡”
最终时机发送的窗口大小,是取流量控制和拥塞控制中的窗口的较小值
2.2.7 延时应答
A把数据传给B,B就立即返回ack给A(正常)
也有时候,A传输给B,此时B等一会再返回给ack给A(延时应答)
本质上也是为了提升传输效率
延时返回ack,给接收方更多的时间,来读取接收缓冲区的数据
此时接收方读了这个数据之后,缓冲区剩余空间,变大了,返回的窗口大小也就更大了
2.2.8 捎带应答
在延时应答的基础上,进一步的提升效率
网络通信中,往往是这种“一问一答”这样的通信模型
ack也是内核立即返回的,response则是应用程序代码来返回的,这两者的时机是不同的
由于tcp引入了延时应答,上面的ack不一定是立即返回,可能要等一会
在等一会的过程中,B就刚好把response给计算好了,计算好了之后,就会把response返回,与此同时顺便就把刚才要返回的ack也带上了,两个数据就合并成了一个数据,此时就可以得到更高效的效果
2.2.9 面向字节流
这里有一个最重要的问题,粘包问题(不是tcp独有的,而是面向字节流的机制都有类似的情况)
此处“包”应用层数据包,如果同时有多个应用层数据包被传输过去,此时就容易出现粘包问题
目前,接收缓冲区中,这三个应用层数据包的数据,就是以字节的形式紧紧挨在一起的
接收方的应用程序,读取数据的时候,可以一次读一个字节,也可以读两个字节也可以读N个字节……
但是最终的目标是为了得到完整的应用层数据包,B应用程序,就不知道,缓冲区里的数据,从哪里到哪里是一个完整的应用数据包了
相比之下,像UDP这样的面向数据报的通信方式,就没有上述问题
UDP的接收缓冲区中,相当于是一个一个的DatagramPacket对象,应用程序读的时候,就明确知道哪里到哪里是一个完整的数据。
如何解决粘包问题?
核心思想:通过定义好应用层协议,明确应用层数据包之间的边界
1.引入分隔符
例如,可以使用\n作为分隔符
aaa\n
bbb\n
ccc\n
2.引入长度
3aaa
4bbbb
3ccc
自定义应用层协议的格式
xml,json,protobuffer,本身都是明确了包的边界的
2.2.10 异常情况的处理
如果在tcp的使用中出现了意外,会如何处理?
1.进程崩溃
(本质上是进程没了,异常终止了。文件描述符表,也就释放了,相当于调用socket.close(),此时就会触发FIN,对方收到之后,自然也就返回FIN和ACK,这边再进行ACK,即正常的四次挥手断开连接的流程)
TCP的连接,可以独立于进程存在(进程没了,TCP连接不一定没)
2.主机关机(正常关机)
进行关机的时候,就是会触发强制终止进程的操作,相当于1,此时就会触发FIN,对方收到之后就会返回FIN和ACK,此时不仅仅是进程没了,整个系统也可能关闭了,如果在系统关闭之前,对端返回的ACK和FIN到了,此时系统还是可以返回ACK,进行正常的四次挥手。但如果系统已经关闭了,ACK和FIN迟到了,无法进行后续ACK的响应,站在对端的角度,对端以为是自己的FIN丢包了,重传FIN,重传几次都没有响应,自然就会放弃连接(把持有的对端的信息就删了)。
3.主机掉电(非正常)
此时,就是一瞬间的事情,来不及杀进程,也来不及发送FIN,主机就直接停机了,站在对端的角度,对端不一定知道这个事情
如果对端是在发送数据(接收方掉电),发送的数据就一直会等待ACK ,触发超时重传,触发TCP连接重置功能,发起“复位报文段”,如果复位报文段发过去之后,也没有效果,此时就会释放连接了
RST复位报文段
如果对端是在接收数据(发送方掉电),对端还在等待数据到达…..等了半天没消息,此时其实是无法区分,是对端没发消息,还是对方挂了
TCP中提供了心跳包机制(形象的比喻),即接收方也会周期性的给发送方发送一个特殊的,不携带业务数据的数据包,并且期望对方返回一个应答,如果对方没有应答,并且重复了多次之后,仍然没有,就视为对方挂了,此时就可以单方面释放连接了。
4.网线断开
和主机掉电非常类似
当前A给B发送数据,一旦网线断开
A就相当于会触发超时重传 -> 连接重置 ->单方面释放连接
B就会触发心跳包 -> 发现对端没响应 -> 单方面释放连接
3.对比
TCP和UDP之间的对比
TCP优势在于可靠传输,更适用绝大部分场景
UDP优势在于高效率,更适用于“可靠性不敏感,性能敏感”场景
局域网内部(同一个机房)的主机之间通信
如果要传输比较大的数据包,TCP更优先(UDP有64kb的限制)
如果要进行“广播传输”,优先考虑UDP,UDP天然支持广播,TCP不支持(应用程序额外写代码实现)
有一种特殊的场景,需要把数据发给局域网的所有机器,这个情况就是广播