1、三次握手

三次握手详解
三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。
image.png
刚开始客户端处于 closed 的状态,服务端处于 listen 状态。然后

  1. 第一次握手([SYN], Seq = x)
    客户端发送一个SYN标记的包,Seq初始序列号x,发送完成后客户端进入SYN_SEND状态。
  2. 第二次握手([SYN,ACK], Seq = y, ACK = x + 1)
    服务器返回确认包(ACK)应答,同时还要发送一个SYN包回去。ACK = x + 1,表示确认收到(客户端发来的Seq值 + 1),Seq = y, 表示让客户端确认是否能收到。发送完成后服务端进入SYN_RCVD状态。
  3. 第三次握手([ACK], ACK = y + 1)
    客户端再次发送确认包(ACK),ACK = y + 1, 表示确认收到服务器的包(服务端发来的Seq值 + 1)。客户端发送完毕后,进入ESTABLISHED状态,服务端接收到这个包,也进入ESTABLISHED状态, TCP握手结束。

    2、为什么需要三次握手,两次?四次?

  • 第一次握手:客户端发送网络包,服务端收到了。 这样服务端就能得出结论:客户端的发送能力、

服务端的接收能力是正常的。

  • 第二次握手:服务端发包,客户端收到了。 这样客户端就能得出结论:服务端的接收、发送能力,

客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。

  • 第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正

常,服务器自己的发送、接收能力也正常。
因此,需要三次握手才能确认双方的接收与发送能力是否正常。
如果是用两次握手,则会出现下面这种情况:
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。
后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报
文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时
间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新
的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出
确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一直等待
客户端发送数据,浪费资源。
可以改为四次握手!
TCP三次握手原本应该是”四次握手”,但是中间的同步报文段SYN和应答报文ACK是可以合在一起的,这两个操作在时间上是同时发送的。
当客户端的到达同步报文段SYN到达服务器的时候,服务器的内核就会第一时间进行应答报文段ACK, 同时也会第一时间发起同步报文段SYN,这两件事情同时触发,于是就没必要分成两次传输,直接一步到位。分成两次反而会更浪费系统资源(需要进行两次的封装和分用)。

3、四次挥手

四次挥手
在断开连接之前客户端和服务器都处于ESTABLISHED状态,双方都可以主动断开连接,以客户端主动断开连接为优。
image.png
第一次挥手:客户端打算断开连接,向服务器发送FIN报文(FIN标记位被设置为1,1表示为FIN,0表示不是),FIN报文中会指定一个序列号,之后客户端进入FIN_WAIT_1状态。
也就是客户端发出连接释放报文段(FIN报文),指定序列号seq = u,主动关闭TCP连接,等待服务器的确认。
第二次挥手:服务器收到连接释放报文段(FIN报文)后,就向客户端发送ACK应答报文,以客户端的FIN报文的序列号 seq+1 作为ACK应答报文段的确认序列号ack = seq+1 = u + 1。
接着服务器进入CLOSE_WAIT(等待关闭)状态,此时的TCP处于半关闭状态(下面会说什么是半关闭状态),客户端到服务器的连接释放。客户端收到来自服务器的ACK应答报文段后,进入FIN_WAIT_2状态。
第三次挥手:服务器也打算断开连接,向客户端发送连接释放(FIN)报文段,之后服务器进入LASK_ACK(最后确认)状态,等待客户端的确认。
服务器的连接释放(FIN)报文段的FIN=1,ACK=1,序列号seq=m,确认序列号ack=u+1。
第四次挥手:客户端收到来自服务器的连接释放(FIN)报文段后,会向服务器发送一个ACK应答报文段,以连接释放(FIN)报文段的确认序号 ack 作为ACK应答报文段的序列号 seq,以连接释放(FIN)报文段的序列号 seq+1作为确认序号ack。
之后客户端进入TIME_WAIT(时间等待)状态,服务器收到ACK应答报文段后,服务器就进入CLOSE(关闭)状态,到此服务器的连接已经完成关闭。
客户端处于TIME_WAIT状态时,此时的TCP还未释放掉,需要等待2MSL后,客户端才进入CLOSE状态。
由客户端到服务器需要一个FIN和ACK,再由服务器到客户端需要一个FIN和ACK,因此通常被称为四次挥手。

4、TCP报文

