TCP与UDP的区别
区别:
- UDP是无连接的,而TCP是面向连接的,传数据前要先建立连接。
- UDP可以一对多,多对多通讯,而TCP只能一对一。
- UDP使用最大努力交付,即不保证可靠交付。而TCP是可靠交付数据,并且有拥塞控制和流量控制机制。
- UDP是面向报文的,适合一次性传输少量数据。而TCP是面向字节流的
总的来说UDP不太靠谱,自管把数据发出去,丢了也不管。就好像两个人说话,其实一个人只管自己说自己的。
相比之下,TCP就靠谱多了,它通过序列号和确认应答机制保证连接的可靠性,任何一方向对方发送消息(SYN),都要收到回应(ACK)。相当于两个人正常聊天,接下来的谈话内容会根据对方的反应做出调整,比如对方开小差没有听到你刚才讲什么,就重复一遍刚刚说的话。
上图写的只是相对序列号(相对于初始序列号,序列号如果一直是一个固定值,会增加被攻击的风险),初始序列号(ISN)的确定是在连接建立也就是三次握手阶段。
三次握手
同步序列号
握手的首要目的是告知对方自己的初始序列号,只有同步了序列号才能实现可靠传输,比如拥塞控制,消息重发等等。
虽然两次握手就能同步双方的序列号,但无法保证客户端是否接收到了服务端发过去的初始序列号,所以我们需要进行第三次握手来确认。
双方都需要确认一次自己发过去的序列号对方有没有收到,三次握手之后双方就都能明确自己和对方的收发能力是正常的。
实际上任意次的握手都不可靠,三次是双方互相明确对方收发能力的最低值。这 一点可以看看两军问题
第一次握手:客户端在给服务端数据通信之前,会发送连接请求并等待确认应答,此时TCP首部SYN为1,seq为随机生成的序列号(X)。
第二次握手:服务器接受到连接请求之后,会进行答复这时SYN仍为1,确认应答号ack为X+1,表示你发过来的请求我接收到了。同时服务器也会生成一个随机序列号y写入seq,等待客户端的确认应答。
第三次握手:前两次都是通过TCP首部发送SYN包作为建立连接的请求,并未携带数据。三次握手的最后一次握手可以在报文段负载中携带数据,并且因为连接已经建立,所以在之后的报文中SYN都为0(包括最后一次握手),双方正式开始通信。
前两次握手都不能携带数据,并且需要消耗掉一个序列号,也就是seq要加一。
第三次握手可以携带数据,如果不携带数据则不消耗序列号。也就是说三次握手结束后,客户端发送的第一个报文中的seq仍然为x+1
实际上,本来是四次握手的
Alice ---> Bob SYNchronize with my Initial Sequence Number of X
Alice <--- Bob I received your syn, I ACKnowledge that I am ready for [X+1]
Alice <--- Bob SYNchronize with my Initial Sequence Number of Y
Alice ---> Bob I received your syn, I ACKnowledge that I am ready for [Y+1]因为二三步的接收对象相同,所以可以合为一步
认识TCP首部
突然出现SYN,seq这些字母你可能感觉晕乎乎的。特别是ACK和ack,更是让人傻傻分不清楚。现在就来认识一下他们吧。
图中的序列号就是seq(发送数据的顺序编号),它的值是本报文段所发送数据的第一个字节的序号。比如这次发送的数据包是由文件的第1-100个字节,那么seq的值为1.
小写ack对应确认应答号,表示期望收到对方下一个报文段数据的第一个字节的序号。比如已经收到1-100个字节数据后,那么确认应答号就应为101,这样发送端下次就会发送101字节及其之后的数据过来。
需要注意的是只有控制位ACK为1时,确认应答号ack才有效。
防止历史连接扰乱通讯
如果你有过打游戏突然掉线或者异常卡顿的经历,我相信你对网络通讯并不像“如意如意,随我心意”那么顺利有着深刻的认识
假设我们对服务器发送了连接请求,但因为物理链路选择的问题,迟迟没有到达。超过了等待时间之后,重发了一次请求,这一次很快就顺利建立起连接了。但没过多久之前那个请求报文风尘仆仆的来到服务器这,也建立起了连接。
不过还好初始序列号生成的算法比较巧妙:
ISN=M+F(本地IP,本地端口,目的IP,目的端口)
M是过四毫秒就加一的计时器,F是根据上面四个参数进行映射的哈希函数
这样就能保证客户端能过凭借序列号判断这是否是一个过期连接(通过跟记住的最后接收到的seq进行比对)。
如果是,则发送RST报文给服务端,终止这个过期连接。不会让服务端还傻傻的等着客户端发数据,避免了资源的浪费。
从这里也可以看出,序列号seq是多么重要。因为TCP没有时间戳,所以通过同步双方的序列号来“校准双方的时间”就显得尤为重要。
TCP的可靠连接是通过序列号和确认应答来实现的
SYN洪水攻击
SYN洪水攻击(SYN flood attack)利用了TCP协议 3次握手的原理,攻击者发送大量建立连接的网络包,但却不实际建立连接(不进行第三次握手),导致服务器的连接资源耗尽。
这种攻击因为SYN cookie技术的产生,在现代网络已经不奏效了。
SYN cookie的具体做法是:
服务器只在握手完成之后分配资源,在这之前,它会根据特殊的算法生成一个cookie(seq序列号)发给对方。服务器本身并不记忆该cookie。
因为该cookie的生成与对方的IP和端口号有关,所以服务器可以凭此重新算出cookie,并与发过来的报文段中的ack进行比对。
如果对方是合法的连接,服务器就生成一个具有套接字的全开连接。如果不合法,那咱也不亏,服务器没有为它分配过任何资源。
一般SYN cookie只在半连接队列爆满的情况下才会启用。除此之外,常见的防范方式还有SYN Proxy防火墙,Syn Cache技术等。
多亏了序列号和确认应答
滑动窗口
如果传输比较大的数据,一般会将其分为小包,进行分段传输。如果每发送一个数据包都要等待对方确认之后再发下一个,这段时间内什么都不做,看起来太浪费了。
TCP采用滑动窗口来避免资源的浪费:在等待ACK号的这段时间,继续发送后面的数据包。如果收到了ACK号,那么就将该ACK之前的数据包从缓存中清除。
不过这样做有些问题,因为对方可能无法在短时间内处理大量的数据包,导致后来的都被丢弃(缓冲区溢出)。所以在连接的时候,双方应该要明确发送和接收能力。
在TCP首部的可选字段中,双方会协商好能够接收的最大数据量(一般称为窗口大小),它的值一般与接收方缓冲区大小相等。
连接的两端都有各自的接收缓存和发送缓存,无论哪方都能同时收发信息,因此TCP提供的是全双工服务。
拥塞控制
拥塞控制的目的是防止过多数据传输,使得传输链路过载,甚至造成瘫痪。在网络畅通的时候,我们就多发点,猛踩油门;网络差的时候,就别一个劲的发送大量报文添堵了。
TCP采取慢启动、拥塞避免、快速重传、快速恢复来进行拥塞控制。
- 慢启动:
刚建立起的连接,拥塞窗口大小从默认初始值开始,大胆进行试探性指数增长。 - 拥塞避免:
当达到慢启动阈值(ssthresh)时,再进行指数增长显得有点鲁莽了。这时候我们改变策略,采取加法的形式谨慎增加cwnd - 快速重传:
当发现收到四个相同的ACK时(检测到三个冗余ACK),进行快速重传。即立马发送这个丢失的报文段,无需等待超时重传定时器。 - 快速恢复:
发生快速重传之后就会进入快速恢复阶段,此时ssthresh=cwnd/2,cwnd=cwnd/2,之后迁移到慢启动阶段。
四次挥手
断开连接的步骤居然要比建立连接还多,这是怎么回事?难道不能像握手那样:
- 客户端告诉服务器我要断开连接了
- 服务端回复好的,并且告诉客户端我也准备断开了
- 客户端收到服务端的确认之后,再给服务端发送一个确认消息
这样三步不就解决了吗?
断开连接与建立连接还是有些不同的,因为建立连接之前,双方是没有任何通讯的。但在结束连接的时候,双方可能还存在着没有传输完成的数据包。
所谓上山容易下山难,多加一次挥手是为了防止出现误操作。
如果是三次挥手,这边客户端要求服务器重发的请求刚出去,就收到对面说断开连接的消息,实在是让人错手不及
所以我们得分四步进行。
并且连接关闭之后,并不会马上删除相关数据。这是为了防止乌龙事件的产生。
如果因为网络原因,最后一步ACK丢了。服务器迟迟未收到客户端对其断开连接的确认,重发了FIN。
但好巧不巧,客户端的新连接就建立在这个之前断开的端口号上。得,执行断开操作,全乱套了。