Linux:带你理解传输层的UDP/TCP协议


再谈端口号

端口号(Port)标识了一个主机上进行通信的不同的应用程序;

在这里插入图片描述

在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过 netstat -n查看);

在这里插入图片描述

端口号范围划分

  • 0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
ssh服务器, 使用22端口 
ftp服务器, 使用21端口 
telnet服务器, 使用23端口 
http服务器, 使用80端口 
https服务器, 使用443端口
  • 1024 - 65535: 操作系统动态分配的端口号. (客户端程序的端口号, 就是由操作系统从这个范围分配的)

执行下面的命令, 可以看到知名端口号

cat /etc/services

我们自己写一个程序使用端口号时, 要避开这些知名端口号.

注意:

一个进程可以bind多个端口号,但是一个端口号只能被一个进程bind

netstat

netstat是一个用来查看网络状态的重要工具.

语法: netstat [选项]

功能: 查看网络状态

常用选项:

  • n 拒绝显示别名,能显示数字的全部转化成数字
  • l 仅列出有在 Listen (监听) 的服務状态
  • p 显示建立相关链接的程序名
  • t (tcp)仅显示tcp相关选项
  • u (udp)仅显示udp相关选项
  • a (all)显示所有选项,默认不显示LISTEN相关

pidof

在查看服务器的进程id时非常方便.

语法: pidof [进程名]

功能: 通过进程名, 查看进程id

传输层

负责应用程序之间的数据传输( UDP协议 / TCP协议 )

UDP

UDP协议端格式

在这里插入图片描述

udp报文头部长度是8个字节

  • 16位源端口 / 16位目的端口:描述数据从哪个进程发送到哪个进程(负责实现应用程序之间的数据传输)

  • 16位数据报长度:包含udp报文头部在内的整体报文长度(存储的最大数字是65535)

  • 16位校验和:二进制反码求和算法,用于效验接收到的数据和发送的数据是否一致

    • 发送方在发送数据前,校验和为0,从完整报文的第0个字节开始,每个字节进行取反想加,超过16位的部分取出来,再次跟低16位进行想加,最终得到一个校验和填充到协议字段中
    • 接收方接收到数据之后,再次从完整报文的第0个字节开始,每个字节进行取反想加,最终得到一个值若为0,则表示数据一致;否则,则表示数据不一致(如果校验和出错, 就会直接丢弃)

UDP的特点

UDP传输的过程类似于寄信.

  • 无连接:通信的时候,不需要建立连接,只需要知道对方地址信息,就可以直接发送数据
  • 不可靠:通信过程中,并不保证数据安全可靠以及有序的到达对端。(没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层 返回任何错误信息)
  • 数据报传输:无连接,不可靠的,有最大传输长度限制的一种传输方式。
    有最大传输长度限制:udp报文头部中有一个数据报长度字段,最大数字是65535字节,限制了一个完整udp报文的长度不能超过64k

协议实现对上层应用的影响

  • 若sendto传输的数据(应用层的原始数据)大于64k-8,则传输会报错。
    因为若数据过长,需要程序员在应用层自己手动将大数据分割成一个个小的数据端(不大于64k-8)进行sendto发送。

  • udp并不保持数据有序到达,这时候在上层,程序员进行数据分包之后,就要考虑在应用层实现各个数据段的包序管理

  • udp报文都是整条收发的;因此udp在使用recvfrom获取数据的时候,给予的buffer就要足够大, 防止出现数据过长的buffer不够而报错,接收失败的情况。

    • udp报文头部中定义了报文长度,因此sendto发送数据的时候,数据已到发送缓冲区就会直接封装udp报文头部,然后发送数据。(udp的socket发送缓冲区,形同虚设,因此并没有起到数据缓冲的作用)
    • 接收方接收到数据,放到接收缓冲区中,用户recvfrom接收的时候,不能出现接收半条数据或者多条数据的情况,只能是一条完整的数据。(接收半条数据,就会破坏接收缓冲区中的数据完整性,没有头部中长度字段,就没法解析剩下的数据)