image.png
最少20字节,因为TCP的头部中20字节的首部是固定的.
source port 和 destination port
两者分别为「源端口号」和「目的端口号」。源端口号就是指本地端口,目的端口就是远程端口。
可以这么理解,我们有很多软件,每个软件都对应一个端口,假如,你想和我数据交互,咱们得互相知
道你我的端口号。
再来一个很官方的:
扩展:应用程序的端口号和应用程序所在主机的 IP 地址统称为 socket(套接字),IP:端口号, 在互
联网上 socket 唯一标识每一个应用程序,源端口+源IP+目的端口+目的IP称为”套接字对“,一对套
接字就是一个连接,一个客户端与服务器之间的连接。
序号(32bit):传输方向上字节流的字节编号。初始时序号会被设置一个随机的初始值(ISN),之
后每次发送数据时,序号值 = ISN + 数据在整个字节流中的偏移。假设A -> B且ISN = 1024,第一段
数据512字节已经到B,则第二段数据发送时序号为1024 + 512。用于解决网络包乱序问题。
确认号(32bit):接收方对发送方TCP报文段的响应,其值是收到的序号值 + 1。 确认序列号是接收确认端所期望收到的下一序列号。确认序号应当是上次已成功收到数据字节序号加1,只有当标志位中的 ACK 标志为 1 时该确认序列号的字段才有效。主要用来解决不丢包的问题。
首部长(4bit):标识首部有多少个4字节 首部长,最大为15,即60字节。
TCP首部长度,因为只有4位,所以首部长度最长为60字节,如果没有选项字段,正常的长度是 20字节。
首部长度的计算方式:32位系统中一个字等于4个字节,首部长度的单位是字,首部长度字段有4位,所以TCP首部最长为 2^4-1 = 15(字) = 60(字节)。
标志位(6bit)
URG:标志紧急指针是否有效。
ACK:标志确认号是否有效(确认报文段)。用于解决丢包问题。
PSH:提示接收端立即从缓冲读走数据。 推送标志
RST:表示要求对方重新建立连接(复位报文段)。
SYN:表示请求建立一个连接(连接报文段)。 同步标志。
FIN:表示关闭连接(断开报文段)。
窗口(16bit):接收窗口。用于告知对方(发送方)本方的缓冲还能接收多少字节数据。用来进行流量控制。
*校验和(16bit)
:接收端用CRC检验整个报文段有无损坏。

5、为什么需要四次

这是由于TCP的半关闭(half-close)造成的。半关闭是指:TCP提供了连接的一方在结束它的发送后还能接受来自另一端数据的能力。通俗来说,就是不能发送数据,但是还可以接受数据。
TCP不允许连接处于半打开状态时,就单向传输数据,因此完成三次握手后才可以传输数据(第三握手可以携带数据)。
当连接处于半关闭状态时,TCP是允许单向传输数据的,也就是说服务器此时仍然可以向客户端发送数据,等服务器不再发送数据时,才会发送FIN报文段,同意现在关闭连接。
这一特性是由于TCP双向通道互相独立所导致的,也使得关闭连接必须经过四次握手。

为什么中间的ACK和FIN不可以像三次握手那样合为一个报文段呢?
在socket网络编程中,执行close()方法会触发内核发送FIN报文。什么时候调用close()方法,这是由用户态决定的,假如服务器仍有大量数据等待处理,那么服务器会等数据处理完后,才调用close()方法,这个时间可能会很久,而ACK报文则是由系统内核来完成的,这个过程会很快。所以中间的ACK和FIN不能合为一个包。

6、2MSL

TIME_WAIT等待的2MSL时间,可以理解为数据报一来一回所需要的最大时间。
1.保证客户端最后发送的ACK能够到达服务器,帮助其正常关闭。
由于这个ACK报文段可能会丢失,使得处于LAST_ACK状态的服务器得不到对已发送FIN报文段的确认,从而会触发超时重传。服务器会重发FIN报文段,客户端能保证在2MSL时间内收到来自服务器的重传FIN报文段,从而客户端重新发送ACK应答报文段,并重置2MSL计数。
假如客户端不等待2MSL就之间进入CLOSE状态,那么服务器会一直处于LAST_ACK状态。
当客户端发起建立SYN报文段请求建立新的连接时,服务端会发送RST报文段给客户端,连接建立的过程就会被终止。
2.防止已失效的连接请求报文段出现在本连接中。
TIME_WAIT等待的2MSL时间,确保本连接内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。

7、TIME_WAIT状态过多有什么危害?如何解决?

只有主动发起断开请求的一方才会进入TIME_WAIT状态!
1.占用系统资源
2.socket的TIME_WAIT状态结束之前,该socket占用的端口号将一直无法释放。如果服务器TIME_WAIT状态过多,占满了所有端口资源,则会导致无法创建新的连接。
解决:尽量让客户端主动断开连接,除非遇到一些异常情况,如客户端协议错误、客户端超时等。

8、常见TCP的连接状态有哪些?

