1. 计算机网络体系结构分层

image.png

TCP/IP模型是互联网的基础,它是一系列网络协议的总称。这些协议可以划分为四层,分别为:

  • 链路层:负责封装和解封装IP报文,发送和接受ARP/RARP报文等。
  • 网络层:负责路由以及把分组报文发送给目标网络或主机。
  • 传输层:负责对报文进行分组和重组,并以TCPUDP协议格式封装报文。
  • 应用层:负责向用户提供应用程序,比如HTTPFTPTelnetDNSSMTP等。

image.png

不难看出,**TCP/IP****OSI**在分层模块上稍有区别。**OSI**参考模型注重“通信协议必要的功能是什么”,而**TCP/IP**则更强调“在计算机上实现协议应该开发哪种程序”。

2. TCP/IP的含义

从字面意义上讲,有人可能会认为TCP/IP是指TCPIP两种协议。实际生活当中有时也确实就是指这两种协议。然而在很多情况下,它只是利用IP进行通信时所必须用到的协议群的统称。具体来说,IPICMPTCPUDPTELNETFTP以及HTTP 等都属于TCP/IP协议。他们与TCPIP的关系紧密,是互联网必不可少的组成部分。TCP/IP一词泛指这些协议,因此有时也称 **TCP/IP**为网际协议群

image.png

**TCP**(传输控制协议)和**IP**(网际协议)是最先定义的两个核心协议,所以才统称为**TCP/IP**协议族

3. TCP 包头

TCPTransmission Control Protocol传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。当应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,TCP把数据流分割成适当长度的报文段,最大传输段大小(MSS)通常受该计算机连接的网络的数据链路层的最大传送单元(MTU)限制。之后**TCP**把数据包传给**IP**层,由它来通过网络将包传送给接收端实体的**TCP**

下图为TCP头部结构:

image.png

这里我们就介绍常用的几个字节:

  • Sequence number(seq)顺序号4个字节,用来标识从TCP源端向TCP目的端发送的数据字节流,它表示在这个报文段中的第一个数据字节的顺序号。如果将字节流看作在两个应用程序间的单向流动,则TCP用顺序号对每个字节进行计数。序号是32bit的无符号数,序号到达2^32 - 1后又从0开始。
  • Acknowledgement number(ack)确认号4个字节,包含发送确认的一端所期望收到的下一个顺序号。因此,确认序号应当是上次已成功收到数据字节顺序号加1只有**ACK**标志为**1** 时确认序号字段才有效
  • Control Flags(6位)控制位,其中:
    • ACK:为1表示确认号有效,为0表示报文中不包含确认信息,忽略确认号字段。
    • SYN:为1表示连接请求。用于建立连接和使顺序号同步(synchronize)。用于请求连接。
    • FIN:为1表示发送方已经没有数据发送了,即关闭本方数据流。用于释放连接。

4. TCP 三次握手(重点)

  • TCP提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作
  • 所谓三次握手是指建立一个**TCP**连接时需要客户端和服务器端总共发送三个包以确认连接的建立
  • 浏览器获得域名对应的IP地址后,浏览器与远程web服务器通过TCP三次握手协商来建立一个TCP/IP连接。

下面来看看三次握手的流程图:

image.png

  1. 第一次握手:客户端发送一个标志位为SYN=1、顺序号为seq=J的数据包给服务端,告知服务端:“我要跟你建立连接!你能收到我的请求吗?”。客户端进入SYN_SENT状态,等待服务端确认。
  2. 第二次握手:服务端收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端发送一个标志位SYNACK都为1、确认号为ack=J+1、顺序号为seq=K的数据包发送给客户端,告知客户端:“我收到你的请求了,你能收到我的请求吗?”服务端进入SYN_RCVD状态。
  3. 第三次握手:客户端收到确认后,检查确认号ack是否为J+1,标志位ACK是否为1,如果正确则发送一个标志位为ACK=1、顺序号为ack=K+1的数据包给服务端,告知服务端:“我也能收到!”服务端检查顺序号ack是否为K+1,标志位ACK是否为1,如果正确则连接建立成功,客户端和服务端进入ESTABLISHED状态,完成三次握手,随后客户端与服务端之间可以开始传输数据了。

syn_sent: syn package has been sent syn_rcvd: syn package has been received