udp为什么会丢包

  1. 接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。
  2. 发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。
  3. 发送的包较大,超过接受者缓存导致丢包:包超过mtu size数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。
    int nRecvBuf=321024;//设置为32K
    setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char
    )&nRecvBuf,sizeof(int));
  4. 发送的包频率太快:虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。这种情况也有时可以通过设置socket接收缓冲解决,但有时解决不了。所以在发送频率过快的时候还是考虑sleep一下吧。
  5. 局域网内不丢包,公网上丢包。这个问题我也是通过切割小包并sleep发送解决的。如果流量太大,这个办法也不灵了。总之udp丢包总是会有的,如果出现了用我的方法解决不了,还有这个几个方法: 要么减小流量,要么换tcp协议传输,要么做丢包重传的工作。

TCP

TCP协议段格式

在这里插入图片描述

  • 16位源端口/16位目的端口:负责应用程序之间的数据传输
  • 32位序号/32位确认序号:实现确认应答机制,以及进行包序管理
  • 4位头部长度:表示tcp头部有多长(tcp头部是不定长数据,主要取决于选项数据的大小,以4字节为单位)
    Tcp头部最小20字节,最大60字节(其中选项数据占0~40字节)
  • 6位保留位:没有想好用来干什么,先预留着用于以后扩展
  • 6位标志为:URG / ACK / PSH / RST / SYN / FIN
标记位
紧急位 URG:紧急处理,可提升数据包发送的优先级
确认位 ACK:代表确认号是否有效
急迫位 PSH:接收方应尽快将这个报文交给应用层
重置位 PST:将建立的连接重置
同步位 SYN:同步序号用来发起一个连接
终止位 FIN:终止一个连接
  • 16位窗口大小:用于实现滑动窗口机制,进行流量控制(告诉对端所能发送的最大数据量)

  • 16位校验和:二进制反码求和算法,校验数据接收与发送的一致性

  • 16位紧急指针:标示那些数据是紧急数据

  • 0~40字节的选项数据:通常用于协商一些信息(三次握手时,协商MSS大小的数据)

TCP的特点

面向连接,可靠传输,面向字节流

  • 面向连接:(connection-oriented)
    面向连接的协议要求正式发送数据之前需要通过握手建立一个逻辑连接,结束通信时也是通过有序的四次挥手来断开连接
  • 可靠传输:保证数据能够安全有序到达对端(只要不是网络断开一定保证数据到达对端)
  • 面向字节流: 可靠的,有序的,双向的,基于连接的字节流传输(不限制上层发送/接收的数据大小)

TCP数据传输的过程

在这里插入图片描述

服务器初始化:

  • 调用socket, 创建文件描述符;
  • 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
  • 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
  • 调用accecpt, 并阻塞, 等待客户端连接过来;

建立连接的过程:

  • 调用socket, 创建文件描述符;
  • 调用connect, 向服务器发起连接请求;
  • connect会发出SYN段并阻塞等待服务器应答; (第一次)
  • 服务器收到客户端的SYN, 会应答一个SYN、ACK段表示"同意建立连接"; (第二次)
  • 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)

这个建立连接的过程, 通常称为 三次握手

数据传输的过程

  • 建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
  • 服务器从accept()返回后立刻调用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
  • 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
  • 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;
  • 客户端收到后从read()返回, 发送下一条请求,如此循环下去;

断开连接的过程:

在这里插入图片描述

  • 如果客户端没有更多的请求了, 就调用close / shutdown 关闭写操作, 客户端会向服务器发送FIN段 (客户端进入FIN_WAIT1状态)
  • 此时服务器收到FIN后, 会响应一个ACK, 同时read会返回0 (被动关闭方进入CLOSE_WAIT状态,主动关闭方进入FIN_WAIT2状态)
  • read返回之后, 服务器就知道客户端关闭了连接, 也调用 close / shutdown 关闭写操作, 这个时候服务器会向客户端发送 一个FIN (进入LAST_ACK状态)
  • 客户端收到FIN(进入WIME_WAIT状态), 再返回一个ACK给服务器,被动关闭方接收到后(被动关闭方进入CLOSED状态 - 释放资源,主动关闭方在等待一段时间后进入CLOSED状态 - 释放资源)

这个断开连接的过程, 通常称为 四次挥手