CLOSED:初始状态。
LISTEN:服务器处于监听状态。
SYN_SEND:客户端socket执行CONNECT连接,发送SYN包,进入此状态。
SYN_RECV:服务端收到SYN包并发送服务端SYN包,进入此状态。
ESTABLISH:表示连接建立。客户端发送了最后一个ACK包后进入此状态,服务端接收到ACK包后
进入此状态。
FIN_WAIT_1:终止连接的一方(通常是客户机)发送了FIN报文后进入。等待对方FIN。
CLOSE_WAIT:(假设服务器)接收到客户机FIN包之后等待关闭的阶段。在接收到对方的FIN包之
后,自然是需要立即回复ACK包的,表示已经知道断开请求。但是本方是否立即断开连接(发送FIN
包)取决于是否还有数据需要发送给客户端,若有,则在发送FIN包之前均为此状态。
FIN_WAIT_2:此时是半连接状态,即有一方要求关闭连接,等待另一方关闭。客户端接收到服务器
的ACK包,但并没有立即接收到服务端的FIN包,进入FIN_WAIT_2状态。
LAST_ACK:服务端发动最后的FIN包,等待最后的客户端ACK响应,进入此状态。
TIME_WAIT:客户端收到服务端的FIN包,并立即发出ACK包做最后的确认,在此之后的2MSL时间
称为TIME_WAIT状态。
9、TCP拥塞控制
TCP的拥塞控制及拥塞控制方法
与流量控制不同,前者是全局性的控制,而后者是针对点对点通信量的控制。
网络上有很多计算机,可能当前的网络状态已经比较拥堵了。此时贸然发送大量数据,会造成大量丢包。若出现拥塞而不进行控制,整个网络的吞吐量将随输入负荷的增大而下降。TCP的拥塞控制便是用于防止网络出现拥塞的。
判断
1、是否发生超时,超时重传RTO[Retransmission Timeout]
如果过了超时重传时间发送方仍未收到确认报文,TCP就会认为当前网络已经发生拥塞。采用慢开始算法处理。
2、收到连续3个重复的ACK报文
如果发送方收到了连续3个重复的ACK报文,那么TCP也会认为当前网络发生了拥塞。采用快恢复算法进行处理。
方法
1、慢开始
2、拥塞避免
3、快重传
4、快恢复
拥塞窗口
TCP连接中发送方会维护一个叫拥塞窗口(cwnd)的状态变量。
拥塞窗口的大小取决于网络的拥塞程度,并动态地变化着,发送方会使自己的发送窗口等于拥塞窗口。
慢开始
慢开始算法的思路是先发送少量数据探探路,然后慢慢地增大拥塞窗口的大小。
在慢启动算法中,每经过一个传播轮次,拥塞窗口就会加倍。
为了不让拥塞窗口增长过快,TCP引入了慢启动门限(ssthresh),当拥塞窗口大小超过这个门限时,就不再按慢开始的算法增大拥塞窗口的大小了,而是改用拥塞避免算法。
拥塞避免
每经过一个往返时间RTT,就把发送方的拥塞窗口+1。
即拥塞窗口按线性规律缓慢增大,这要比慢开始算法的拥塞窗口增长速率缓慢很多。
快重传
快重传算法可以让发送方尽早知道发生了个别数据段的丢失。
按照快重传算法,假如接收方收到了M1数据段和M2数据段,然后没有收到M3数据段就收到了M4数据段,那么接收方会重复发送M2确认报文,提醒发送方M3数据段已丢失。
在快重传算法中,发送方一旦收到连续3个重复的确认报文,就会立即重新发送丢失数据段。
快恢复
当发送方连续收到3个重复的确认报文时,发送方就知道当前发生了个别数据段的丢失,但网络很可能还没有发生拥塞,若采用慢开始算法速率下降过于严重,因此采用快恢复算法以缓解当前网络状态。
快恢复算法具体流程:
1、慢开始门限值改为当前拥塞窗口/2。
2、新拥塞窗口=慢开始门限。
3、开始执行拥塞避免算法,使拥塞窗口缓慢地线性增大。

9、流量控制

流量控制

滑动窗口

TCP的流量控制机制是为了解决端到端的数据传输速率问题。是接收端控制发送端发送数据的速率,以便使接收端来得及接收。
TCP协议使用滑动窗口机制来实现对发送方的流量控制。
TCP 中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数
。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时,发送方一般
不能再发送数据报,但有两种情况除外:

  • 一种情况是可以发送紧急数据。 例如,允许用户终止在远端机上的运行进程。
  • 另一种情况是发送方可以发送一个 1 字节的数据报来通知接收方重新声明它希望接收的下一字节及发送方的滑动窗口大小。