5. TCP 四次挥手(重点)

  • 四次挥手即终止TCP连接,就是指断开一个**TCP**连接时,需要客户端和服务端总共发送**4**个包以确认连接的断开
  • 由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭

下面来看看四次挥手的流程图:

image.png

中断连接端可以是客户端,也可以是服务器端。

  1. 第一次挥手:客户端发送一个标志位FIN=1,顺序号为seq=u的数据包,用来关闭客户端到服务器端的数据传送,客户端进入FIN_WAIT_1状态。意思是说:“我客户端没有数据要发给你了,”但是如果你服务器端还有数据没有发送完成,则不必急着关闭连接,可以继续发送数据。
  2. 第二次挥手:服务器端收到数据包后由标志位FIN=1知道客户端要释放连接,服务器端将标志位ACK都置为1,确认号ack=u+1,随机产生一个顺序号seq=v,并将该数据包发送给客户端,告诉客户端:“你的请求我收到了,但是我还没准备好,请你继续等我的消息。”客户端就进入FIN_WAIT_2状态继续等待服务器端的FIN报文。服务器端进入SYN_RCVD状态。
  3. 第三次挥手:当服务器端确定数据已发送完成,则向客户端发送标志位FIN=1ACK=1,确认号为ack=u+1,自己的顺序号为seq=w的报文,告诉客户端:“好了,我这边数据发完了,准备好关闭连接了。”服务器端进入LAST_ACK状态。
  4. 第四次挥手:客户端收到FIN=1的报文后,并检查确认号ack是否为u+1,标志位ACK是否为1,如果正确,客户端就知道可以关闭连接了。这个时候客户端再发送一个标志位为ACK=1、顺序号为seq=u+1、确认号为ack=w+1的数据包给服务端,告诉服务端:“你关闭连接吧,等你关闭了,我也关闭。”客户端进入TIME_WAIT状态。服务器端验证报文后,就知道可以断开连接了,立刻进入CLOSED状态。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,那好,我客户端也可以关闭连接了。最终完成了四次挥手。

上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况。

6. TCP/IP 通信传输流

image.png

TCP/IP通过分层管理进行网络通信,发送端从应用层往下走,接收端则往应用层上层走。然后便一层层包裹,解析。

image.png

  • 发送端,每经过一层会打上该层所属的首部信息
  • 接收端,每经过一层会把对应的首部信息解析

7. TCP 和 UDP

在网络体系结构中网络通信的建立必须是在通信双方的对等层进行,不能交错。在整个数据传输过程中,数据在发送端时经过各层时都要附加上相应层的协议头和协议尾(仅数据链路层需要封装协议尾)部分,也就是要对数据进行协议封装,以标识对应层所用的通信协议。TCP/IP中有两个具有代表性的传输层协议——TCPUDP

7.1 区别

  • 是否面向连接

面向连接,是指发送数据之前必须在两端建立连接。TCP需要经过三次握手才能建立连接。UDP则不需要,想发数据就可以发送。

  • 是否可靠

可靠性体现在是否建立连接上,如果通信都不需要建立连接,想发就发,这样的情况肯定不可靠。并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。所以TCP是可靠的,UDP是不可靠的。

  • 连接对象个数

每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。而UDP不止支持一对一的传输方式,同样支持一对多、多对多、多对一的方式,也就是说UDP提供了单播,多播,广播的功能。

  • 传输方式

UDP面向报文的,只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作,在添加首部后就向下交付IP层。因此,应用程序必须选择合适大小的报文。TCP则不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。

  • 传输效率

UDP的头部开销小,只有8字节,相比TCP的至少20字节(最大60字节)要少得多,并且UDP传输不像TCP传输要建立连接,在传输数据报文时UDP是很高效的。

  • 适用场景

UDP适用于一些实时应用(IP电话、视频会议、直播等)。TCP适用于要求可靠传输的应用,例如文件传输。

总结:

  • TCP向上层提供面向连接的可靠服务,UDP向上层提供无连接不可靠服务。
  • 虽然UDP并没有TCP传输来的准确,但是也能在很多实时性要求高的地方有所作为。
  • 对数据准确性要求高,速度可以相对较慢的,可以选用TCP