上图可以这么理解:

客户端:“兄弟,我这边没数据要传了,咱关闭连接吧。”
服务端:“收到,我看看我这边有木有数据了。”
服务端:“兄弟,我这边也没数据要传你了,咱可以关闭连接了。”
客户端:“好嘞。”

TCP 和 UDP 对比

  • 可靠传输 vs 不可靠传输
  • 有连接 vs 无连接
  • 字节流 vs 数据报

确认应答(ACK)机制与超时重传机制

  • 确认应答机制:对于发送的每一条数据,都要求接收方进行确认回复

在这里插入图片描述

TCP将每个字节的数据都进行了编号. 即为序列号.
在这里插入图片描述

每一个ACK都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据; 下一次你从哪里开始发.

  • 超时重传机制:在指定时间内没有收到确认回复,则认为数据丢失,对之前的数据进行重传

在这里插入图片描述

  • 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;
  • 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发;

但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了;

在这里插入图片描述

因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉. 这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.

协议字段中的序号/确认序号:进行包序管理,并且实现确认应答机制

确认序号

功能:告诉发送方,这个确认序号以前的数据都已经收到了

如果第一条数据丢失,就算收到第二条,也不会进行回复,因为每一条回复表示的都是前边的数据都已经完全收到了。
这种方式可以避免因为ack确认回复的丢失而导致的重传

对发送方来说收到1025,表示1~1024成功传输;收到2049,表示1~2048成功传输

协议字段中的校验和

校验数据是否一致,不一致则要求对方进行重传

  • 哪一条数据不一致,则回复这条数据的启示序号
  • 比如1~1024的数据不一致,就会回复确认序号1,要求对方重传1起始的数据

面向连接:保证连接建立成功之后再发送数据

滑动窗口(针对确认应答机制的缺点)

刚才我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段. 这样做有一个比较大的缺点, 就是 性能较差 。 尤其是数据往返的时间较长的时候.

注:

  • 在三次握手的时候,通信双方会通过选项数据写上一个信息:MSS-最大数据段大小(应用层的原始数据大小);
  • tcp发送数据的时候,会从发送缓冲区中取出不大于MSS大小的数据封装tcp头部进行发送

在这里插入图片描述

既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了).

在这里插入图片描述

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口大小就是4000个字节(四个段).
  • 发送前四个段的时候, 不需要等待任何ACK, 直接发送;
  • 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推;
  • 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉;
  • 窗口越大, 则网络的吞吐率就越高;

注:

  • 窗口大小通常不大于接收方接收缓冲区中剩余空间大小
  • 窗口前沿的序号减去窗口后延的序号,大小不能大于对方发送的窗口大小

在这里插入图片描述

那么如果出现了丢包, 如何进行重传? 这里分两种情况讨论.

  • 情况一: 数据包已经抵达, ACK被丢了.

在这里插入图片描述

这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认;

  • 情况二: 数据包就直接丢了.

在这里插入图片描述

  • 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 “我想要的是 1001” 一样;
  • 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
  • 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中;

这种机制被称为 “高速重发控制”(也叫 “快重传”).

发送窗口: 前沿减去后沿就是接收方告知的窗口大小

  • 后沿:发送数据的起始序号 — 后沿的移动取决于是否收到了确认回复
  • 前沿:能够发送的数据的结束序号 — 前沿的移动取决于接收方告知的窗口大小

接收窗口: 前沿减去后沿不大于接收缓冲区中剩余空间大小

  • 后沿:接收数据的起始序号 — 后沿移动取决于是否收到起始序号的数据
  • 前沿:接收数据的结束序号 — 前沿移动取决于缓冲区中剩余空间大小(改变:数据被取出/缓冲区重新调整大小)

通过滑动窗口机制就可以实现数据的连续发送以及流量控制,避免发送数据过多而导致的丢包

重传这里存在三种协议:停等协议 / 选择重传协议 / 回退n步协议

  • 停等协议:收到一条回复才会发送第二条数据 – 适用于网络状况特别差的场景
  • 选择重传协议:哪条丢了,就重传哪条
  • 回退n步协议:从丢失的数据开始,往后的数据都需要重传一遍

