#1. 网络结构
1.1 计算机网络结构?
计算机网络一共有3种模型。
- OSI七层结构
- TCP/IP结构
- 五层协议结构

OSI是Open Systems Interconnect,也就是开放的互联系统,将复杂的互联网系统划分为不同块,方便处理。
实际应用中,并没有采用这个理论模型,而是使用TCP/IP协议的四层模型。
而5层模型是一个理论上的网络通信模型,方便教学的时候理解,实际上并不存在。
1.2 计算机网络中各层作用

(1)应用层
应用层的任务是通过应用进程间的交互来完成特定网络应用,访问OSI环境的手段,应用层协议定义的是应用进程(进程:主机中正在运行的程序)间的通信和交互的规则。
常见的协议有域名系统DNS,万维网应用的HTTP协议,支持电子邮件的SMTP协议。
应用层是不用去关心数据是如何传输的,就类似于,我们寄快递的时候,只需要把包裹交给快递员,由他负责运输快递,我们不需要关心快递是如何被运输的。
把应用层交互的数据单元称为报文。
(2)运输层
为两台主机进程之间的通信提供**通用的数据传输服务,端对端的可靠报文传递和错误恢复。**主要包含两种协议:
-
传输控制协议 TCP(Transmisson Control Protocol)。
面向连接 面向字节流 可靠 传输慢 流量控制阻塞控制 1v1
**面向连接(三次握手四次挥手), 面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),可靠(握手、ACK和重传机制),传输慢,**有流量控制阻塞控制。
-
用户数据报协议 UDP(User Datagram Protocol)。
无连接,面向报文,不可靠尽最大可能交付,传输快,没有流量控制和拥塞控制,可1vn to nv1
无连接的,尽最大可能交付,不可靠,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部) ,支持一对一、一对多、多对一和多对多的交互通信,传输快,没有流量控制拥塞控制。
当然,UDP 也可以实现可靠传输,把 TCP 的特性在应用层上实现就可以,不过要实现一个商用的可靠 UDP 传输协议,也不是一件简单的事情。
应用需要传输的数据可能会非常大,如果直接传输就不好控制,因此当传输层的数据包大小超过 MSS(TCP 最大报文段长度) ,就要将数据包分块,这样即使中途有一个分块丢失或损坏了,只需要重新发送这一个分块,而不用重新发送整个数据包。
在 TCP 协议中,我们把每个分块称为一个 TCP 段(TCP Segment)。
当设备作为接收方时,传输层则要负责把数据包传给应用,但是一台设备上可能会有很多应用在接收或者传输数据,因此需要用一个编号将应用区分开来,这个编号就是端口。
比如 80 端口通常是 Web 服务器用的,22 端口通常是远程登录服务器用的。
而对于浏览器(客户端)中的每个标签栏都是一个独立的进程,操作系统会为这些进程分配临时的端口号。由于传输层的报文中会携带端口号,因此接收方可以识别出该报文是发送给哪个应用。
(3)网络层
网络层的任务就是选择合适的网间路由和交换结点,确保数据及时传送,数据包传递与网际互连。
网络层最常使用的是 IP 协议(Internet Protocol),IP 协议会将传输层的报文作为数据部分,再加上 IP 包头组装成 IP 报文,如果 IP 报文大小超过 MTU(最大传输单元,Maximum Transmission Unit)以太网中一般为 1500 字节)就会再次进行分片,得到一个即将发送到网络的 IP 报文。
使用IP协议,ARP协议,IP协议,ICMP协议,IGMP协议等。
网络层有两个任务:
- 把运输层产生的报文段或用户数据报 封装成分组和包进行传送。在 TCP/IP 体系结构中,由于网络层使用 IP 协议,因此分组也叫 IP 数据报 ,简称 数据报。
- 注意:不要把运输层的用户数据报UDP和网络层的IP数据报弄混。
- 选择合适的路由,找到目的主机。
网络层负责将数据从一个设备传输到另一个设备,世界上那么多设备,又该如何找到对方呢?因此,网络层需要有区分设备的编号。
我们一般用 IP 地址给设备进行编号,对于 IPv4 协议, IP 地址共 32 位,分成了四段(比如,192.168.100.1),每段是 8 位。只有一个单纯的 IP 地址虽然做到了区分设备,但是寻址起来就特别麻烦,全世界那么多台设备,难道一个一个去匹配?这显然不科学。
因此,需要将 IP 地址分成两种意义:
- 一个是网络号,负责标识该 IP 地址是属于哪个「子网」的;
- 一个是主机号,负责标识同一「子网」下的不同主机;
(4)数据链路层
数据链路层的任务是确保在直接相连的两个节点之间可靠地传输数据,并处理与物理层交互和链路管理相关的事务
两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层的协议。
在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP 数据报组装程帧,在两个相邻节点间的链路上传送帧。
主要协议:1、Point-to-Point Protocal——PPP点到点。2、Ethernet——以太网。3、High-Level Data Link Control Protocal——高级链路控制协议。4、Frame Relay——帧中继。5、Asynchronous Transfer Mode——异步传输模式。
每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)。
(5)物理层
物理层的任务就是透明地传输比特流,尽可能屏蔽掉具体传输介质和物理设备的差异,确定电气规范,使其上面的数据链路层不必考虑网络的具体传输介质是什么。换句话说实际电路传送后比特流没有发生变化。
Tips:
网络接口层
生成了 IP 头部之后,接下来要交给**网络接口层(Link Layer)在 IP 头部的前面加上 MAC 头部,**并封装成数据帧(Data frame)发送到网络上。
主要为网络层提供「链路级别」传输的服务,负责在以太网、WiFi 这样的底层网络上发送原始数据包,工作在网卡这个层次,使用 MAC 地址来标识网络上的设备。
2. TCP/IP协议
2.1 TCP/IP协议的结构


-
1、源端口号(Source Port) 16位的源端口字段包含初始化通信的端口号。源端口和IP地址的作用是标识报文的返回地址。
2、目的端口号(Destination Port) 16位的目的端口字段定义传输的目的。这个端口指明接收方计算机上的应用程序接口。
3、序列号(Sequence Number) 该字段用来标识TCP源端设备向目的端设备发送的字节流,它表示在这个报文段中的第几个数据字节。序列号是一个32位的数。
4、确认号(Acknowledge Number) TCP使用32位的确认号字段标识期望收到的下一个段的第一个字节,并声明此前的所有数据已经正确无误地收到,因此,确认号应该是上次已成功收到的数据字节序列号加1。收到确认号的源计算机会知道特定的段已经被收到。确认号的字段只在ACK标志被设置时才有效。 5、首部长度 长度为4位,用于表示TCP报文首部的长度。用4位(bit)表示,十进制值就是[0,15],一个TCP报文前20个字节是必有的,后40个字节根据情况可能有可能没有。如果TCP报文首部是20个字节,则该位应是20/4=5。 6、保留位(Reserved) 长度为6位,必须是0,它是为将来定义新用途保留的。 7、标志(Code Bits) 长度为6位,在TCP报文中不管是握手还是挥手还是传数据等,这6位标志都很重要。6位从左到右依次为: 1. • URG:紧急标志位,说明紧急指针有效; • ACK:确认标志位,多数情况下空,说明确认序号有效; 取1时表示应答字段有效,也即TCP应答号将包含在TCP段中,为0则反之。 • PSH:推标志位,置位时表示接收方应立即请求将报文交给应用层; • RST:复位标志,用于重建一个已经混乱的连接,用来复位产生错误的连接,也会用来拒绝错误和非法的数据包。 • SYN:同步标志,该标志仅在三次握手建立TCP连接时有效 • FIN:结束标志,表示发送端已经发送到数据末尾,数据传送完成,发送FIN标志位的TCP段,连接将被断开。 8、窗口大小(Window Size) 长度为16位,TCP流量控制由连接的每一端通过声明的窗口大小来提供。 9、检验和(Checksum) 长度为16位,该字段覆盖整个TCP报文端,是个强制性的字段,是由发送端计算和存储,到接收端后,由接收端进行验证。 10、紧急指针(Urgent Pointer) 长度为16位,指向数据中优先部分的最后一个字节,通知接收方紧急数据的长度,该字段在URG标志置位时有效。 11、选项(Options) 长度为0-40B(字节),必须以4B为单位变化,必要时可以填充0。通常包含:最长报文大小(MaximumSegment Size,MSS)、窗口扩大选项、时间戳选项、选择性确认(Selective ACKnowlegement,SACK)等。 12、数据 可选报文段数据部分。
-
- 首先,源端口号和目标端口号是不可少的,如果没有这两个端口号,数据就不知道应该发给哪个应用。
- 接下来有包的序号,这个是为了解决包乱序的问题。
- 还有应该有的是确认号,目的是确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,这个是为了解决不丢包的问题。
- 接下来还有一些状态位flag。例如
SYN是发起一个连接,ACK是回复,RST是重新连接,FIN是结束连接等。 TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。 - 还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。
- 除了做流量控制以外,TCP还会做拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。不能改变世界,就改变自己嘛。
UDP报头