TCP是流数据,发送出去的数据流可以被分为以下四部分:已发送且被确认部分 | 已发送未被确认部
分 | 未发送但可发送部分 | 不可发送部分,其中发送窗 = 已发送未确认部分 + 未发但可发送部分。接
收到的数据流可分为:已接收 | 未接收但准备接收 | 未接收不准备接收。接收窗 = 未接收但准备接收
部分。

停止等待协议
送方每发送一个报文段后就停止发送,一直等收到接收方的确认后再发送下一个报文段。
滑动窗口
发送方在发送了一个段之后不必一直等待这个段的确认应答,而是继续发送下一个报文段。每当收到一个报文段的确认应答后,窗口就向前滑动一个报文段的长度,因为已发送并收到确认应答的报文段不需要再保留在窗口中了,但已发送还未收到确认应答的段还必须保留在窗口中,以便在超时重传时使用。需要注意的是,滑动窗口是以字节为单位向前滑动的。
大小
发送窗口的位置由窗口前沿和后沿的位置共同确定。
它后沿变化有两种,(1)不动(没有收到新的确定)(2)前移(收到新的确认);
前沿是不断向前移动的,但也可能不动(1)没有收到新确认,对方窗口也不变,(2)收到新的确认,对方的接收窗口缩小了,使前沿正好不变)
发送窗口值不是由发送方自己决定的,而是根据接收方给出的窗口值,发送方再构造出自己的发送窗口值。
缓冲区
发送方和接收方都有自己的缓冲区。
发送缓冲区存放:
1、发送发TCP准备发送的数据
2、TCP已发送但尚未收到确认的数据(为超时重传准备)
接收缓冲区存放:
1、按序到达但未被应用程序读取的数据
2、未按序到达的数据
发送方的应用进程把字节流数据写入 TCP 的发送缓存,而接收方的应用进程则从 TCP 的接收缓存中读取字节流数据。发送窗口通常只是发送缓存的一部分。 接收窗口通常也是接收缓存的一部分。

三种协议
1、1比特滑动窗口协议(发送窗口=1,接收窗口=1 )
滑动窗口协议退化成停止等待协议。
2、后退n协议(发送窗口>1,接收窗口>1)
发送方在每发送完一个数据帧时都要设置超时定时器。
缺点是重发出错的帧和之后的即使没出错的帧。
3、选择重传协议(发送窗口>1,接收窗口>1)
当接收方发现某个帧出错后(可能未按序到达),它将后面到达的正确的帧放入接收缓冲区中,同时要求发送方重传出错的帧,当重传的帧到达后,将缓冲区中按序一并发送给高层。

流量控制
接收方每次收到数据包,可以在发送确定报文的时候,同时告诉发送方自己的缓存区还剩余多少是空闲的,我们也把缓存区的剩余大小称之为接收窗口大小,用变量 win 来表示接收窗口的大小。
发送方收到之后,便会调整自己的发送速率,也就是调整自己发送窗口的大小,当发送方收到接收窗口的大小为0时,发送方就会停止发送数据,防止出现大量丢包情况的发生。
可以继续发送时,接收方会发通知报文通知对方。零窗口探测报文段仅携带一个字节的数据

TCP规定,即使设置为零窗口,也必须接收以下几种报文段:零窗口探测报文段、确认报文段(ACK=1)和携带紧急数据的报文段(URG=1)。

但如果这个报文丢失,双方就会死锁。解决:
当发送方收到接受窗口 win = 0 时,这时发送方停止发送报文,并且同时开启一个定时器,每隔一段时间就发个测试报文去询问接收方,打听是否可以继续发送数据了,如果可以,接收方就告诉他此时接受窗口的大小;如果接受窗口大小还是为0,则发送方再次刷新启动定时器。

TCP报文段的发送时机:
应用进程把数据传送到 TCP 的发送缓存后,剩下的发送任务就由 TCP 来控制了。
三种机制:

  • TCP 维持一个变量,它等于 最大报文段长度MSS。只要缓存中存放的数据达到MSS字节时,就组装成一个 TCP 报文段发送出去。
  • 第二种机制是由发送方的应用进程指明要求发送报文段,即 TCP 支持的 推送(push)操作。
  • 第二种机制是由发送方的应用进程指明要求发送报文段,即 TCP 支持的 推送(push)操作。

数据较少时这样的效率并不高,可以适当推迟发回确认报文,并尽量使用捎带确认的方法。Nagle(纳格)算法