7.2 运行协议

  • 运行在TCP协议上的协议:
    • HTTP(Hypertext Transfer Protocol):超文本传输协议,主要用于普通浏览。
    • HTTPS(HTTP over SSL):安全超文本传输协议,HTTP协议的安全版本。
    • FTP(File Transfer Protocol):文件传输协议,用于文件传输。
    • POP3(Post Office Protocol, version 3):邮局协议,收邮件用。
    • SMTP(Simple Mail Transfer Protocol):简单邮件传输协议,用来发送电子邮件。
    • TELNET(Teletype over the Network):网络电传,通过一个终端(terminal)登陆到网络。
    • SSH(Secure Shell):用于替代安全性差的TELNET,用于加密安全登陆用。
  • 运行在UDP协议上的协议:
    • BOOTP(Boot Protocol):启动协议,应用于无盘设备。
    • NTP(Network Time Protocol):网络时间协议,用于网络同步。
    • DHCP(Dynamic Host Configuration Protocol):动态主机配置协议,动态配置IP地址。
  • 运行在TCPUDP协议上:
    • DNS(Domain Name Service):域名系统,用于完成地址查找,邮件转发等工作。

8. 常见的面试题

8.1 为什么 TCP 连接的时候是三次握手,关闭的时候却是四次挥手?

因为当服务端收到客户端的SYN报文的连接请求后,它可以把ACKSYNACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但是关闭连接时,当服务端收到FIN报文时,它仅仅表示对方没有数据发送给你了,可是服务端所有的报文还没发送完,所以只能先回复一个ACK报文,告诉Client端:“你发的FIN报文我收到了。”等服务端报文都发送完了,再发送FIN报文给对方来表示你同意可以关闭连接了。故需要四步握手。

image.png

8.2 为什么 TIME_WAIT 状态需要经过2MSL(最大报文段生存时间)才能返回到 CLOSED 状态?

由于网络是不可靠的,客户端发送出最后的**ACK**回复,该**ACK**可能丢失。服务端如果没有收到**ACK**,将不断重复发送**FIN**片段。所以客户端不能立即关闭,它必须确认客户端接收到了该ACK。客户端会在发送出ACK之后进入到TIME_WAIT状态。

客户端会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么客户端会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSLMaximum Segment Lifetime)。MSL一个片段在网络中最大的存活时间,**2MSL**就是一个发送和一个回复所需的最大时间。如果直到2MSL,客户端都没有再次收到FIN的报文,那么客户端推断ACK已经被成功接收,则结束TCP连接。

8.3 为什么 TCP 不能用两次握手进行连接?

弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。

  • 第一次握手:客户端发包,服务端收到了。

这样服务端就能得出结论:客户端的发送能力,服务端自己的接收能力是正常的。

  • 第二次握手:服务端发包,客户端收到了。

这样客户端就能得出结论:服务端的接收、发送能力正常,客户端自己的发送、接收能力正常。不过此时,服务端并不能确认客户端的接收能力是否正常。

  • 第三次握手:客户端发包,服务端收到了。

这样服务端就能得出结论:客户端的接收、发送能力正常,服务端自己的接收、发送能力也正常。

因此,需要三次握手才能确认双方的接收与发送能力是否正常

8.4 如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。

服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为两小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔**75**秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接

8.5 什么是半连接队列?

服务器第一次收到客户端发送的标志位为**SYN=1**的包之后,就会处于**SYN_RCVD**状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列

当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

8.6 SYN (洪范)攻击是什么?

首先补充一点关于SYN-ACK重传次数的问题:

服务器发送完**SYN-ACK**包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除

目前,Linux下默认会进行5次重发SYN-ACK包,重试的间隔时间从1s开始,下次的重试间隔时间是前一次的双倍,5次的重试时间间隔为1s2s4s8s16s,总共31s,第5次发出后还要等32s都知道第5次也超时了。

所以,总共需要1s + 2s + 4s+ 8s+ 16s + 32s = 63sTCP才会断开这个连接。

由于,SYN超时需要63秒,那么就给攻击者一个攻击服务器的机会。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此**Server**需要不断重发直至超时,这些伪造的**SYN**包将长时间占用半连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN攻击是一种典型的DoS/DDoS攻击。

检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在Linux/Unix上可以使用系统自带的netstats命令来检测SYN攻击。

  1. netstat -n -p TCP | grep SYN_RECV

常见的防御SYN攻击的方法有如下几种:

  • 缩短超时(SYN Timeout)时间
  • 增加最大半连接数
  • 过滤网关防护
  • SYN cookies技术