目录
TCP和UDP是什么?
TCP和UDP有什么区别?
三次握手和四次挥手
解决大量TIME_WAIT问题的核心方法
TCP维护可靠的通信方式
TCP协议的长连接和短连接
长连接的保活机制
Syn泛洪攻击
拥塞控制
滑动窗口的原理
什么是粘包以及粘包的原因
粘包的处理方式
TCP和UDP使用场景
TCP和UDP是什么?
TCP:
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。
UDP:
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768 描述了 UDP。
TCP和UDP有什么区别?
- TCP面向连接,通过三次握手建立连接,四次挥手接除连接;UDP是无连接的,即发送数据之前不需要建立连接,这种方式为UDP带来了高效的传输效率,但也导致无法确保数据的发送成功。
- TCP是可靠的通信方式。通过TCP连接传送的数据,TCP通过超时重传、 数据校验等方式来确保数据无差错,不丢失,不重复,且按序到达;而UDP由于无需连接的原因,将会以最大速度进行传输,但不保证可靠交付,也就是会出现丢失、重复等等问题。
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流,由于连接的问题,当网络出现波动时,连接可能出现响应问题;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低。
- 每一条TCP连接只能是点到点的;而UDP不建立连接,所以可以支持一对一,一对多,多对一和多对多的交互通信,也就是可以同时接受多个人的包。
- TCP需要建立连接,首部开销20字节相比8个字节的UDP显得比较大。
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道。
三次握手和四次挥手
- 第一次握手:客户端给服务器发送一个 SYN 报文。
- 第二次握手:服务器收到 SYN 报文之后,会应答一个 SYN+ACK 报文。
- 三次握手:客户端收到 SYN+ACK 报文之后,会回应一个 ACK 报文。
- 服务器收到 ACK 报文之后,三次握手建立完成。
- 作用是为了确认双方的接收与发送能力是否正常。
- 第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
- 第二次握手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
- 第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
- 第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态
- 服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。
为什么要进行三次握手?
当进行第一次握手,网络不好可能会堵塞,所以连接的请求并没有到达服务器端;
但是tcp连接有超时重传的机制,所以再一次发送请求,这时候服务器端接收到了你的请求,他也会返回一个请求给你,这是第二次握手;
但是这时候网络环境突然又好了起来,那个堵塞的请求到达了服务器端,服务器端又给你回了一个请求,但是你又不想给服务器发送请求,这时候服务器的资源会进行占用等待你的请求,为了不使服务器的资源继续占用,你又必须发送一个请求给服务器;
所以要进行3次握手
为什么不能两次握手能进行连接?
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
为啥只有三次握手才能确认双方的接受与发送能力是否正常,而两次却不可以:
第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后
1、第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(c)。此时客户端处于 SYN_Send 状态。
2、第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN(s),同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
3、第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 establised 状态。
4、服务器收到 ACK 报文之后,也处于 establised 状态,此时,双方以建立起了链接。
Ps:(1)SYN=1 表示该报文不携带数据,但消耗一个序号 seq=x,seq=x是客户端的初始化序列号,因为tcp是面向字节流的
(2)SYN=1 表示该报文不携带数据,但消耗一个序号 seq=y,seq=y是服务器的初始化序列号,ACK=1是一个确认号
ack=x+1,表示服务器下次接收到的序号希望是x+1。然后服务器进入到SYN-RCVD等待的状态
(3)ACK=1是一个确认号,seq=x+1是上一次服务器回应的序号要求,ack=y+1表示客户下一次接收到的序号希望是y+1
三次握手的作用:
- 确认双方的接受能力、发送能力是否正常。
- 指定自己的初始化序列号,为后面的可靠传送做准备。
- 如果是 https 协议的话,三次握手这个过程,还会进行数字证书的验证以及加密密钥的生成到。
(ISN)是固定的吗?
三次握手的一个重要功能是客户端和服务端交换ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。 如果ISN是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
什么是半连接队列?
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
这里在补充一点关于SYN-ACK 重传次数的问题: 服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传,如果重传次数超 过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s, 2s, 4s, 8s, …
三次握手过程中可以携带数据吗?
可以携带数据的。也就是说,第一次、第二次握手不可以携带数据,而第三次握手是可以携带数据的。
假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。也就是说,第一次握手可以放数据的话,其中一个简单的原因就是会让服务器更加容易受到攻击了。
而对于第三次的话,此时客户端已经处于 established 状态,也就是说,对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据页没啥毛病。
四次挥手:
这里特别需要主要的就是TIME_WAIT这个状态了,这个是面试的高频考点,就是要理解,为什么客户端发送 ACK 之后不直接关闭,而是要等一阵子才关闭。这其中的原因就是,要确保服务器是否已经收到了我们的 ACK 报文,如果没有收到的话,服务器会重新发 FIN 报文给客户端,客户端再次收到 ACK 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文。
至于 TIME_WAIT 持续的时间至少是一个报文的来回时间。一般会设置一个计时,如果过了这个计时没有再次收到 FIN 报文,则代表对方成功就是 ACK 报文,此时处于 CLOSED 状态。
为什么建立连接需要三次,释放连接需要四次?
TCP建立连接要进行3次握手,而断开连接要进行4次,这是由于TCP的半关闭造成的,因为TCP连接是全双工的(即数据可在两个方向上同时传递)所以进行关闭时每个方向上都要单独进行关闭,这个单方向的关闭就叫半关闭.关闭的方法是一方完成它的数据传输后,就发送一个FIN来向另一方通告将要终止这个方向的连接.当一端收到一个FIN,它必须通知应用层TCP连接已终止了这个方向的数据传送,发送FIN通常是应用层进行关闭的结果。
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
解决大量TIME_WAIT问题的核心方法
开启TCP时间戳
在Linux系统中,通过开启TCP时间戳功能,可以避免TIME_WAIT状态的累积。可以通过修改/etc/sysctl.conf
文件,并添加如下配置来开启TCP时间戳:
net.ipv4.tcp_timestamps = 1
然后执行sysctl -p
命令使配置生效。
开启TCP连接复用
TCP连接复用指的是在TIME_WAIT状态的连接被关闭后,将其端口立即重新分配给新的连接,而不必等待TIME_WAIT状态结束。这样可以有效减少TIME_WAIT状态的数量。可以通过修改/etc/sysctl.conf文件,并添加如下配置来开启TCP连接复用:
net.ipv4.tcp_tw_reuse = 1
然后执行sysctl -p命令使配置生效。
调整本地端口范围
本地端口范围指的是用于分配本地端口的范围。如果本地端口范围设置得太小,可能会导致端口耗尽和TIME_WAIT状态的增加。可以通过修改/etc/sysctl.conf文件,并添加如下配置来扩大本地端口范围:
net.ipv4.ip_local_port_range = 1024 65535
然后执行sysctl -p命令使配置生效。
需要注意的是,以上方法仅适用于解决普通的TIME_WAIT问题。在某些特殊情况下,开启net.ipv4.tcp_tw_recycle选项可能会导致问题。因此,不建议开启net.ipv4.tcp_tw_recycle选项。
综上所述,通过开启TCP时间戳、开启TCP连接复用以及调整本地端口范围,可以有效解决大量TIME_WAIT问题,提升系统的性能和资源利用率。在进行相关配置时,需谨慎对待net.ipv4.tcp_tw_recycle选项,以避免潜在的问题。
TCP维护可靠的通信方式
- 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
- 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认包;
- 超时重发:发送方在发送分片后计时,若超时却没有收到相应的确认包,将会重发对应的分片;
- 滑动窗口:TCP连接双方的接收缓冲空间大小都固定,接收端只能接受缓冲区能接纳的数据。
- 失序处理:TCP的接收端需要重新排序接收到的数据。
- 重复处理:如果传输的TCP分片出现重复,TCP的接收端需要丢弃重复的数据。
- 数据校验:TCP通过保持它首部和数据的检验和来检测数据在传输过程中的任何变化。
TCP协议的长连接和短连接
短连接的操作过程:建立连接 ——> 传输数据 ——> 关闭连接。
长连接的操作步骤:建立连接 ——> 传输数据 ——> ... (保持连接) ... ——> 传输数据 ——> 关闭连接。
长连接和短连接的优缺点:
短连接的优缺点:
- 优点:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段。
- 缺点:由于TCP的建立和关闭操作需要一定的系统开销,如果客户端连接请求频繁,会降低服务器的处理速度、浪费系统资源和带宽。
长连接的优缺点:
- 优点:长连接可以省去较多的TCP连接的建立和关闭的操作,减少浪费,节约时间。
- 缺点:client 与 server 之间的连接如果一直不关闭的话,会存在一个问题,随着客户端的连接越来越多,服务器的负载压力会增大,降低服务器的整体性能,更严重者,可能导致服务器崩溃;其次,如果大量处于连接状态的TCP通信双方长时间没有进行数据传输,这也会浪费系统和网络资源。
长连接/短连接的应用场景:
长连接一般多用于需要频繁进行读写操作,点对点通讯,而且连接数不太多的情况。
例如:数据库的连接通常使用长连接,如果用短连接的话,频繁的TCP socket创建和关闭,会造成socket错误,也是对资源的一种浪费。
短连接一般用于不需要频繁进行读写操作,并且连接数很大的情况下。
例如:web网站的http服务一般都用短连接。因为长连接对于服务器来说是要耗费一定的系统资源的,像web网站服务,通常会有大量的客户端连接请求,并发连接量大,使用短连接会更节省系统资源,能够及时响应客户请求。
长连接的保活机制
对于TCP长连接,当通信双方在没有数据传输的时候,如何保持TCP连接一直处于“保活(KeepAlive)”状态,这是一个必须要解决的问题。
在Linux系统中,我们可以使用 netstat、lsof等命令可以查看TCP连接是否处于“ESTABLISHED”状态。
TCP保活的必要性:
- 很多防火墙会主动关闭空闲的socket。
- 可能出现的非正常断连,服务器并不能检测到,为了回收已断连的socket资源,必须提供一种检测机制。
导致TCP非正常断连的可能原因:
(1)网络故障
(2)客户端/服务端一侧突然断电或者进程崩溃
保活机制的方式:
应用层的心跳机制:
在应用层中使用心跳(heartbeat)机制来主动检测。具体做法:当TCP连接建立成功后,客户端开启一个定时任务,定时对已经建立连接的对端发送一个心跳请求消息,服务器收到该心跳消息后,返回一个心跳应答消息。如果在超时时间内没有收到服务器的应答消息,则重发心跳请求消息,如果客户端持续多次没有响应,客户端则可以认为该TCP连接不可用,主动断开连接。当然,也可以是服务器端主动发送心跳请求消息给客户端。
TCP协议自带的保活机制:
Linux内核自带的保活机制keep-alive。使用的时候只需要打开keep-alive功能即可。
TCP Keepalive 和 应用层 heartbeat 优缺点:
1、TCP协议的 Keepalive 机制
优点:TCP协议的Keepalive机制由系统内核实现,上层应用程序只需要处理数据的收发,连接异常通知即可,这就减少了应用层代码的复杂度,内核层面的计时器相比应用层,更为高效。
缺点:第一,TCP keepalive机制,位于传输层,由操作系统负责,只能检测到连接是否存活,但不能检测检测连接是否可用。例如,服务器因为某种原因导致负载超高,CPU使用率达到了100%,无法继续响应任何业务请求,但是TCP探针却仍能确定连接状态,这就是典型的连接活着但是服务已死的状态。对于客户端而言,这时最好的选择就是断开连接重新连接到其他服务器上,而不是一直认为当前服务器仍处于可用状态,一直向当前服务器发送那些必然会失败的请求。
第二,TCP keepalive机制 对于连接异常断开的情况不能及时有效地监测到。如果TCP连接的某一方突然异常断开连接,这个时候发送方并不知道对端已经掉线。而此时,如果有数据发送失败,tcp会自动进行超时重传,而重传报文段的优先级是要高于keepalive的探测报文段的,导致探测报文段总是不能发送出去,直到经过较长时间的重传之后,我们才会知道。
2、应用层的HeartBeat 机制
优点:第一,具有更好的灵活可控性。可以控制心跳的监测时机、间隔和流程,甚至可以在心跳包上附带额外信息,最重要的是不光可以检测连接是否存在,还可以检测到连接是否可用,而TCP的keepalive机制只能提供简单的检活功能。
第二,具有通用性。应用层的心跳不依赖传输层协议,如果有一天不用TCP要改用UDP了,传输层不提供心跳机制了,但是你应用层的心跳机制依然可以使用,只需做少许改动就可以继续使用。
缺点:第一,需要开发人员自己实现,增加软件开发的工作量,由于应用特定的网络框架,还可能增加代码结构的复杂度。
第二,应用层心跳的流量消耗会更大,毕竟这本质上还是一个普通的数据包。
Syn泛洪攻击
在SYN泛洪攻击中,攻击者向目标服务器上的每个端口重复发送SYN数据包,通常使用伪造的IP地址。服务器不知道受到攻击,收到多个看似合法的请求以建立通信。它使用来自每个开放端口的SYN-ACK数据包响应每次尝试。
恶意客户端要么不发送预期的ACK,要么(如果IP地址被欺骗)从一开始就不会收到SYN-ACK。无论哪种方式,受到攻击的服务器都会等待一段时间来等待对其SYN-ACK数据包的确认。
在此期间,服务器无法通过发送RST数据包关闭连接,连接保持打开状态。在连接超时之前,另一个SYN数据包将到达。这导致越来越多的连接处于半开状态——事实上,SYN泛洪攻击也被称为“半开”攻击。最终,当服务器的连接溢出表填满时,对合法客户端的服务将被拒绝,服务器甚至可能出现故障或崩溃。
SYN缓解方法
微块——管理员可以在服务器内存中为每个传入的SYN请求分配一个微记录(少至16字节),而不是一个完整的连接对象。
SYN cookies——使用加密哈希,服务器发送其SYN-ACK响应,其中包含一个序列号(seqno),该序列号由客户端IP地址、端口号和可能的其他唯一标识信息构成。当客户端响应时,此哈希包含在ACK数据包中。服务器验证ACK,然后才为连接分配内存。
RST cookies——对于给定客户端的第一个请求,服务器故意发送无效的SYN-ACK。这应该会导致客户端生成一个RST数据包,告诉服务器出了什么问题。如果收到,服务器知道请求是合法的,记录客户端,并接受来自它的后续传入连接。
堆栈调整——管理员可以调整TCP堆栈以减轻SYN泛洪的影响。这可能涉及减少超时,直到堆栈释放分配给连接的内存,或有选择地丢弃传入连接。
拥塞控制
拥塞现象:当网络的吞吐量达到一定的限制后,会发生拥塞现象,那么此时发送的数据包会因为延迟无法到达接收端,因为超时重传机制所以发送端会重新发送数据包,那么网络带宽中本来就很拥塞,那么的话再发数据包就会更加拥塞,由此会形成恶性循环,如果不加控制的话,那么最终网络的有效吞吐量将接近为0.
拥塞控制代价: 需要获得网络内部流量分布的信息。在实施拥塞控制之前,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。这样就产生了额外的开销。拥塞控制还需要将一些资源分配给各个用户单独使用,使得网络资源不能更好地实现共享。
拥塞控制的四种算法:慢开始、拥塞避免、快重传和快恢复。
滑动窗口的原理
TCP是全双工通信,因此每一方的滑动窗口都包括了接收窗口+发送窗口,接收窗口负责处理自己接收到的数据,发送窗口负责处理自己要发送出去的数据。滑动窗口的本质其实就是维护几个变量,通过这些变量将TCP处理的数据分为几类,同时在发送出一个报文、接收一个报文对这些变量做一定的处理维护。
什么是粘包以及粘包的原因
粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
比如说:发送方发送了两个数据,接收方一次收了一个半数据(接收方可能不清楚一个包有多大)
出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。
发送方引起的粘包是由TCP协议本身造成的:
- TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
- TCP协议规定有MSS,如果数据包过长就会被分开传输。这样接收方就收到了粘包数据。
接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
在代码中常见体现:
- 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
- 要发送的数据大于MSS,TCP在传输前将进行拆包。
- 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。
- 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。
粘包的处理方式
- 发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
- 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
- 可以在数据包之间设置边界,如添加特殊符号
- 这样,接收端通过这个边界就可以将不同的数据包拆分开。
TCP和UDP使用场景
1.UDP 使用场景:
因此UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务,随时都可以发送数据,处理简单且高效。
所以主要使用在以下场景:
- 包总量较小的通信(DNS、SNMP)
- 视频、音频等多媒体通信(即时通信)
- QQ就是使用的UDP协议。
- 广播通信
主要是一切追求速度的场景上
2.TCP 使用场景:
TCP 使用场景:相对于 UDP,TCP 实现了数据传输过程中的各种控制,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。在对可靠性要求较高的情况下,可以使用 TCP,即不考虑 UDP 的时候,都可以选择 TCP。
特别是需要可靠连接,比如付费、加密数据等等方向都需要依靠TCP