-
每个 UDP 报文分为 UDP 报头和 UDP 数据区两部分。报头由 4 个 16 位长(2 字节)字段组成,分别说明该报文的源端口、目的端口、报文长度和校验值。
UDP 报文中每个字段的含义如下:
- 源端口:这个字段占据 UDP 报文头的前 16 位,通常包含发送数据报的应用程序所使用的 UDP 端口。接收端的应用程序利用这个字段的值作为发送响应的目的地址。这个字段是可选的,所以发送端的应用程序不一定会把自己的端口号写入该字段中。如果不写入端口号,则把这个字段设置为 0。这样,接收端的应用程序就不能发送响应了。
- 目的端口:接收端计算机上 UDP 软件使用的端口,占据 16 位。
- 长度:该字段占据 16 位,表示 UDP 数据报长度**,包含 UDP 报文头和 UDP 数据长度**。因为 UDP 报文头长度是 8 个字节,所以这个值最小为 8。
- 校验值:该字段占据 16 位,可以检验数据在传输过程中是否被损坏。
IP数据报的首部
注:IP数据报的格式,能够说明IP协议都具有什么功能。
-
IP数据报首部——固定部分
-
1.1 版本 占4位,指IP协议的版本。 通信双方使用的IP协议的版本必须一致。 IP协议版本号为4(即IPv4),IP协议版本号为6(即IPv6)。
-
1.2 首部长度 占4位,可表示的最大十进制数值是15。 这个字段所表示数的单位是32位字(即4字节),因此,当IP的首部长度为1111(即十进制的15)时,首部长度就达到最大值60个字节。
最常用的首部长度就是20个字节(即首部长度为0101),这时不使用任何选项,是固定首部的长度。 当IP分组的首部长度不是4字节的整数倍时,必须利用最后的填充字段加以填充。
-
1.3 区分服务 占8位,用来获得更好的服务。
-
1.4 总长度 占16位,指首部和数据之和的长度。 数据报的最大长度为2^16 -1 = 65535字节。“MTU是Maximum Transmission Unit的缩写。意思是网络上传送的最大数据包
1 2 3 4 5 6在IP层下面的每一种数据链路层都有其自己的帧格式,其中包括帧格式中的数据字段的最大长度,这称为最大传送单元MTU(Maximum Transfer Unit)。 当一个IP数据报封装成数据链路层的帧时,此数据报的总长度(即首部加上数据部分),一定不能**超过下面的数据链路层的MTU值。** 虽然使用尽可能长的数据报会使传输效率提高,但由于以太网的普遍应用,所以实际上使用的**数据报长度很少有超过1500字节的。** 为了不使IP数据报的传输效率降低,有关IP的标准文档规定,所有的**主机和路由器必须能够处理的IP数据报的长度不得少于576字节。这个数值也就是最小的IP数据报的总长度。** 当数据报长度超过网络所容许的最大传送单元MTU时,就必须把**过长的数据报进行分片后才能在网络上传送(“片偏移”字段相关)。** 这时,**数据报首部中的总长度不是指未分片前的数据报长度,而是指分片后的每一个分片的首部长度与数据长度的总和。** -
1.5 标识 占16位,指IP软件在存储器中维持一个计数器,每产生一个数据报,计数器就加1,并将此值付给标识字段。
但这个标识并不是序号,因为IP是无连接服务,数据报不存在按序接收的问题。 当数据报由于长度超过网络的MTU而必须分片时,这个标识字段的值就会被复制到所有的数据报片的标识字段中。相同的标识字段的值使分片后的各数据报片最后能正确地重装成为原来的数据报。
-
1.6 标志 占3位,但目前只有两位有意义。 标志字段中的最低位记为MF(More Fragment)。
(1) MF=1即表示后面“还有分片”的数据报。
(2)MF=0即表示这已是若干数据报片中的最后一个。
标志字段中的中间位DF(Don’t Fragment),意思是**“不能分片”**,只有当DF=0时才允许分片。
-
1.7 片偏移 占13位,指出较长的分钟再分片后,某片在原分组中的相对位置。 也就是说,相对于用户数据字段的起点,该片从何处开始。 片偏移以8个字节为偏移单位。
也就是说,每个分片的长度一定是8字节(64位)的整数倍。 例子:一数据报的总长度为3820字节,其数据部分为3800字节长(使用固定首部),需要分片为长度不超过1420字节的数据报片。
因固定首部长度为20字节,因此每个数据报片的数据部分长度不能超过1400字节。于是分成3个数据报片,其数据部分的长度分别分为1400,1400和1000字节。原始数据报首部被复制为各数据报片的首部,但必须修改有关字段的值。
-
1.8 生存时间 占8位,常用的英文缩写是TTL(Time To Live),表明数据报在网络中的寿命。
由发出数据报的源点设置这个字段。 其目的是为了防止无法交付的数据报无限制地在因特网中兜圈子,因而白白浪费网络资源。
随着技术的发展,TTL字段的功能改为“跳数限制”。路由器在转发数据报之前就把TTL值减1。若TTL值减少到零,就丢弃这个数据报,不再转发。
因此,现在TTL的单位不再是秒,而是跳数。
TTL的意义是指明数据报在因特网中至多可经过多少个路由器。显然,数据报能在因特网中经过的路由器的最大数是255。
若把TTL的初始值设置为1,就表示这个数据报只能在本局域网中传送。
-
1.9 协议 占8位,指出此数据报携带的数据是使用何种协议,一遍使目的主机的IP层知道应将数据部分上交给哪个处理过程。 常用的一些协议和相应协议的字段值。
-
1.10 首部检验和 占16位,只检验数据报的首部,但不包括数据部分。
这是因为数据报每经过一个路由器,路由器都要重新计算一下首部检验和(一些字段,如生存时间、标志、片偏移等都可能发生变化)。 不检验数据部分可减少计算的工作量。
为了进一步减少计算检验和的工作量,IP首部的检验和不采用复杂的CRC检验码而是采用以下算法: (1)在发送方,先把IP数据报首部划分为许多16位字的序列,并把检验和字段置零。 (2)用反码算术运算把所有16位字相加后,将得到的和的反码写入检验和字段, (3)接收方收到数据报后,将首部的所有16位字再使用反码算术运算相加一次。将得到的和取反码,即得出接收方检验和的计算结果。若首部未发生任何变化,则此结果必为0,于是就保留这个数据报,否则即认为出错,并将此数据报丢弃。
-
1.11 源地址 占32位。
-
1.12 目的地址 占32位。
IP数据报首部——可变部分
IP首部的可变部分就是一个选项字段。
-
2.1 可选字段(长度可变) 选项字段用来支持排错、测量以及安全等措施,内容很丰富。
此字段的长度可变,从1~40个字节不等,取决于所选择的项目。 某些选项项目只需要1个字节,它只包括1个字节的选项代码。但还有些选项需要多个字节,这些选项一个个拼接起来,中间不需要有分隔符。
-
2.2 填充 最后用全0的填充字段补齐成为4字节的整数倍。 注:增加首部的可变部分是为了增加IP数据报的功能,但这同时也使得IP数据报的首部长度成为可变的。
这就增加了每一个路由器处理数据报的开销。实际上这些选项很少被使用。新的IP版本IPv6就把IP数据报的首部长度做成固定的。
2.2 TCP和UDP的区别
- TCP:面向连接(三次握手四次挥手),可靠(握手、ACK和重传机制),面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),传输慢, 有流量控制阻塞控制。
- UDP:无连接的,尽最大可能交付不可靠,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部) ,支持一对一、一对多、多对一和多对多的交互通信, 传输快,没有流量控制拥塞控制,
解释一下报文和字节流的区别:
- 字节流:**发送次数和接收次数可以不相同。**比如向水池倒了20盆水,可以开水龙头一次性全放出。
- 报文:发送次数和接收次数必须相同。
两者的应用场景:
- TCP:效率要求相对低,但对准确性要求相对高的场景。
- 比如邮件,远程登录,文件传输等对准确性要求较高的地方, 远程控制(SSH),File Transfer Protocol(FTP),邮件(SMTP、IMAP)等,点对点文件传出(微信等)
- UDP:效率要求相对高,传输快速,对准确性要求相对低的场景。
- 比如QQ聊天、在线视频、网络语音电话等响应速度要求高的场景,广播通信(广播、多播)。网络游戏, 音视频传输, DNS, Ping, 直播
**第三类:模糊地带(TCP、UDP 都可以考虑),HTTP(目前以 TCP 为主),**文件传输?
TCP报文段

- 序号 :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401,没有携带数据就是302。
- 确认号ack :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段 的序号为 701,B 发送给 A 的确认报文段中确认号就为 701,没有携带数据就是502。
- 数据偏移 :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
- 确认 ACK :当 ACK=1 时确认号字段ack有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
- 同步 SYN :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
- 终止 FIN :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
- 窗口 :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
MTU:一个网络包的最大长度,以太网中一般为1500字节。MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。
如何查看 TCP 的连接状态?
TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。
2.4 三次握手和四次挥手
https://jiangren.work/2019/08/01/Socket%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E5%8E%9F%E7%90%86/
三次握手
所谓三次握手是指建立一个TCP连接时,需要客户端和服务器发送3个包。