拥塞机制

在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就要变坏,这种情况就叫做网络拥塞。

若出现拥塞而不进行控制,整个网络的吞吐量将随输入负荷的增大而下降。

在这里插入图片描述

TCP的四种拥塞控制算法实现原理:

慢开始,拥塞避免,快重传,快恢复
在这里插入图片描述

  • 慢开始

1.慢开始不是指cwnd的增长速度慢(指数增长),而是指TCP开始发送设置cwnd=1。
2.滑动窗口实现了一次连续发送多条数据,一开始在网络状况不明的情况下,有可能会造成因为网络状况不好导致发的越多,丢的越多
因此应该在发送数据时,进行网络探测,查看网络是否能够支持数据的连续快速传输

3.为了防止cwnd增长过大引起网络拥塞,设置一个慢开始门限(ssthresh状态变量)

  当cnwd<ssthresh,使用慢开始算法

  当cnwd==ssthresh,既可使用慢开始算法,也可以使用拥塞避免算法

  当cnwd>ssthresh,使用拥塞避免算法

  • 拥塞避免(按线性规律增长)

拥塞避免并非完全能够避免拥塞,是说在拥塞避免阶段将拥塞窗口控制为按线性规律增长,使网络比较不容易出现拥塞。
思路:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞控制窗口加一。

无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。

加法增大与乘法减小:

乘法减小:无论是慢开始阶段还是拥塞避免,只要出现了网络拥塞(超时),就把慢开始门限值ssthresh减半
加法增大:执行拥塞避免算法后,拥塞窗口线性缓慢增大,防止网络过早出现拥塞
  • 快重传

快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器到期

  • 快恢复(与快重传配合使用)

1.采用快恢复算法时(慢开始只在TCP连接建立时和网络出现超时时才使用)
2.当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半。但是接下去并不执行慢开始算法。
3.考虑到如果网络出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。所以此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。

拥塞控制与流量控制的区别
  • 拥塞控制是防止过多的数据注入到网络中,可以使网络中的路由器或链路不致过载,是一个全局性的过程。
  • 流量控制是点对点通信量的控制,是一个端到端的问题,主要就是抑制发送端发送数据的速率,以便接收端来得及接收。

MTU 和 MSS 区别

  • MTU(Maximum Transmit Unit): 最大传输单元,即物理接口(数据链路层)提供给其上层(通常是IP层)最大一次传输数据的大小

以普遍使用的以太网接口为例,缺省MTU=1500 Byte,这是以太网接口对IP层的约束,如果IP层有<=1500 byte 需要发送,只需要一个IP包就可以完成发送任务;如果IP层有> 1500 byte 数据需要发送,需要分片才能完成发送,这些分片有一个共同点,即IP Header ID相同。

  • MSS(Maximum Segment Size ):TCP提交给IP层最大分段大小,不包含TCP Header和 TCP Option,只包含TCP Payload ,MSS是TCP用来限制application层最大的发送字节数。

如果底层物理接口MTU= 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果application 有2000 byte发送,需要两个segment才可以完成发送,第一个TCP segment = 1460,第二个TCP segment = 540。

  • 为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。

字节流传输(粘包)

数据可以在缓冲区中进行堆积,取出比较灵活,以字节为单位进行存放 / 取出 — 传输比较灵活

  • 发送缓冲区中有很多数据,tcp就会根据MSS取出合适大小的数据进行传输
  • 接收缓冲区中有很多数据,tcp也可以灵活的以用户需要的大小向上交付

但是这种数据在缓冲区中堆积有一个缺陷:tcp粘包 — 将多条数据当作一条数据进行处理

  • 粘包的本质原因:tcp在传输层对数据边界不敏感(不管上层数据是否什么样的,每次都根据mss取出合适大小进行发送 / 不管接收缓冲区中是什么数据,只管从缓冲区中取出用户需要的大小的数据进行交付),因此造成粘包问题
  • 粘包的解决方案:程序员在应用层进行数据的边界管理
    1. 特殊字符间隔 — 每条数据结尾有个特殊字符,数据中的恶特殊字符就需要进行转义
    2. 数据定长 — 每次只发送 / 接收指定长度的数据,数据短了会造成资源浪费
    3. 在定长的应用头中定义数据长度字段 — 先按照指定长度将头部获取,根据头部中的长度,取出指定长度数据