1、若发送应用进程把要发送的数据逐个字节地送到 TCP 的发送缓存,则发送方就把第一个数据字节发送出去,把后面到达的数据字节都先缓存起来。 2、当发送方接收到对第一个数据字符的确认后,再把发送缓存中的所有数据组装成一个报文段再发送出去,同时继续对随后到达的数据进行缓存。只有在收到对前一个报文段的确认后,才继续发送下一个报文段。 3、当数据到达较快而网络速率较慢时,用这样的方法可明显地减少所用的网络带宽。 4、Nagle算法还规定:当到达的数据已达到发送窗口大小的一半或已经达到报文段的最大长度时,就立即发送一个报文段。

糊涂窗口综合征(silly window syndrome)
接收窗口很小,每次只能接受/发送很少数据,降低网络的传输效率。
TCP 接收方的缓存已满,而交互式的应用进程一次只从接收缓存中读取 1 个字节(这样就使接收缓存空间仅腾出 1 个字节),然后向发送方发送确认,并把窗口设置为 1 个字节(但发送的数据报为 40 字节长)。接着,发送方又发来 1 个字节的数据(请注意,发送方的 IP 数据报是 41 字节长)。接收方发回确认,仍然将窗口设置为 1 个字节。这样进行下去,使网络的传输效率很低。
解决:可以让接收方等待一段时间,使得或者接收缓存已有足够空间容纳一个最长的报文段MSS,或者等到接收方缓存已有一半空闲的空间。只要出现这两种情况之一,接收方就发出确认报文,并向发送方通知当前的窗口大小。此外,发送方也不要发送太小的报文段,而是把数据积累成足够大的报文段,或达到接收方缓存的空间的一半大小。

10、流量控制与拥塞控制的区别

作用,对象,方法,窗口大小。
拥塞控制:拥塞控制是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况;常用的方法就是:( 1 )慢开始、拥塞避免( 2 )快重传、快恢复。拥塞控制的拥塞窗口大小变化由试探性发送一定数据量数据探查网络状况后而自适应调整。实际最终发送窗口 = min{流控发送窗口,拥塞窗口}。
流量控制:流量控制是作用于接收者的,它是控制发送者的发送速度从而使接收者来得及接收,防止分组丢失的。属于通信双方协商。需要通信双方各维护一个发送窗、一个接收窗,对任意一方,接收窗大小由自身决定,发送窗大小由接收方响应的TCP报文段中窗口值确定;

11、TCP可靠在哪里?

确认和重传:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就会重传。
数据校验:TCP报文头有校验和,用于校验报文是否损坏。
数据合理分片和排序:tcp会按最大传输单元(MTU)合理分片,接收方会缓存未按序到达的数据,重新
排序后交给应用层。而UDP:IP数据报大于1500字节,大于MTU。这个时候发送方的IP层就需要分
片,把数据报分成若干片,是的每一片都小于MTU。而接收方IP层则需要进行数据报的重组。由于
UDP的特性,某一片数据丢失时,接收方便无法重组数据报,导致丢弃整个UDP数据报。
流量控制:当接收方来不及处理发送方的数据,能通过滑动窗口,提示发送方降低发送的速率,防止
包丢失。
拥塞控制:当网络拥塞时,通过拥塞窗口,减少数据的发送,防止包丢失。

12、TCP和UDP

TCP 传输控制协议 面向连接,可靠,按序传输数据;对数据有校验和重发。
拥塞控制、流量控、差错控制、连接设置
UDP 用户数据报协议 无连接,不可靠,无序传输数据;
对数据无校验和重发。通信速率高,可靠性需要应用程序自己来控制。

对比
TCP UDP
面向连接 无连接,发送数据之前不需要建立连接
提供可靠的服务,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达。 尽最大努力交付,即不保证可靠交付
面向字节流。
发送方TCP会将数据放入缓冲区,等到可以发送的时候就发送,不能发送就等着。TCP会根据当前网络的拥塞状态来确定每个报文段的大小。
拥塞控制。
面向报文。
发送方的UDP对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层,论应用层交给UDP多长的报文,它统统发送,一次发送一个。而对接收方,接到后直接去除首部,交给上面的应用层。它需要应用层控制报文的大小 。
没有拥塞控制,网络出现拥塞不会使源主机的发送速率降低,对实时应用很有用。
每一条TCP连接只能是点到点的。 支持一对一,一对多,多对一和多对多的交互通信
首部开销20字节 UDP的首部开销小,只有8个字节
逻辑通信信道 全双工的可靠信道 不可靠信道

TCP特点:

(1)TCP是面向连接的运输层协议。应用程序在使用TCP协议之前,必须先建立TCP连接,在传送数据完毕后,必须释放已经建立的TCP连接,在传送数据完毕后,必须释放已经连接的TCP连接。
(2)每一条TCP连接只能有两个端点,即点对点的。
(3)TCP提供可靠交付的服务。通过TCP连接传送的数据,无差错、不丢失、不重复、并且按序到达。
(4)TCP提供全双工通信。TCP允许通信双方的应用进程在任何时候都能发送数据。
(5)面向字节流。TCP中的“流”指的是流入到进程或从进程流出的字节序列。面向字节流的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但是TCP把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。