名词解释:
- SYN:Synchronize,同步标志位,为1时表示序列号有效
- ACK:Acknowledgment,确认标志位
- seq:Synchronize Sequence Number,同步序列号
- ack:确认序列号
握手过程:
-
第一次握手:客户端发送SYN标志为1的包,以及同步序列号x,并指明打算连接的服务器端口。此时,connect进入阻塞状态。
客户端会随机初始化序号(
client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把SYN标志位置为1,表示SYN报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT状态。 -
第二次握手:服务器收到后,发送SYN和ACK标志为1的包,同时也发送一个自己的同步序列号y,外加一个确认序列号ack=x+1。此时accept进入阻塞状态。
服务端收到客户端的
SYN报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入client_isn + 1, 接着把SYN和ACK标志位置为1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD状态。 -
第三次握手:客户端收到后,再次发送ACK=1,以及同步序列号seq(x+1)和确认序列号ack(y+1),与此同时,connect返回。当服务器收到ACK=1时,accept返回。
客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部
ACK标志位置为1,其次「确认应答号」字段填入server_isn + 1,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于ESTABLISHED状态。 -
服务器收到客户端的应答报文后,也进入
ESTABLISHED状态。
从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。
四次挥手

- 客户端打算关闭连接,此时会发送一个 TCP 首部
FIN标志位被置为1的报文,也即FIN报文,之后客户端进入FIN_WAIT_1状态。 - 服务端收到该报文后,就向客户端发送
ACK应答报文,接着服务端进入CLOSED_WAIT状态。 - 客户端收到服务端的
ACK应答报文后,之后进入FIN_WAIT_2状态。 - 等待服务端处理完数据后,也向客户端发送
FIN报文,之后服务端进入LAST_ACK状态。 - 客户端收到服务端的
FIN报文后,回一个ACK应答报文,之后进入TIME_WAIT状态 - 服务器收到了
ACK应答报文后,就进入了CLOSED状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL一段时间后,自动进入CLOSED状态,至此客户端也完成连接的关闭
Q1. 为什么不能用两次握手连接
- 三次握手才可以阻止重复历史连接的初始化(主要原因)
- 三次握手才可以同步双方的初始序列号
- 三次握手才可以避免资源浪费
原因一:避免历史连接
-
一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;
-
那么此时服务端就会回一个
SYN + ACK报文给客户端; -
客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送
RST报文给服务端,表示中止这一次连接。主要是因为在两次握手的情况下,「被动发起方」没有中间状态给「主动发起方」来阻止历史连接,导致「被动发起方」可能建立一个历史连接,造成资源浪费。
两次握手的情况下,「被动发起方」在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据给,但是「主动发」起方此时还没有进入 ESTABLISHED 状态,假设这次是历史连接,主动发起方判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,而「被动发起方」在第一次握手的时候就进入 ESTABLISHED 状态,所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST 报文后,才会断开连接
原因二:同步双方初始序列号
TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:
- 接收方可以去除重复的数据;
- 接收方可以根据数据包的序列号按序接收;
- 可以标识发送出去的数据包中, 哪些是已经被对方收到的(通过 ACK 报文中的序列号知道);
四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。
而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。
原因三:避免资源浪费
如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?
如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。这样就会造成死锁。也有可能打开两个连接或更多。
如果握手只是两次,服务器端在没有确定客户端是否对自己做出了正确应答的情况下就建立了连接,此时客户端因为意外连接请求报文早就失效了,也不可能再理服务器端,但是服务器端会一直傻傻地等待客户端会发来点数据,造成了资源的浪费。这真是服务器端自己自作多情啊。
三次握手如果第三次失败了会怎么样:
失败了服务端收到不确认包,会超时重发5次,若还是没有收到确认包,或者收到了数据包,则服务端直接发送reset重置包结束本次连接。
Q2. 为什么连接是三次握手,而关闭时是四次挥手
为什么连接是三次握手:
为了保证服务端能收接受到客户端的信息并能做出正确的应答而进行前两次(第一次和第二次)握手。
为了保证客户端能够接收到服务端的信息并能做出正确的应答而进行后两次(第二次和第三次)握手。
具体原因见Q1:
关闭时四次挥手:
关闭连接时,服务端需要回复两次。
四次挥手之所以结束时需要多一次请求是因为:客户端单方面无数据发送认为可以结束了,但是服务端不一定没有数据发送。
所以服务端要将确信信息和自身发起断开分作两步。
再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。
- 关闭连接时,客户端向服务端发送
FIN时,仅仅表示客户端不再发送数据了但是还能接收数据。 - 服务器收到客户端的
FIN报文时,先回一个ACK应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN报文给客户端来表示同意现在关闭连接。
Q3. 为什么TIME_WAIT状态需要经过2MSL?
2MSL是一次发送和回复的最大时间 (Maximum Segment Lifetime报文最大生存时间)
主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包,客户端最后一次发送ACK可能会丢失,如果此时冒然关闭,会导致服务器没收到ACK,然后一直不断地发Fin。
所以需要等2MSL,如果超过这个时间,都还没有收到服务器的信息,说明已经完成,可以关闭。
MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。
MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。
TTL 的值一般是 64,Linux 将 MSL 设置为 60秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
Q4. 为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
主要原因有两个方面:
- 为了防止历史报文被下一个相同四元组的连接接收(主要方面);
- 为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;
Q5. 初始序列号 ISN 是如何随机产生的?
起始 ISN 是基于时钟的,每 4 微秒 + 1,转一圈要 4.55 个小时。
RFC793 提到初始化序列号 ISN 随机生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。
M是一个计时器,这个计时器每隔 4 微秒加 1。F是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。
可以看到,随机数是会基于时钟计时器递增的,基本不可能会随机成一样的初始化序列号。
Q6. 既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?
MTU:一个网络包的最大长度,以太网中一般为1500字节;MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;
如果在 TCP 的整个报文(头部 + 数据)交给 IP 层进行分片,会有什么异常呢?
当 IP 层有一个超过 MTU 大小的数据(TCP 头部 + TCP 数据)要发送,那么 IP 层就要进行分片,把数据分片成若干片,保证每一个分片都小于 MTU。
把一份 IP 数据报进行分片以后,由目标主机的 IP 层来进行重新组装后,再交给上一层 TCP 传输层。
这看起来井然有序,但这存在隐患的,那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。
因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。
所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。
Q7. 第一次,第二次,第三次握手丢失了,会发生什么?
第一次握手丢失了,会发生什么?
当客户端想和服务端建立 TCP 连接的时候,首先第一个发的就是 SYN 报文,然后进入到 SYN_SENT 状态。在这之后,如果客户端迟迟收不到服务端的 SYN-ACK 报文(第二次握手),就会触发「超时重传」机制,重传 SYN 报文。
不同版本的操作系统可能超时时间不同,有的 1 秒的,也有 3 秒的,这个超时时间是写死在内核里的,如果想要更改则需要重新编译内核,比较麻烦。当客户端在 1 秒后没收到服务端的 SYN-ACK 报文后,客户端就会重发 SYN 报文,那到底重发几次呢?
在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5。
通常,第一次超时重传是在 1 秒后,第二次超时重传是在 2 秒,第三次超时重传是在 4 秒后,第四次超时重传是在 8 秒后,第五次是在超时重传 16 秒后。没错,每次超时的时间是上一次的 2 倍。
当第五次超时重传后,会继续等待 32 秒,如果服务端仍然没有回应 ACK,客户端就不再发送 SYN 包,然后断开 TCP 连接。
所以,总耗时是 1+2+4+8+16+32=63 秒,大约 1 分钟左右。
第二次握手丢失了,会发生什么?
当第二次握手丢失了,客户端和服务端都会重传:
- 客户端会重传 SYN 报文,也就是第一次握手,最大重传次数由
tcp_syn_retries内核参数决定; - 服务端会重传 SYN-ACK 报文,也就是第二次握手,最大重传次数由
tcp_synack_retries内核参数决定。
如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK 报文。
在 Linux 下,SYN-ACK 报文的最大重传次数由 tcp_synack_retries内核参数决定,默认值是 5。
当服务端收到客户端的第一次握手后,就会回 SYN-ACK 报文给客户端,这个就是第二次握手,此时服务端会进入 SYN_RCVD 状态。
第二次握手的 SYN-ACK 报文其实有两个目的 :
- 第二次握手里的 ACK, 是对第一次握手的确认报文;
- 第二次握手里的 SYN,是服务端发起建立 TCP 连接的报文;
所以,如果第二次握手丢了,就会发送比较有意思的事情,具体会怎么样呢?
因为第二次握手报文里是包含对客户端的第一次握手的 ACK 确认报文。
如果客户端迟迟没有收到第二次握手,那么客户端就觉得可能自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文。
然后,因为第二次握手中包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。
第三次握手丢失了,会发生什么?
当第三次握手丢失了,服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。
客户端收到服务端的 SYN-ACK 报文后,就会给服务端回一个 ACK 报文,也就是第三次握手,此时客户端状态进入到 ESTABLISH 状态。
因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK 报文,直到收到第三次握手,或者达到最大重传次数。
- 注意,ACK 报文是不会有重传的,当 ACK 丢失了,就由对方重传对应的报文。
Q8. 第一次,第二次,第三次,第四次挥手丢失了,都会发生什么?
第一次挥手丢失了,会发生什么?
如果第一次挥手丢失了,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数控制。当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,直接进入到 close 状态。
当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1 状态。正常情况下,如果能及时收到服务端(被动关闭方)的 ACK,则会很快变为 FIN_WAIT2状态。
第二次挥手丢失了,会发生什么?
所以如果服务端的第二次挥手丢失了,客户端就会触发超时重传机制,重传 FIN 报文,直到收到服务端的第二次挥手,或者达到最大的重传次数。
当服务端收到客户端的第一次挥手后,就会先回一个 ACK 确认报文,此时服务端的连接进入到 CLOSE_WAIT 状态。在前面我们也提了,ACK 报文是不会重传的。
这里提一下,当客户端收到第二次挥手,也就是收到服务端发送的 ACK 报文后,客户端就会处于 FIN_WAIT2 状态,在这个状态需要等服务端发送第三次挥手,也就是服务端的 FIN 报文。
对于 close 函数关闭的连接,由于无法再发送和接收数据,所以FIN_WAIT2 状态不可以持续太久,而 tcp_fin_timeout 控制了这个状态下连接的持续时长,默认值是 60 秒。
这意味着对于调用 close 关闭的连接,如果在 60 秒后还没有收到 FIN 报文,客户端(主动关闭方)的连接就会直接关闭。
但是注意,如果主动关闭方使用 shutdown 函数关闭连接且指定只关闭发送方向,而接收方向并没有关闭,那么意味着主动关闭方还是可以接收数据的。
如果主动关闭方一直没收到第三次挥手,那么主动关闭方的连接将会一直处于 FIN_WAIT2 状态(tcp_fin_timeout 无法控制 shutdown 关闭的连接)。
第三次挥手丢失了,会发生什么?
如果迟迟收不到这个 ACK,服务端就会重发 FIN 报文,重发次数仍然由 tcp_orphan_retries 参数控制,这与客户端重发 FIN 报文的重传次数控制方式是一样的。
当服务端(被动关闭方)收到客户端(主动关闭方)的 FIN 报文后,内核会自动回复 ACK,同时连接处于 CLOSE_WAIT 状态,顾名思义,它表示等待应用进程调用 close 函数关闭连接。
此时,内核是没有权利替代进程关闭连接,必须由进程主动调用 close 函数来触发服务端发送 FIN 报文。
服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。
第四次挥手丢失了,会发生什么?
如果第四次挥手的 ACK 报文没有到达服务端,服务端就会重发 FIN 报文,重发次数仍然由前面介绍过的 tcp_orphan_retries 参数控制。
当客户端收到服务端的第三次挥手的 FIN 报文后,就会回 ACK 报文,也就是第四次挥手,此时客户端连接进入 TIME_WAIT 状态。
在 Linux 系统,TIME_WAIT 状态会持续 2MSL 后才会进入关闭状态。
然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。
Q9. 为什么需要 TIME_WAIT 状态?
- 避免连接混淆或者连接冲突,防止历史连接中的数据,被后面相同四元组的连接错误的接收;
- 保证「被动关闭连接」的一方,能被正确的关闭,确保最后的数据包被接收,或处理延迟包;
TIME_WAIT 过多有什么危害?
过多的 TIME-WAIT 状态主要的危害有两种:
- 第一是内存资源占用;
- 第二是对端口资源的占用,一个 TCP 连接至少消耗「发起连接方」的一个本地端口;
2.5 TCP协议如何保证可靠性
TCP 是通过序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输的
(1)采用三次握手四次挥手保证建立的传输信道是可靠的。
(2)采用了ARQ自动(超时)重传请求协议数据传输的可靠性。
(3)采用滑动窗口协议进行流量控制。
(4)使用慢开始、拥塞避免、快重传和快恢复来进行拥塞控制.
(5)校验和CRC计算方式:在数据传输的过程中,将发送的数据段都当做一个16位的整数。将这些整数加起来。并且前面的进位不能丢弃,补在后面,最后取反,得到校验和。
发送方:在发送数据之前计算检验和,并进行校验和的填充。
接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方的进行比对。
(6)确认应答与序列号
序列号:TCP传输时将每个字节的数据都进行了编号。
确认应答:TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。
自动超时重传机制
简单理解就是发送方在发送完数据后等待一个时间,时间到达没有接收到ACK报文,那么对刚才发送的数据进行重新发送。
由于TCP传输时保证能够在任何环境下都有一个高性能的通信,因此这个最大超时时间(也就是等待的时间)是动态计算的。
在Linux中(BSD Unix和Windows下也是这样)超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。重发一次后,仍未响应,那么等待2500ms的时间后,再次重传。等待4500ms的时间继续重传。以一个指数的形式增长。累计到一定的重传次数,TCP就认为网络或者对端出现异常,强制关闭连接。
具体步骤如下:
(1)为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;
(2)并为每个已发送的数据包启动一个超时定时器;
(3)如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区;
(4)否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。
(5)接收方收到数据包后,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。
如果接收方收到二次重发的数据后,便进行ACK应答。
如果接收方发现接收的数据已存在(判断存在的根据就是序列号,所以上面说序列号还有去除重复数据的作用),那么直接丢弃,仍旧发送ACK应答。
2.6 TCP协议如何进行流量控制?
控制流量的前提当然需要保证正确率可靠性,因此首先要引入ARQ(自动重传请求(Automatic Repeat-reQuest,ARQ)协议。
TCP采用大小可变的滑动窗口进行流量控制,窗口大小的单位是字节。
数据接收端将自己可以接受的缓冲区大小放入TCP首部中“窗口大小”字段,通过ACK来通知数据传输。(在TCP的首部,有一个16位窗口字段,此字段就是用来存放窗口大小信息的。)
无差错时,A向B发送分组M1,B收到M1后向A回复,A收到回复后,发送下一个M2…..

如果出现差错,B没有收到信息,自然不会回复,A等待超时后,自动重传一个信息M,这就是所谓的ARQ。
但停止等待ARQ协议信道利用率太低。
所以需要使用连续ARQ协议来进行改善。这个协议会连续发送一组数据包,然后再等待这些数据包的ACK。

连续ARQ协议通常是结合滑动窗口协议来使用的,发送方需要维持一个发送窗口,如下图所示:

位于发送窗口内的5个分组都可以连续发送出去,而不需要等待对方的确认,这样就提高了信道利用率。、
发送方每收到一个确认,就把发送窗口向前滑动一个分组的位置。
接收方一般都是采用累积确认的方式。收到几个分组后,对按序到达的最后一个分组发送确认。
-
滑动窗口需掌握的知识点:
A、数据接收端将自己可以接受的缓冲区大小放入TCP首部中“窗口大小”字段,通过ACK来通知数据传输端。
B、窗口大小字段越大,说明网络的吞吐率越高。
C、窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,即就是说不需要数据接收端的应答,可以一次连续的发送数据。
D、操作系统内核为了维护滑动窗口,需要开辟发送缓冲区,来记录当前还有哪些数据没有应答,只有确认应答过的数据,才能从缓冲区删除。 (PS:发送缓冲区如果太大,会有空间开销)
E、数据接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给数据传输端,数据传输端收到这个值后,就会减慢自己的发送速度。
F、如果数据接收端发现自己的缓冲区满了,就会将窗口大小设置为0,此时数据传输端不再传输数据,但是需要定期发送一个窗口探测数据段,将数据接收端把窗口大小告诉数据传输端。
TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。
Q1. TCP 是如何解决窗口关闭时,潜在的死锁现象呢?
为了解决这个问题,TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。
如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
窗口探测的次数一般为 3 次,每次大约 30-60 秒(不同的实现可能会不一样)。
如果 3 次过后接收窗口还是 0 的话,有的 TCP 实现就会发 RST 报文来中断连接。
2.7 TCP协议如何进行拥塞控制?
下载时我们的速度一般都是由慢变快,原因就是拥塞控制。
网络拥塞是指在分组交换网络中传送分组的数目太多时,由于存储转发节点的资源有限而造成网络传输性能下降的情况。
常见的拥塞控制有:慢开始,拥塞避免,快重传,快恢复
慢开始:不要一开始就发送大量的数据,由小到大逐渐增加拥塞窗口的大小, 一次RTT(RTT(Round-Trip Time):往返时延)后,也就是收到一次ACK后拥塞窗口就翻倍,也就是指数型增长。
**拥塞避免:**拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1而不是加倍。这样拥塞窗口按线性规律缓慢增长。
发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。
当cwnd到达ssthresh(慢启动阈值)时,改用拥塞避免算法。
拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。
我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。
拥塞窗口 cwnd 变化的规则:
- 只要网络中没有出现拥塞,
cwnd就会增大; - 但网络中出现了拥塞,
cwnd就减少;
快重传:我们可以剔除一些不必要的拥塞报文,提高网络吞吐量。比如接收方在收到一个失序的报文段后就立即发出重复确认,而不要等到自己发送数据时捎带确认。
快重传规定:发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期。

**快恢复:**主要是配合快重传。当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把ssthresh门限减半(为了预防网络发生拥塞)
但接下来并不执行慢开始算法,因为如果网络出现拥塞的话就不会收到好几个重复的确认,收到三个重复确认说明网络状况还可以。

快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。
就是重传的时候,是重传之前的一个,还是重传所有的问题。
比如对于上面的例子,是重传 Seq2 呢?还是重传 Seq2、Seq3、Seq4、Seq5 呢?因为发送端并不清楚这连续的三个 Ack 2 是谁传回来的。
根据 TCP 不同的实现,以上两种情况都是有可能的。可见,这是一把双刃剑。
为了解决不知道该重传哪些 TCP 报文,于是就有 SACK 方法。
SACK 方法
一种实现重传机制的方式叫:SACK( Selective Acknowledgment 选择性确认)。
这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
如某图例子(发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。
2.8 Socket编程TCP
- 服务端和客户端初始化
socket,得到文件描述符; - 服务端调用
bind(),将文件描述符绑定在 IP 地址和端口; - 服务端调用
listen,进行监听; - 服务端调用
accept,等待客户端连接; - 客户端调用
connect,向服务器端的地址和端口发起连接请求; - 服务端
accept返回用于传输的socket的文件描述符; - 客户端调用
write写入数据;服务端调用read读取数据; - 客户端断开连接时,会调用
close,那么服务端read读取数据的时候,就会读取到了EOF,待处理完数据后,服务端调用close,表示连接关闭。
Linux内核中会维护两个队列:
- 半连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态;
- 全连接队列(Accpet 队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态;
从上面的描述过程,我们可以得知客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。
客户端调用 close 了,连接是断开的流程是什么?
-
我们看看客户端主动调用了
close,会发生什么?客户端调用 close 过程
- 客户端调用
close,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态; - 服务端接收到了 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符
EOF到接收缓冲区中,应用程序可以通过read调用来感知这个 FIN 包。- 这个
EOF会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;
- 这个
- 接着,当处理完数据后,自然就会读到
EOF,于是也调用close关闭它的套接字,这会使得服务端发出一个 FIN 包,之后处于 LAST_ACK 状态; - 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
- 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
- 客户端经过
2MSL时间之后,也进入 CLOSE 状态。
- 客户端调用
2.9 增大 TCP 半连接队列和全连接队列的方式
-
增大 TCP 半连接队列的方式是增大 /proc/sys/net/ipv4/tcp_max_syn_backlog;
-
增大 TCP 全连接队列的方式是增大 listen() 函数中的 backlog;
Linux系统中,则使用两个队列syn queue, accept queue分别存储状态为SYN_REVD和ESTABLISHED的连接,并且在linux2.2及以后,backlog表示accept queue的大小,而syn queue大小由
/proc/sys/net/ipv4/tcp_max_syn_backlog配置。内核参数somaxconn
全称:socket max connections 位置:
/proc/sys/net/core/somaxconn这是系统层面对于backlog的控制,实际上accept queue的大小 = min(somaxconn, backlog)。因此在listen这个系统调用层面,backlog最终还是受限于somaxconn。
查看队列
`ss -l
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port tcp LISTEN 0 128 [::]:ssh [::]:*`
在LISTEN状态下 Recv-Q 表示当前accept queue中的已连接数。Send-Q 表示总大小
2.10 如何优化 TCP
你可以根据网络的稳定性和目标服务器的繁忙程度修改 **SYN 的重传次数**,**调整客户端的三次握手时间上限**。TCP 三次握手的性能提升;TCP 四次挥手的性能提升;TCP 数据传输的性能提升;
Q1: 三次握手优化
客户端的优化
当客户端发起 SYN 包时,可以通过 tcp_syn_retries 控制其重传的次数。
服务端的优化
当服务端 SYN 半连接队列溢出后,会导致后续连接被丢弃,可以通过 netstat -s 观察半连接队列溢出的情况。
如果 SYN 半连接队列溢出情况比较严重,可以通过 tcp_max_syn_backlog、somaxconn、backlog 参数来调整 SYN 半连接队列的大小。
服务端回复 SYN+ACK 的重传次数由 tcp_synack_retries 参数控制。
如果遭受 SYN 攻击,应把 tcp_syncookies 参数设置为 1,表示仅在 SYN 队列满后开启 syncookie 功能,可以保证正常的连接成功建立。
服务端收到客户端返回的 ACK,会把连接移入 accpet 队列,等待进行调用 accpet() 函数取出连接。
可以通过 ss -lnt 查看服务端进程的 accept 队列长度
如果 accept 队列溢出,系统默认丢弃 ACK,如果可以把 tcp_abort_on_overflow 设置为 1 ,表示用 RST 通知客户端连接建立失败。
如果 accpet 队列溢出严重,可以通过 listen 函数的 backlog 参数和 somaxconn 系统参数提高队列大小,accept 队列长度取决于 min(backlog, somaxconn)。
绕过三次握手
TCP Fast Open 功能可以绕过三次握手,使得 HTTP 请求减少了 1 个 RTT 的时间,Linux 下可以通过 tcp_fastopen 开启该功能,同时必须保证服务端和客户端同时支持。
Q2: TCP 四次挥手的性能提升
针对 TCP 四次挥手的优化,我们需要根据主动方和被动方四次挥手状态变化来调整系统 TCP 内核参数。

四次挥手的优化策略
主动方的优化
-
主动发起 FIN 报文断开连接的一方,如果迟迟没收到对方的 ACK 回复,则会重传 FIN 报文,重传的次数由
tcp_orphan_retries参数决定。 -
当主动方收到 ACK 报文后,连接就进入 FIN_WAIT2 状态,根据关闭的方式不同,优化的方式也不同:
- 如果这是 close 函数关闭的连接,那么它就是孤儿连接。如果
tcp_fin_timeout秒内没有收到对方的 FIN 报文,连接就直接关闭。同时,为了应对孤儿连接占用太多的资源,tcp_max_orphans定义了最大孤儿连接的数量,超过时连接就会直接释放。 - 反之是 shutdown 函数关闭的连接,则不受此参数限制;
-
当主动方接收到 FIN 报文,并返回 ACK 后,主动方的连接进入 TIME_WAIT 状态。这一状态会持续 1 分钟,为了防止 TIME_WAIT 状态占用太多的资源,
tcp_max_tw_buckets定义了最大数量,超过时连接也会直接释放。 -
当 TIME_WAIT 状态过多时,还可以通过设置
tcp_tw_reuse和tcp_timestamps为 1 ,将 TIME_WAIT 状态的端口复用于作为客户端的新连接,注意该参数只适用于客户端。
被动方的优化
被动关闭的连接方应对非常简单,它在回复 ACK 后就进入了 CLOSE_WAIT 状态,等待进程调用 close 函数关闭连接。因此,出现大量 CLOSE_WAIT 状态的连接时,应当从应用程序中找问题。
当被动方发送 FIN 报文后,连接就进入 LAST_ACK 状态,在未等到 ACK 时,会在 tcp_orphan_retries 参数的控制下重发 FIN 报文。
Q3: TCP 数据传输的性能提升

2.11 如何解决粘包?
(1)发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。
若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
UDP不存在粘包问题, 是由于UDP发送的时候, 没有经过Negal算法优化, 不会将多个小包合并一次发送出去。
另外,在UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包,这样接收端应用程序一次recv只能从socket接收缓冲区中读出一个数据包。
也就是说,发送端send了几次,接收端必须recv几次(无论recv时指定了多大的缓冲区)
(2)接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。
这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息。
一般有三种方式分包的方式:
- 固定长度的消息;
- 特殊字符作为边界;
- 自定义消息结构。
-
固定长度的消息固定长度的消息
这种是最简单方法,即每个用户消息都是固定长度的,比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息。
但是这种方式灵活性不高,实际中很少用。
-
特殊字符作为边界
我们可以在两个用户消息之间插入一个特殊的字符串,这样接收方在接收数据时,读到了这个特殊字符,就把认为已经读完一个完整的消息。
HTTP 是一个非常好的例子。

HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界。
有一点要注意,这个作为边界点的特殊字符,如果刚好消息内容里有这个特殊字符,我们要对这个字符转义,避免被接收方当作消息的边界点而解析到无效的数据。
-
自定义消息结构
我们可以自定义一个消息结构,由包头和数据组成,其中包头包是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大。
比如这个消息结构体,首先 4 个字节大小的变量来表示数据长度,真正的数据则在后面。
1 2 3 4struct { u_int32_t message_length; char message_data[]; } message;当接收方接收到包头的大小(比如 4 个字节)后,就解析包头的内容,于是就可以知道数据的长度,然后接下来就继续读取数据,直到读满数据的长度,就可以组装成一个完整到用户消息来处理了。
2.12 SYN 报文什么时候情况下会被丢弃?
-
开启 tcp_tw_recycle 参数,并且在 NAT 环境下,造成 SYN 报文被丢弃,由于 NAT 环境中的客户端可能有不同的时间戳时钟和值,tcp_tw_recycle 在这种环境中可能会导致合法的 SYN 报文被误丢弃。因此,如果你的服务器有 NAT 后面的客户端连接,通常建议不要启用 tcp_tw_recycle。
-
TCP 两个队列满了(半连接队列和全连接队列),造成 SYN 报文被丢弃
在 Linux 操作系统下,TIME_WAIT 状态的持续时间是 60 秒,这意味着这 60 秒内,客户端一直会占用着这个端口。要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000 ,也可以通过如下参数设置指定范围:
不过,Linux 操作系统提供了两个可以系统参数来快速回收处于 TIME_WAIT 状态的连接,这两个参数都是默认关闭的:
- net.ipv4.tcp_tw_reuse,如果开启该选项的话,客户端(连接发起方) 在调用 connect() 函数时,内核会随机找一个 time_wait 状态超过 1 秒的连接给新的连接复用,所以该选项只适用于连接发起方。
- net.ipv4.tcp_tw_recycle,如果开启该选项的话,允许处于 TIME_WAIT 状态的连接被快速回收;
要使得这两个选项生效,有一个前提条件,就是要打开 TCP 时间戳,即net.ipv4.tcp_timestamps=1(默认即为 1)。
但是,tcp_tw_recycle 在使用了 NAT 的网络下是不安全的!
3. DNS协议和ARP协议
这两个协议都是用于地址间的转化,起到了“翻译官”的职责。
3.1 DNS解析过程是什么?
DNS (Domain Name System) 是 域名系统 的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统。
它用于 TCP/IP 网络,它从事将主机名或域名转换为实际 IP 地址的工作,类似于翻译官。
DNS查询时优先考虑本地的Host文件和本地的DNS解析器是否保留有缓存映射,如果没有就向上一级请求。
依次按照DNS根服务器,DNS顶层服务器,DNS管理方服务器的顺序请求。

所谓递归查询就是变更查询者,迭代查询则没有变更。
3.2 什么是MAC地址?
MAC地址是数据链路层和物理层使用的地址(硬件地址),IP地址网络层和以上各层使用的地址,是一种逻辑地址。
在发送数据时,数据从高层到低层,然后才到通信链路上传输。使用IP地址的IP数据报一旦交给了数据链路层,就被封装成了MAC帧。
MAC帧在传送时使用的源地址和目的地址都是硬件地址。

有了IP,为啥还需要MAC地址?
事实上,IP协议的产生并不只是为解决上述的“广播问题”。
还解决了很多其他网络传输过程会遇到的问题,比如一次传输的消息过大时,如何对消息进行分组等问题。
-
由于历史原因,MAC 地址及相关技术先出现,但是后来发现它并不能解决所有(已知)的问题,所以,先驱们发明了 IP 地址及相关技术来解决。
-
另一个角度,个人认为,由于 MAC 地址没有办法表达网络中的子网的概念,而 IP 地址可以。
如果网络互换设备(比如路由器)能从目标 MAC 地址中分析出目标网络,而不是只是目标主机,IP 地址还会出现吗?
有另一个有趣的问题:**如果历史反过来,**一开始就使用的是 IP 地址,而不是 MAC 地址,我们现在的网络世界会怎么样?
3.3 ARP协议工作机制是什么?
ARP(Address Resolution Protocol)即地址解析协议, 用于实现从 IP 地址到 MAC 地址的映射,即询问目标IP对应的MAC地址。
在每台安装有TCP/IP协议的电脑或路由器里都有一个ARP缓存表,表里的IP地址与MAC地址是一对应的,如下表所示。

解析MAC地址时,主机A首先在其ARP高速缓存中查找有无主机B的IP地址。
如果没有就就向本地网段发起一个ARP请求的广播包,查询此目的主机对应的MAC地址。
网络中所有的主机收到这个ARP请求后,会检查数据包中的目的IP是否和自己的IP地址一致。
如果相同,该主机首先将发送端的MAC地址和IP地址添加到自己的ARP列表中,如果ARP表中已经存在该IP的信息,则将其覆盖,然后给源主机发送一个ARP响应数据包,告诉对方自己是它需要查找的MAC地址。
源主机收到后在其ARP高速缓存中写入主机B的IP地址到硬件地址的映射。
并且采用LRU机制,及时淘汰。

查看 ARP 缓存内容
在 Linux 系统中,我们可以使用 arp -a 命令来查看 ARP 缓存的内容。
4. HTTP协议
4.1 HTTP常见的请求方法和状态码
-
OPTIONS
返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性。
-
HEAD
向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。
这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。
-
GET
向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在Web Application中,其中一个原因是GET可能会被网络蜘蛛等随意访问。Loadrunner中对应get请求函数:web_link和web_url
-
POST
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。
数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form
-
PUT
向指定资源位置上传其最新内容
-
DELETE
请求服务器删除Request-URL所标识的资源
-
TRACE
回显服务器收到的请求,主要用于测试或诊断
-
CONNECT
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
注意:
1)方法名称是区分大小写的,当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Mothod Not Allowed);
当服务器不认识或者不支持对应的请求方法时,应返回状态码501(Not Implemented)。
2)HTTP服务器至少应该实现GET和HEAD/POST方法,其他方法都是可选的,此外除上述方法,特定的HTTP服务器支持扩展自定义的方法。

100 客户端必须继续发出请求 101 客户端要求服务器根据请求转换HTTP协议版本
200 交易成功 201 提示知道新文件的URL 202 接受和处理、但处理未完成
203 返回信息不确定或不完整 204 请求收到,但返回信息为空
205 服务器完成了请求,用户代理必须复位当前已经浏览过的文件 206 服务器已经完成了部分用户的GET请求
300 请求的资源可在多处得到 301 永久重定向,在Location响应首部的值仍为当前URL(隐式重定向) 302 临时重定向,在Location响应首部的值仍为新的URL(显示重定向)
303 建议客户端访问其他URL或访问方式 304 Not Modified 请求的资源没有改变 可以继续使用缓存 305 请求的资源必须从服务器指定的地址得到
306 前一版本HTTP中使用的代码,现行版本中不再使用 307 声明请求的资源临时性删除
400 错误请求,如语法错误 401 未授权402 保留有效ChargeTo头响应 403 禁止访问
404 没有发现文件、查询或URL 405 用户在Request-Line字段定义的方法不允许
406 根据用户发送的Accept拖,请求资源不可访问 407 类似401,用户必须首先在代理服务器上得到授权
500 - 内部服务器错误 HTTP 500.100 - 内部服务器错误 HTTP 500-11 服务器关闭 HTTP
500-12 应用程序重新启动 HTTP 500-13 - 服务器太忙 HTTP 500-14 - 应用程序无效 HTTP 500-15 - 不允许请求
501 - 未实现 502 - 网关错误 503 - 服务不可用 504 - 网关超时。
4.2 HTTP协议和其他协议之间的关系
HTTP(超文本传输协议)是利用TCP在两台电脑(通常是Web服务器和客户端)之间传输信息的协议。
如果把TCP比作是高速路,HTTP就是卡车。
Socket是对TCP/IP协议的封装,Socket本身并不是协议,而是一个调用接口(API Application Programming Interface,应用程序编程接口)。
通过Socket,我们能方便地使用TCP/IP协议。
4.3 HTTP长连接和短连接
短连接:客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。
长连接:客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。有一个保持时间,
通过Keep-Alive头字段, 服务器配置Nginx, 应用程序代码设置
4.4 HTTP和HTTPS(安全性,端口,证书,URL,明密)
-
HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输。
-
HTTP 连接建立相对简单无状态的, TCP 三次握手之后便可进行 HTTP 的报文传输。
而 HTTPS 在 TCP 三次握手之后,还需进行 SSL/TLS 的握手过程,才可进入加密报文传输。
-
HTTP 的端口号是 80,HTTPS 的端口号是 443。
-
HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的,一般免费证书较少,因而需要一定费用。
HTTP:运行在TCP之上,明文传输,客户端与服务器端都无法验证对方的身份。
HTTPS:Https是身披SSL(Secure Socket Layer)外壳的Http,运行于SSL上,SSL运行于TCP之上,是添加了加密和认证机制的HTTP。
「HTTPS 是先进行 TCP 三次握手,再进行 TLS v1.2四次握手」
ps: 这句话一点问题都没有,怀疑这句话是错的人,才有问题。
「HTTPS 中的 TLS 握手过程可以同时进行三次握手」
这个场景是可能存在到,但是在没有说任何前提条件,而说这句话就等于耍流氓。需要下面这两个条件同时满足才可以:
- 客户端和服务端都开启了 TCP Fast Open 功能,且 TLS 版本是 1.3;
- 客户端和服务端已经完成过一次通信;
4.4.1 HTTPS 是如何建立连接的?其间交互了什么?
HTTPS 在HTTP的基础上加入了SSL/TLS协议,SSL/TLS依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
SSL/TLS 协议基本流程:
- 客户端向服务器索要并验证服务器的公钥。
- 双方协商生产「会话秘钥」。
- 双方采用「会话秘钥」进行加密通信。
HTTPS的缺点,虽然说HTTPS有很大的优势,但其相对来说,还是存在不足之处的:
(耗电,效率,证钱,IP,范围)
(1)HTTPS协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电;
(2)HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗,甚至已有的安全措施也会因此而受到影响;
(3)SSL证书需要钱,功能越强大的证书费用越高,个人网站、小网站没有必要一般不会用。
(4)SSL证书通常需要绑定IP,不能在同一IP上绑定多个域名,IPv4资源不可能支撑这个消耗。
(5)HTTPS协议的加密范围也比较有限,在黑客攻击、拒绝服务攻击、服务器劫持等方面几乎起不到什么作用。
最关键的,SSL证书的信用链体系并不安全,特别是在某些国家可以控制CA根证书的情况下,中间人攻击一样可行。
4.5 GET和POST的区别
GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
-
GET在浏览器回退或者刷新时时无害的,而POST会再次提交请求,因为GET请求是安全幂等的,而POST不是
-
GET参数通过URL传递,POST放在Request body中
-
GET请求在URL中传送的参数是有长度限制的;而POST没有,因为大多数浏览器通常都会限制url长度在2K个字节,而大多数服务器最多处理64K大小的url
-
GET请求只能进行url编码,而POST支持多种编码方式,form, json, xml
-
对参数的数据类型,GET只接受ASCII字符,而POST没有限制
-
GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
-
GET请求参数会被完整保留在浏览器历史记录里,可被收藏为书签,而POST中的参数不会被保留
-
GET请求会被浏览器主动cache,而POST不会,除非手动设置
-
GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
- get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
- 在做数据查询时,建议用Get方式; 而在做数据添加、修改或删除时,建议用Post方式;
-
安全的是指没有明显的对用户有影响的副作用(包括修改该资源的状态)仅指该方法的多次调用不会产生副作用,不涉及传统意义上的“安全”,这里的副作用是指资源状态。
即,安全的方法不会修改资源状态,尽管多次调用的返回值可能不一样(被其他非安全方法修改过)。HTTP方法里的GET和HEAD都是安全的。
-
幂等指的是一个方法不论多少次操作,结果都是一样。
**PUT(把内容放到指定URL),**DELETE(删除某个URL代表的资源),虽然都修改了资源内容,但多次操作,结果是相同的,因此和HEAD,GET一样都是幂等的。
4.6 Cookie和Session的区别
Cookie和Session都是客户端与服务器之间保持状态的解决方案
具体来说,cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。
Cookie实际上是一小段文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就向客户端浏览器颁发一个Cookie。
客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器,服务器检查该Cookie,以此来辨认用户状态。
Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对。
一个request或者response同时使用多个Cookie。
因为Cookie类位于包javax.servlet.http.*下面,所以JSP中不需要import该类。每个属性对应一个getter方法与一个setter方法。
Cookie并不提供修改、删除操作。
如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。

Session的区别在于,会话状态完全保存在服务器。客户端请求服务器,如果服务器记录该用户状态,就获取Session来保存状态,这时,如果服务器已经为此客户端创建过session就按照sessionid把这个session检索出来使用。
服务器Session常常依赖于Cookie机制检索ID。
但Cookie被禁用时也有其他方法比如URL重写机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。
Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。
Session对象是在客户端第一次请求服务器的时候创建的。
Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。Servlet里通过request.getSession()方法获取该客户的Session,
为了获得更高的存取速度,服务器一般把Session放在内存里。每个用户都会有一个独立的Session。如果Session内容过于复杂,当大量客户访问服务器时可能会导致内存溢出。因此,Session里的信息应该尽量精简。
由于会有越来越多的用户访问服务器,因此Session也会越来越多。
为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,Session就自动失效了。
它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。
HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie来识别是否为同一用户。
该Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。
因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。
但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击桌面浏览器图标等打开的窗口)除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session。
URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。
服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。
4.7 HTTP请求报文和响应报文的格式
请求报文格式:
- 请求行(请求方法+URI协议+版本)
- 请求头部
- 空行
- 请求主体
|
|
响应报文:
- 状态行(版本+状态码+原因短语)
- 响应首部
- 空行
- 响应主体
|
|
4.8 HTTP/1.1、HTTP/2、HTTP/3 演变
-
HTTP/1.1 相比 HTTP/1.0 性能上的改进:
(长连接,管道网络)
- 使用 TCP 长连接的方式改善了 HTTP/1.0 短连接造成的性能开销。
- 支持管道(pipeline)网络传输,只要第一个请求发出去了,不必等其回来,就可以发第二个请求出去,可以减少整体的响应时间。
但 HTTP/1.1 还是有性能瓶颈:
(头部未压缩,相同首部浪费,依然有响应的队头阻塞,无请求优先级,服务器只能被动响应)
- 请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩
Body的部分; - 发送冗长的首部。每次互相发送相同的首部造成的浪费较多;
- 服务器是按请求的顺序响应的,如果服务器响应慢,会导致客户端一直请求不到数据,也就是响应的队头阻塞;
- 没有请求优先级控制;
- 请求只能从客户端开始,服务器只能被动响应。
HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。
-
HTTP/2 相比 HTTP/1.1 性能上的改进:
HTTP/2 协议是基于 HTTPS 的,所以 HTTP/2 的安全性也是有保障的。
(双向数据流(同一连接并行请求响应),资源处理优先级,服务器推送,压缩头部二进制)
- 可以使用同一个连接并行发送多个请求和相应,可以承接双向数据流
- 允许设定数据流中不同资源的优先级,明确资源处理的先后顺序
- 打破了请求-响应的束缚,除了最初的请求响应外,服务器还能向客户端推送额外的资源(客户端没有明确要求的情况下)
- 压缩头部,头信息和数据体都是二进制格式
1. 头部压缩
HTTP/2 会压缩头(Header)如果你同时发出多个请求,他们的头是一样的或是相似的,那么,协议会帮你消除重复的部分。
这就是所谓的 HPACK 算法:在客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就提高速度了。
2. 二进制格式
HTTP/2 不再像 HTTP/1.1 里的纯文本形式的报文,而是全面采用了二进制格式,头信息和数据体都是二进制,并且统称为帧(frame):头信息帧(Headers Frame)和数据帧(Data Frame)。