比较典型的:http 先用特殊字符间隔头部,头部中定义正文长度;udp 头部定长,在头部定义数据长度

TCP的长连接与短连接

  • TCP短连接

我们模拟一下TCP短连接的情况,client向server发起连接请求,server接到请求,然后双方建立连接。client向server发送消息,server回应client,然后一次读写就完成了,这时候双方任何一个都可以发起close操作,不过一般都是client先发起close操作。为什么呢,一般的server不会回复完client后立即关闭连接的,当然不排除有特殊的情况。从上面的描述看,短连接一般只会在client/server间传递一次读写操作

优点:

  管理起来比较简单;存在的连接都是有用的连接;不需要额外的控制手段

  • TCP长连接

接下来我们再模拟一下长连接的情况,client向server发起连接,server接受client连接,双方建立连接。Client与server完成一次读写之后,它们之间的连接并不会主动关闭,后续的读写操作会继续使用这个连接。

在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。

长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。

保活机制

  • tcp是面向连接通信,若通信双方长时间没有数据往来,就需要确定对方还是否在线,连接是否正常?
  • 若通信双方长时间(840秒)没有数据往来,在服务端会像客户端每隔一段时间(20秒)发送一个保活探测数据包,要求对方进行响应,若多次(1次)无响应,则认为连接断开。(这些时间可以配置 - 可以通过设置套接字选项 - setsockopt)
  • 连接断开怼上层程序编写的影响:recv返回0,send触发异常

保活功能主要为服务器应用提供,服务器应用希望知道客户主机是否崩溃,从而可以代表客户使用资源。如果客户已经消失,使得服务器上保留一个半开放的连接,而服务器又在等待来自客户端的数据,则服务器将应远等待客户端的数据,保活功能就是试图在服务器端检测到这种半开放的连接。

如果一个给定的连接在两小时内没有任何的动作,则服务器就向客户发一个探测报文段,客户主机必须处于以下4个状态之一:

客户主机依然正常运行,并从服务器可达。客户的TCP响应正常,而服务器也知道对方是正常的,服务器在两小时后将保活定时器复位。
客户主机已经崩溃,并且关闭或者正在重新启动。在任何一种情况下,客户的TCP都没有响应。服务端将不能收到对探测的响应,并在75秒后超时。服务器总共发送10个这样的探测 ,每个间隔75秒。如果服务器没有收到一个响应,它就认为客户主机已经关闭并终止连接。
客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应,这个响应是一个复位,使得服务器终止这个连接。
客户机正常运行,但是服务器不可达,这种情况与2类似,TCP能发现的就是没有收到探查的响应。
从上面可以看出,TCP保活功能主要为探测长连接的存活状况,不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。

TCP、UDP 的区别以及应用场景:

区别:

  • 面向连接VS无连接

TCP建立一个连接需要3次握手IP数据包,断开连接需要4次握手。另外断开连接时发起方可能进入TIME_WAIT状态长达数分钟(视系统设置,windows一般为120秒),在此状态下连接(端口)无法被释放。

UDP不需要建立连接,可以直接发起。

  • 可靠VS不可靠

TCP利用握手、ACK和重传机制,udp没有。

1,校验和(校验数据是否损坏);
2,定时器(分组丢失则重传);
3,序列号(用于检测丢失的分组和重复的分组);
4,确认应答ACK(接收方告知发送方正确接收分组以及期望的下一个分组);
5,否定确认(接收方通知发送方未被正确接收的分组);
6,窗口和流水线(用于增加信道的吞吐量)。(窗口大小:无需等待确认应答而可以继续发送数据的最大值)

  • 有序性

TCP利用seq序列号对包进行排序,udp没有。

  • 面向字节流vs面向报文

面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。(不限制上层发送/接收的数据大小)

面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。(一个upd的最大报文长度2^16-1-20-8,20是ip报文头,8是udp报文头)

  • TCP有流量控制,UDP没有