UDP特点:

(1)UDP是无连接的,即发送数据之前不需要建立连接(当然,发送数据结束时也没有连接可以释放),因此减少了开销和发送数据之前的时延。
(2)UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表(这里面有很多参数)。
(3)UDP是面向报文的。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这就是说,应用层交给UDP多长 的报文,UDP就照样发送,即一次发送一个报文。
(4)UDP没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。但是不使用拥塞控制功能的UDP有可能会引起网络产生严重的拥塞问题。
(5)UDP支持一对一、一对多、多对一和多对多的交互通信。
(6)UDP的首部开销小,只有8个字节,比TCP的20个字节的首部还要短。

应用场景

TCP应用场景:
效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。
UDP应用场景:
效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)

13、TCP粘包

TCP粘包

是什么

发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾。
在进行 Java NIO 学习时,可能会发现:如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况。

  1. TCP 是基于字节流的,虽然应用层和 TCP 传输层之间的数据交互是大小不等的数据块,但是 TCP 把这些数据块仅仅看成一连串无结构的字节流,没有边界;
  2. 从 TCP 的帧结构也可以看出,在 TCP 的首部没有表示数据长度的字段。

基于上面两点,在使用 TCP 传输数据时,才有粘包或者拆包现象发生的可能。一个数据包中包含了发送端发送的两个数据包的信息,这种现象即为粘包。
接收端收到了两个数据包,但是这两个数据包要么是不完整的,要么就是多出来一块,这种情况即发生了拆包和粘包。

为什么

(1)发送方原因
TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:
只有上一个分组得到确认,才会发送下一个分组,收集多个小分组,在一个确认到来时一起发送。Nagle算法造成了发送方可能会出现粘包问题。
采用 TCP 协议传输数据的客户端与服务器经常是保持一个长连接的状态(一次连接发一次数据不存在粘包),双方在连接不断开的情况下,可以一直传输数据。但当发送的数据包过于的小时,那么 TCP 协议默认的会启用 Nagle 算法,将这些较小的数据包进行合并发送(缓冲区数据发送是一个堆压的过程);这个合并过程就是在发送缓冲区中进行的,也就是说数据发送出来它已经是粘包的状态了。
(2)接收方原因
TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。
接收方采用 TCP 协议接收数据时的过程是这样的:数据到接收方,从网络模型的下方传递至传输层,传输层的 TCP 协议处理是将其放置接收缓冲区,然后由应用层来主动获取(C 语言用 recv、read 等函数);这时会出现一个问题,就是我们在程序中调用的读取数据函数不能及时的把缓冲区中的数据拿出来,而下一个数据又到来并有一部分放入的缓冲区末尾,等我们读取数据时就是一个粘包。(放数据的速度 > 应用层拿数据速度)

什么时候需要处理
  1. 如果发送方发送的多组数据本来就是同一块数据的不同部分,比如说一个文件被分成多个部分发送,这时当然不需要处理粘包现象
  2. 如果多个分组毫不相干,甚至是并列关系,那么这个时候就一定要处理粘包现象了

    如何处理

    (1)发送方
    对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。
    (2)接收方
    接收方没有办法来处理粘包现象,只能将问题交给应用层来处理。
    (3)应用层
    分包机制一般有两个通用的解决方法:

  3. 特殊字符控制;

  4. 在包头首都添加数据包的长度。
    UDP会出现吗
    TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。
    UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
    但是有丢包和乱序。不完整的包是不会有的,收到的都是完全正确的包。传送的数据单位协议是 UDP 报文或用户数据报,发送的时候既不合并,也不拆分。

14、RTO RTT 超时重传

超时重传:发送端发送报⽂后若⻓时间未收到确认的报⽂则需要重发该报⽂。可能有以下⼏种情况:

  • 发送的数据没能到达接收端,所以对⽅没有响应。
  • 接收端接收到数据,但是ACK报⽂在返回过程中丢失。
  • 接收端拒绝或丢弃数据。

RTO:重传间隔,从上⼀次发送数据,因为⻓期没有收到ACK响应,到下⼀次重发之间的时间。
通常每次重传RTO是前⼀次重传间隔的两倍,单位通常是RTT。例:1RTT,2RTT,4RTT,
8RTT…… ,重传次数到达上限之后停⽌重传间隔传。
RTT:数据从发送到接收到对⽅响应之间的时间间隔,即数据报在⽹络中⼀个往返⽤时。⼤⼩不稳定。