这样虽然对人不友好,但是对计算机非常友好,因为计算机只懂二进制,那么收到报文后,无需再将明文的报文转成二进制,而是直接解析二进制报文,这增加了数据传输的效率。
-
HTTP/1.1 中的管道( pipeline)虽然解决了请求的队头阻塞,但是没有解决响应的队头阻塞,因为服务端需要按顺序响应收到的请求,如果服务端处理某个请求消耗的时间比较长,那么只能等相应完这个请求后, 才能处理下一个请求,这属于 HTTP 层队头阻塞。
-
HTTP/2 虽然通过多个请求复用一个 TCP 连接解决了 HTTP 的队头阻塞 ,但是一旦发生丢包,就会阻塞住所有的 HTTP 请求,这属于 TCP 层队头阻塞。

HTT/1 ~ HTTP/2
HTTP/2 队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!

HTTP/1 ~ HTTP/3
UDP 发生是不管顺序,也不管丢包的,所以不会出现像 HTTP/2 队头阻塞的问题。
大家都知道 UDP 是不可靠传输的,但基于 UDP 的 QUIC 协议 可以实现类似 TCP 的可靠性传输。
4.9 如何减少 HTTP 请求次数?
减少 HTTP 请求次数自然也就提升了 HTTP 性能,可以从这 3 个方面入手:
(减少重定向请求次数,合并请求,延迟发送请求)
- 减少重定向请求次数;服务器上的一个资源可能由于迁移、维护等原因从 url1 移至 url2 后,而客户端不知情,它还是继续请求 url1,这时服务器不能粗暴地返回错误,而是通过
302响应码和Location头部,告诉客户端该资源已经迁移至 url2 了,于是客户端需要再发送 url2 请求以获得服务器的资源。 - 合并请求;如果把多个访问小文件的请求合并成一个大的请求,虽然传输的总资源还是一样,但是减少请求,也就意味着减少了重复发送的 HTTP 头部。
- 延迟发送请求;请求网页的时候,没必要把全部资源都获取到,而是只获取当前用户所看到的页面资源,当用户向下滑动页面的时候,再向服务器获取接下来的资源,这样就达到了延迟发送请求的效果。
4.10 如何减少 HTTP 响应的数据大小?
(有损无损压缩,质量因子)
我们可以考虑对响应的资源进行压缩,这样就可以减少响应的数据大小,从而提高网络传输的效率。
压缩的方式一般分为 2 种,分别是:无损压缩;有损压缩;
无损压缩
无损压缩是指资源经过压缩后,信息不被破坏,还能完全恢复到压缩前的原样,适合用在文本文件、程序可执行文件、程序源代码。
gzip 就是比较常见的无损压缩。(客户端支持的压缩算法,会在 HTTP 请求中通过头部中的 Accept-Encoding 字段告诉服务器)
有损压缩
与无损压缩相对的就是有损压缩,经过此方法压缩,解压的数据会与原始数据不同但是非常接近。
有损压缩主要将次要的数据舍弃,牺牲一些质量来减少数据量、提高压缩比,这种方法经常用于压缩多媒体数据,比如音频、视频、图片。
可以通过 HTTP 请求头部中的 Accept 字段里的「 q 质量因子」,告诉服务器期望的资源质量。
关于图片的压缩,目前压缩比较高的是 Google 推出的 WebP 格式
4.11 Http如何优化
对于硬件优化的方向,因为 HTTPS 是属于计算密集型,应该选择计算力更强的 CPU,而且最好选择支持 AES-NI 特性的 CPU,这个特性可以在硬件级别优化 AES 对称加密算法,加快应用数据的加解密。
对于软件优化的方向,如果可以,把软件升级成较新的版本,比如将 Linux 内核 2.X 升级成 4.X,将 openssl 1.0.1 升级到 1.1.1,因为新版本的软件不仅会提供新的特性,而且还会修复老版本的问题。
对于协议优化的方向:
- 密钥交换算法应该选择 ECDHE 算法,而不用 RSA 算法,因为 ECDHE 算法具备前向安全性,而且客户端可以在第三次握手之后,就发送加密应用数据,节省了 1 RTT。
- 将 TSL1.2 升级 TSL1.3,因为 TSL1.3 的握手过程只需要 1 RTT,而且安全性更强。
对于证书优化的方向:
- 服务器应该选用 ECDSA 证书,而非 RSA 证书,因为在相同安全级别下,ECC 的密钥长度比 RSA 短很多,这样可以提高证书传输的效率;
- 服务器应该开启 OCSP Stapling 功能,由服务器预先获得 OCSP 的响应,并把响应结果缓存起来,这样 TLS 握手的时候就不用再访问 CA 服务器,减少了网络通信的开销,提高了证书验证的效率;
对于重连 HTTPS 时,我们可以使用一些技术让客户端和服务端使用上一次 HTTPS 连接使用的会话密钥,直接恢复会话,而不用再重新走完整的 TLS 握手过程。
常见的会话重用技术有 Session ID 和 Session Ticket,用了会话重用技术,当再次重连 HTTPS 时,只需要 1 RTT 就可以恢复会话。
对于 TLS1.3 使用 Pre-shared Key 会话重用技术,只需要 0 RTT 就可以恢复会话。这些会话重用技术虽然好用,但是存在一定的安全风险,它们不仅不具备前向安全,而且有重放攻击的风险,所以应当对会话密钥设定一个合理的过期时间。
4.12 HSTS协议
HSTS(HTTP Strict Transport Security)是一种安全协议,旨在增强网站的安全性,特别是针对HTTPS连接。HSTS通过强制客户端(如浏览器)只能通过加密连接(HTTPS)与服务器通信来防止中间人攻击和SSL剥离攻击。
HSTS的工作原理是在服务器的响应头中包含一个特殊的HTTP头部字段(Strict-Transport-Security),该字段告知浏览器在未来一段时间内(例如一年)只能通过HTTPS连接访问该网站。一旦浏览器接收到这个头部字段,它将会记住并在接下来的请求中自动使用HTTPS连接。
使用HSTS可以有效减少网站受到中间人攻击和SSL剥离攻击的风险,提高网站的安全性。
5. IP地址
5.1 IP地址的格式是什么?
什么是IP地址?IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
IP地址编址方案将IP地址空间划分为A、B、C、D、E五类,其中A、B、C是基本类,D、E类作为多播和保留使用,为特殊地址。
每个IP地址包括两个标识码(ID):网络ID和主机ID。
同一个物理网络上的所有主机都使用同一个网络ID,网络上的一个主机(包括网络上工作站,服务器和路由器等)有一个主机ID与其对应。3字节的网络地址 + 1字节主机地址的意思就是:前三段号码为网络号码,剩下的一段号码为本地计算机的号码。
-
A类地址:1字节的网络地址 + 3字节主机地址,网络地址的最高位必须是0。A类IP地址的地址范围1.0.0.0到127.255.255.255,IP地址的子网掩码为255.0.0.0,每个网络支持的最大主机数为
256^3-2 -
B类地址:2字节的网络地址 + 2字节主机地址,网络地址的最高位必须是10。B类IP地址地址范围**128.0.0.0-191.255.255.255,**B类IP地址的子网掩码为255.255.0.0,每个网络支持的最大主机数为
256^2-2。注:1000 0000=128 -
C类地址:3字节的网络地址 + 1字节主机地址,网络地址的最高位必须是110。C类IP地址范围**192.0.0.0-223.255.255.255。**每个网络支持的最大主机数为
256-2。适用于小规模局域网络。 -
D类地址:多播地址,用于1对多通信,最高位必须是1110。范围从224.0.0.0到239.255.255.255。
-
E类地址::为保留地址,最高位必须是“11110”
IPv4 首部与 IPv6 首部
IPv4 首部与 IPv6 首部的差异如下图:

IPv6 相比 IPv4 的首部改进:
(取消了首部校验和字段, 取消了中间路由分片/重新组装相关字段,取消选项字段)
- 取消了首部校验和字段。 因为在数据链路层和传输层都会校验,因此 IPv6 直接取消了 IP 的校验。
- 取消了分片/重新组装相关字段。 分片与重组是耗时的过程,IPv6 不允许在中间路由器进行分片与重组,这种操作只能在源与目标主机,这将大大提高了路由器转发的速度。
- 取消选项字段。 选项字段不再是标准 IP 首部的一部分了,但它并没有消失,而是可能出现在 IPv6 首部中的「下一个首部」指出的位置上。删除该选项字段使的 IPv6 的首部成为固定长度的
40字节。
5.2 单播广播多播的区别是什么?
单播:主机间一对一通信。 优点:个性化服务,及时响应;缺点:流量压力大。
广播:主机间一对所有通信。优点:布局简单,维护方便,流量负载低。缺点:缺乏个性化服务,无法在Internet宽带上传播。
多播(组播):主机间一对一组通信。优点:兼具流量负载和个性化的优点,允许在Internet宽带上传播。缺点:与单播协议相比没有纠错机制。
5.3 如何划分子网?
划分子网的方法是从主机号借用若干个位作为子网号,而主机号也就相应减少了若干个位。
于是两级IP地址在本单位内部就变为三级IP地址:网络号、子网号和主机号。
区分子网号和主机号的办法是:通过子网掩码将网络号和子网号全设为1的IP地址为子网掩码。
假设公司有4个部门,A部门有10台主机,B部门有15台主机,C部门有30台主机,D部门有20台主机。分配了一个总的网段为:192.168.2.0/24。请问该如何划分子网?
网段前面的数字是我们的网络地址,后面的24表示用24位来表示网络位,用32-24=8位来表示主机位。
主机数目不多,可以小型组网,因此采用C类地址(最大254个主机),默认掩码为225.255.255.0。
首先假设借用主机位2位来划分4个子网,则子网掩码组合为:
|
|
然而全为0和全为1的地址不能用,所以我们需要借用主机位3位,划分8-2=6个子网:
|
|
验证一下:最后提供的主机位数是2^5=32,也就是说每个子网最大的主机数是32-2=30,符合题目要求。所以子网划分如下:
|
|
子网掩码是:
|
|
6. 网络安全
6.1 什么是DDos攻击?
DDos全称Distributed Denial of Service,分布式拒绝服务攻击。最基本的DOS攻击过程如下:
- 客户端向服务端发送请求链接数据包
- 服务端向客户端发送确认数据包
- 客户端不向服务端发送确认数据包,服务器一直等待来自客户端的确认
DDoS则是采用分布式的方法,通过在网络上占领多台“肉鸡”,用多台计算机发起攻击。
DOS攻击现在基本没啥作用了,因为服务器的性能都很好,而且是多台服务器共同作用,1V1的模式黑客无法占上风。对于DDOS攻击,预防方法有:
- 减少SYN timeout时间。在握手的第三步,服务器会等待30秒-120秒的时间,减少这个等待时间就能释放更多的资源。
- 限制同时打开的SYN半连接数目。
6.2 什么是XSS攻击?
XSS也称 cross-site scripting,跨站脚本。攻击者在web页面中会插入一些恶意的script代码。当用户浏览该页面的时候,那么嵌入到web页面中script代码会执行,因此会达到恶意攻击用户的目的。
那么XSS攻击最主要有如下分类:反射型、存储型、及 DOM-based型。
反射性和DOM-baseed型可以归类为非持久性XSS攻击,存储型可以归类为持久性XSS攻击。
比如一个存在XSS漏洞的论坛,用户发帖时就可以引入带有<script>标签的代码,导致恶意代码的执行。
预防措施有:
- 前端:过滤
- 后端:转义,比如go自带的处理器就具有转义功能。
反射性xss一般指攻击者通过特定的方式来诱惑受害者去访问一个包含恶意代码的URL。当受害者点击恶意链接url的时候,恶意代码会直接在受害者的主机上的浏览器执行。
存储型XSS的原理是:主要是将恶意代码上传或存储到服务器中,下次只要受害者浏览包含此恶意代码的页面就会执行恶意代码。
6.3 什么是注入SQL攻击?
XSS是将脚本代码注入,而SQL注入攻击顾名思义就是注入SQL语句。
SQL注入是通过客户端的输入把SQL命令注入到一个应用的数据库中,从而执行恶意的SQL语句。
如果通过参数进行拼接,拼接后的sql语句就是: select * from user where username = ’’ and password = ’ ’ or ‘123’ = ‘123’;
这样的了,那么会有一个or语句,只要这两个有一个是正确的话,就条件成立,因此 123 = 123 是成立的。因此验证就会被跳过。
这只是一个简单的列子,比如还有密码比如是这样的:’; drop table user;, 这样的话,那么sql命令就变成了:
select * from user where username = ’’ and password = ’‘; drop table user;’ , 那么这个时候我们会把user表直接删除了。
比如代码:
|
|
当用户输入myuser' or 'foo' = 'foo' --,那么SQL就变成了:
|
|
在SQL里面--是注释标记,所以查询语句会在此中断。
这就让攻击者在不知道任何合法用户名和密码的情况下成功登录了。
预防方法:
- 限制数据库权限,给用户提供仅仅能够满足其工作的最低权限。
- 对进入数据库的特殊字符(’”&*;等)转义处理。
- 提供参数化查询接口,不要直接使用原生SQL。
- 永远不要信任用户的输入。对用户的输入进行校验,可以通过正则表达式,或限制长度;对单引号和 双“-”进行转换等。
- 永远不要使用动态拼装sql,可以使用参数化的sql或者直接使用存储过程进行数据查询存取。
- 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接。
- 不要把机密信息直接存放,加密或者hash掉密码和敏感的信息。
- 应用的异常信息应该给出尽可能少的提示,最好使用自定义的错误信息对原始错误信息进行包装
- sql注入的检测方法一般采取辅助软件或网站平台来检测,软件一般采用sql注入检测工具jsky,网站平台就有亿思网站安全平台检测工具。MDCSOFT SCAN等。采用MDCSOFT-IPS可以有效的防御SQL注入,XSS攻击等。