TCP 会告诉对端它能接收多少字节的数据,称作“通知窗口”,该窗口任何时刻都指出接收缓冲区中的可用空间,从而确保发送端发送的数据不会溢出接收缓冲区

  • TCP有拥塞控制,UDP没有

UDP始终以恒定的速率发送数据,并不会根据网络拥塞情况对发送速率作调整。这种方式有利有弊。弊端:网络拥塞时有些报文可能会丢失,因此UDP不可靠。优点:有些使用场景允许报文丢失,如:直播、语音通话,但对实时性要求很高,此时UDP还是很有用武之地的。

  • 支持一对一,一对多,多对一,多对多的交互通信(单播、多播、广播),而TCP只支持一对一通信

  • tcp的首部最少20byte,最大60字节;udp首部仅8字节

应用场景

  • TCP效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

  • UDP效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。

面试系列

  1. 握手为什么是3次? 两次不安全,四次没必要

tcp是双向通信,必须确保双方都具有数据收发的能力,因此都要给对方发送SYN请求。(这是为了防止客户端发送链接请求之后,直接退出了 / 防止延迟的SYN)

  1. 若三次握手失败了,服务端如何处理的?
  • 客户端发送的SYN直接就没有到达服务端 - 服务端不知道有这个请求,因此没什么处理的
  • 客户端发送了SYN后服务端收到并进行了SYN + ACK响应,但是没有收到ACK。服务端等待最后一个ACK超时后,则会给客户端发送RST重置连接报文要求对方重新发起连接请求,释放当前的socket套接字;而并非重传SYN+ACK。
  1. 挥手为什么是4次?

发送FIN,只是表示不在给对方发送数据,但是不代表不再接收数据,被动关闭方依然还有可能要发送数据,主动关闭方还有可能会继续接收数据。也正是因为如此,因此被动关闭方不能将ACK不能和 FIN一起发送(因为中间依然有可能有数据通信)。

  1. 主动关闭方的TIME_WAIT状态有什么用?
  • 假如没有这个TIME_WAIT状态,主动关闭方直接释放socket,意味着端口和地址可以立即使用。
    若最后一次主动关闭方发送ACK丢失,被动关闭方等待最后一个ACK超时后就会重传FIN包,此时假如主动关闭方立即使用了刚才套接字的端口和地址信息重新启动程序

    • 新的程序使用原先的地址信息进行通信,有可能会收到对方重传的FIN包

    • 新的程序使用原先的地址信息连接相同的服务器,而这时候这个服务器有可能处于LASK_ACK,收到SYN则会认为状态错误,向客户端发送RST重置连接报文,要求对方重新建立连接。

这些都属于上一次的连接没有完全清理完毕,导致对新的连接造成影响

因此主动关闭方必须等待一段时间:2个MSL — 2个报文的最大生存周期
能够处理被动关闭方有可能重传的FIN包,并且等待本次通信的所有数据都消失在网络中,避免对后续连接造成影响

  • Windows : MSL = 2 min
  • linux(Ubuntu, CentOs) : MSL = 60s
  • Unix : MSL = 30s
  1. 为什么是TIME_WAIT的时间是2MSL?
  • MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话
  • 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的);
  • 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK);
  1. TIME_WAIT状态是用来保护谁的?

服务端就算重启,也必须使用与以前相同的地址信息;(没有time_wait更好,否则重启还需要等待一段时间)
time_wait其实主要是用来保护客户端的

  1. 一个主机上出现了大量的TIME_WAIT是什么原因?

TIME_WAIT是主动关闭方发送最后一个ACK之后的状态,意味着服务器上大量主动的关闭了连接。常出现于爬虫服务器
可以开启地址重用 / 设置MSL时间(将MSL时间设置短一点)

  1. 一个主机上出现了大量的CLOSE_WAIT是什么原因?

代码有问题,连接断开后,没有关闭套接字释放资源(CLOSE_WAIT状态是被动关闭方接收到FIN进行回复之后的状态,等待用户确认关闭套接字不在给对方发送信息)

  1. tcp粘包 / udp粘包 ?
  • tcp有可能产生粘包
  • udp根本不会产生粘包(udp数据是整条收发的)
  1. TCP连接建立的三次握手过程可以携带数据吗?