15、TCP和UDP对应的应用层协议

TCP

FTP:定义了文件传输协议,使用21端口.
Telnet:它是一种用于远程登陆的端口,23端口
SMTP:定义了简单邮件传送协议,服务器开放的是25号端口。
POP3:它是和SMTP对应,POP3用于接收邮件。111端口
HTTP协议 :从 Web 服务器传输超文本到本地浏览器的传送协议。

UDP

DNS:用于域名解析服务,用的是53号端口
SNMP:简单网络管理协议,使用161号端口
TFTP(Trival File Transfer Protocal):简单文件传输协议,69

16、封包与拆包

针对TCP的,TCP是无边界的流传输,需要对TCP进行封包和拆包,确保发送和接收的数据不粘连。
封包:封包就是在发送数据报的时候为每个TCP数据包加上一个包头,将数据报分为包头和包体两个
部分。包头是一个固定长度的结构体,里面包含该数据包的总长度。
拆包:接收方在接收到报文后提取包头中的长度信息进行截取。

17、保活计时器作用

除时间等待计时器外,TCP 还有一个保活计时器(keepalive timer)。设想这样的场景:客户已主动与服务器建立了 TCP 连接。但后来客户端的主机突然发生故障。显然,服务器以后就不能再收到客户端发来的数据。因此,应当有措施使服务器不要再白白等待下去。这就需要使用保活计时器了。
服务器每收到一次客户的数据,就重新设置保活计时器,时间的设置通常是两个小时。若两个小时都没有收到客户端的数据,服务端就发送一个探测报文段,以后则每隔 75 秒钟发送一次。若连续发送 10个 探测报文段后仍然无客户端的响应,服务端就认为客户端出了故障,接着就关闭这个连接。

18、TIME_WAIT状态会导致什么问题,如何解决?

高并发短链接,会导致一些正常的连接失败。
解决方案:

  • 修改配置或设置 SO_REUSEADDR 套接字,使得服务器处于 TIME-WAIT 状态下的端口能够快速回收和重用。
  • 采用长连接的方式减少 TCP 的连接与断开,在长连接的业务中往往不需要考虑 TIME-WAIT 状态,但其实在长连接的业务中并发量一般不会太高。

    19、CLOSE_WAIT的连接大量出现的原因是什么,如何解决?

    close_wait状态是在TCP四次挥手的时候收到FIN但是没有发送自己的FIN时出现的,服务器出现大量
    close_wait状态的原因有两种:

  • 服务器内部业务处理占用了过多时间,都没能处理完业务;或者还有数据需要发送;或者服务器的业 务逻辑有问题,没有执行close()方法

  • 服务器的父进程派生出子进程,子进程继承了socket,收到FIN的时候子进程处理但父进程没有处理该信号,导致socket的引用不为0无法回收

处理方法:

  • 停止应用程序
  • 修改程序里的bug

    20、TCP soctet交互流程?

    image.png
    image.png
    服务器

  • 创建socket -> int socket(int domain, int type, int protocol);

    • domain:协议域,决定了socket的地址类型,IPv4为AF_INET。
    • type:指定socket类型,SOCK_STREAM为TCP连接。
    • protocol:指定协议。IPPROTO_TCP表示TCP协议,为0时⾃动选择type默认协议。
  • 绑定socket和端⼝号 -> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    • sockfd:socket返回的套接字描述符,类似于⽂件描述符fd。
    • addr:有个sockaddr类型数据的指针,指向的是被绑定结构变ᰁ。
    • addrlen:地址⻓度。
  • 监听端⼝号 -> int listen(int sockfd, int backlog);
    • sockfd:要监听的sock描述字。
    • backlog:socket可以排队的最⼤连接数。
  • 接收⽤户请求 -> int accept(int sockfd, struct sockaddr addr, socklen_t addrlen);
    • sockfd:服务器socket描述字。
    • addr:指向地址结构指针。
    • addrlen:协议地址⻓度。
    • 注:⼀旦accept某个客户机请求成功将返回⼀个全新的描述符⽤于标识具体客户的TCP连接。
  • 从socket中读取字符 -> ssize_t read(int fd, void *buf, size_t count);
    • fd:连接描述字。
    • buf:缓冲区buf。
    • count:缓冲区⻓度。
    • 注:⼤于0表示读取的字节数,返回0表示⽂件读取结束,⼩于0表示发⽣错误。
  • 关闭socket -> int close(int fd);
    • fd:accept返回的连接描述字,每个连接有⼀个,⽣命周期为连接周期。
    • 注:sockfd是监听描述字,⼀个服务器只有⼀个,⽤于监听是否有连接;fd是连接描述字,⽤于每个连接的操作。

客户机

  • 创建socket -> int socket(int domain, int type, int protocol);
    • addr:服务器的地址。
    • addrlen:socket地址⻓度。
  • 向socket写⼊信息 -> ssize_t write(int fd, const void *buf, size_t count);
    • fd、buf、count:同read中意义。
    • ⼤于0表示写了部分或全部数据,⼩于0表示出错。
  • 关闭oscket -> int close(int fd);
    • fd:同服务器端fd。

      // IPv4的sockaddr地址结构 struct sockaddr_in { sa_family_t sin_family; // 协议类型,AF_INET in_port_t sin_port; // 端⼝号 struct in_addr sin_addr; // IP地址 }; struct in_addr { uint32_t s_addr; }连接指定计算机 -> int connect(int sockfd, struct sockaddr* addr, socklen_t addrlen); sockfd客户端的sock描述字。

21、TCP最大连接数

如何标识一个TCP连接
在确定最大连接数之前,先来看看系统如何标识一个tcp连接。系统用一个4四元组来唯一标识一个TCP连接:{local ip, local port,remote ip,remote port}。
client最大tcp连接数
client每次发起tcp连接请求时,除非绑定端口,通常会让系统选取一个空闲的本地端口(local port),该端口是独占的,不能和其他tcp连接共享。tcp端口的数据类型是unsigned short,因此本地端口个数最大只有65536,端口0有特殊含义,不能使用,这样可用端口最多只有65535,所以在全部作为client端的情况下,最大tcp连接数为65535,这些连接可以连到不同的server ip。
server最大tcp连接数
server通常固定在某个本地端口上监听,等待client的连接请求。不考虑地址重用(unix的SO_REUSEADDR选项)的情况下,即使server端有多个ip,本地监听端口也是独占的,因此server端tcp连接4元组中只有remote ip(也就是client ip)和remote port(客户端port)是可变的,因此最大tcp连接为客户端ip数×客户端port数,对IPV4,不考虑ip地址分类等因素,最大tcp连接数约为2的32次方(ip数)×2的16次方(port数),也就是server端单机最大tcp连接数约为2的48次方。
实际的tcp连接数
上面给出的是理论上的单机最大连接数,在实际环境中,受到机器资源、操作系统等的限制,特别是sever端,其最大并发tcp连接数远不能达到理论上限。在unix/linux下限制连接数的主要因素是内存和允许的文件描述符个数(每个tcp连接都要占用一定内存,每个socket就是一个文件描述符),另外1024以下的端口通常为保留端口。在默认2.6内核配置下,经过试验,每个socket占用内存在15~20k之间。
影响一个socket占用内存的参数包括:
rmem_max
wmem_max
tcp_rmem
tcp_wmem
tcp_mem
grep skbuff /proc/slabinfo
对server端,通过增加内存、修改最大文件描述符个数等参数,单机最大并发TCP连接数超过10万 是没问题的,国外 Urban Airship 公司在产品环境中已做到 50 万并发 。

22、进行UDP编程时,一次发送多少字节好?

相对于不同的系统,不同的要求,其得到的答案是不一样的。
TCP/IP通常被认为是一个四层协议系统,包括链路层,网络层,运输层,应用层.UDP属于运输层,以太网(Ethernet)数据帧的长度必须在46-1500字节之间,这是由以太网的
物理特性决定的.这个1500字节被称为链路层的MTU(最大传输单元).但这并不是指链路层的长度被限制在1500字节,其实这这个MTU指的是链路层的数据区.并不包括链路层的首部和尾部的18个字节.
所以,事实上,这个1500字节就是网络层IP数据报的长度限制。因为IP数据报的首部为20字节,所以IP数据报的数据区长度最大为1480字节.而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的.又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节.这个1472字节就是我们可以使用的字节数。
当我们发送的UDP数据大于1472的时候会怎样呢? 这也就是说IP数据报大于1500字节,大于MTU.这个时候发送方IP层就需要分片(fragmentation). 把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组. 这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便无法重组数据报.将导致丢弃整个UDP数据报。
因此,在普通的局域网环境下,建议将UDP的数据控制在1472字节以下为好.
进行Internet编程时则不同,因为Internet上的路由器可能会将MTU设为不同的值. 如果我们假定MTU为1500来发送数据的,而途经的某个网络的MTU值小于1500字节,那么系统将会使用一
系列的机制来调整MTU值,使数据报能够顺利到达目的地,这样就会做许多不必要的操作. 鉴于Internet上的标准MTU值为576字节,所以我建议在进行Internet的UDP编程时.
最好将UDP的数据长度控件在548字节(576-8-20)以内