FC793文档里说明:带有SYN标志的过程包是不可以携带数据的,也就是说三次握手的前两次是不可以携带数据的。

但是第三次握手的ACK包是允许携带数据。

  • 假如第一次握手可以携带数据的话,那对于服务器太危险了,有人如果恶意攻击服务器,每次都在第一次握手中的SYN报文中放入大量数据。而且频繁重复发SYN报文,服务器会花费很多的时间和内存空间去接收这些报文。

  • 第三次握手时,此时客户端已经处于ESTABLISHED状态。对于客户端来说,他已经建立起连接了,并且已经知道服务器的接收和发送能力是正常的。所以也就可以携带数据了。

知识点习题

  1. UDP报头中没有下面那些信息?()

A. 目的地址
B. 窗口大小
C. 序列号
D. 检验和

正确答案:A,B,C

  1. TCP链接中主动断开链接netstat观察可能出现的状态流转是:

A. ESTABLISHED->CLOSE_WAIT->TIME_WAIT->CLOSED
B. ESTABLISHED->TIME_WAIT->CLOSE_WAIT->CLOSED
C. ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
D. ESTABLISHED->FIN_WAIT_1->TIME_WAIT->CLOSED

正确答案:C,D

答案解析:

同一时间关闭读写端就可以了(1和2分别等待写端和读端关闭),故FIN-WAIT-2状态跳过

  1. TCP的握手与分手,可能出现的情形有()。

A. 握手需要3次通信
B. 分手需要进行4次通信
C. FIN和ACK 在同一包里
D. ACK 和SYN 在同一包里

正确答案:A,B,C,D

答案解析
关于C,FIN和ACK在同一个包里:

是因为在四次挥手时,TCP/IP协议特有的延时应答和捎带应答特性机制可能导致FIN和ACK在同一个包里。

延时应答保证了数据的传输最大化,捎带应答是在延时应答的基础上实现的。

因此,有些情况下ACK在发送时由于延时应答机制不会立刻发送,而会等待一段时间,且在这段时间内FIN包刚好准备就绪,系统就会在发送ACK包的同时捎带的发送FIN包,此时的4次挥手也就变成3次挥手了(FIN_WAI1直接到TIME_WAIT就是同时接收FIN和ACK的结果)

  1. 以下对于 TCP 和 UDP 的描述正确的是

A. TCP 数据传输效率高于 UDP
B. TCP 是面向连接的,UDP 不是面向连接的
C. TCP 运行在IP层之上,UDP 不一定运行在IP层之上
D. UDP 能够保证数据传输的可靠性
E. TCP 和 UDP 程序在同一机器上可以使用相同的端口

正确答案:B,E

答案解析

  • A:tcp有三次握手,所以效率略低于udp
  • C:TCP 不一定运行在 IP 层之上,通用路由封装(GRE)定义了在任意一种网络层协议上封装任意一个其它网络层协议的协议
  • D:UDP是面向无连接的,所以不可以提供数据传输的可靠性
  • E:TCP和UDP传输协议监听同一个端口后,接收数据互不影响,不冲突。因为数据接收时时根据五元组{传输协议,源IP,目的IP,源端口,目的端口}判断接受者的。
  1. 下列关于TCP的说法是错误的

A. 快恢复是为了提高传输效率
B. 慢启动是指TCP启动慢
C. 拥塞控制是通过滑动窗口实现的
D. 快重传是指 TCP 收到乱序报文,应当立即回复ACK

正确答案: C

答案解析:

开始传输数据时候,接收端的接受缓冲区很大,因此接受窗口比较大,发送方一次发送的数据比较多。但是不确定网络状况是否良好,发送方不能贸然发送大量数据。

因此,TCP采取 慢启动 策略。先发送很少量的数据去探测网络状况,根据网络状况调整发送窗口大小。

快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等到自己发送数据时捎带确认。快重传规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器到期

拥塞控制有自己的拥塞窗口,虽然也跟滑动窗口有关,但是谈不上通过滑动窗口实现


如有幸帮助到您,请留个赞,激励博主呐~~

相关推荐
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页