1. TCP/IP 四层模型
FILE TAGS
Network
应用层
协议:
- HTTP
- FTP
- Telnet
- DNS
- SMTP
应用层是工作在操作系统中的用户态,传输层及以下则工作在内核态。
HTTP
DNS
操作系统协议栈 (包含传输层和网络层)
通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈
START
Basic
Linux 内核是怎么使用中断来处理网络包的
Back:
为了解决频繁中断带来的性能开销,Linux 内核在 2.6 版本中引入了 NAPI 机制,它是混合「中断和轮询」的方式来接收网络包,它的核心概念就是不采用中断的方式读取数据,而是首先采用中断唤醒数据接收的服务程序,然后 poll
的方法来轮询数据。
因此,当有网络包到达时,会通过 DMA 技术,将网络包写入到指定的内存地址,接着网卡向 CPU 发起硬件中断,当 CPU 收到硬件中断请求后,根据中断表,调用已经注册的中断处理函数。
硬件中断处理函数会做如下的事情:
- 需要先「暂时屏蔽中断」,表示已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知 CPU 了,这样可以提高效率,避免 CPU 不停的被中断。
- 接着,发起「软中断」,然后恢复刚才屏蔽的中断。
至此,硬件中断处理函数的工作就已经完成。
硬件中断处理函数做的事情很少,主要耗时的工作都交给软中断处理函数了。
内核中的 ksoftirqd 线程专门负责软中断的处理,当 ksoftirqd 内核线程收到软中断后,就会来轮询处理数据。
ksoftirqd 线程会从 Ring Buffer 中获取一个数据帧,用 sk_buff 表示,从而可以作为一个网络包交给网络协议栈进行逐层处理。
发送数据包的过程发生了几次拷贝?
第一次,调用发送数据的系统调用的时候,内核会申请一个内核态的 sk_buff 内存,将用户待发送的数据拷贝到 sk_buff 内存,并将其加入到发送缓冲区。
第二次,在使用 TCP 传输协议的情况下,从传输层进入网络层的时候,每一个 sk_buff 都会被克隆一个新的副本出来。副本 sk_buff 会被送往网络层,等它发送完的时候就会释放掉,然后原始的 sk_buff 还保留在传输层,目的是为了实现 TCP 的可靠传输,等收到这个数据包的 ACK 时,才会释放原始的 sk_buff 。
第三次,当 IP 层发现 sk_buff 大于 MTU 时才需要进行。会再申请额外的 sk_buff,并将原来的 sk_buff 拷贝为多个小的 sk_buff。
END
传输层
START
Basic
TCP 包头格式
Back:
序号:这个是为了解决包乱序的问题。
确认号:解决丢包的问题
状态位:例如 SYN
是发起一个连接,ACK
是回复,RST
是重新连接,FIN
是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
窗口大小:流量控制和拥塞控制
END
START
Basic
TCP 三次握手四次挥手
Back:
END
START
Basic
TCP 数据分割
Back:
如果 HTTP 请求消息比较长,超过了 MSS
的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。
MTU
:一个网络包的最大长度,以太网中一般为1500
字节。MSS
:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。
END
网络层(IP 协议)
当存在多个网卡时,在填写源地址 IP 时,就需要判断到底应该填写哪个地址。这个判断相当于在多块网卡中判断应该使用哪个一块网卡来发送包。
这个时候就需要根据路由表规则,来判断哪一个网卡作为源地址 IP。
第三条目比较特殊,它目标地址和子网掩码都是 0.0.0.0
,这表示默认网关,如果其他所有条目都无法匹配,就会自动匹配这一行。并且后续就把包发给路由器,Gateway
即是路由器的 IP 地址。
START
Basic
网络层(IP 层)协议
Back:
此外 IP 中还包括 ICMP
协议和 ARP
协议。
ICMP
用于告知网络包传送过程中产生的错误以及各种控制信息。ARP
用于根据 IP 地址查询相应的以太网 MAC 地址。
END
网络接口层(MAC)
一般在 TCP/IP 通信里,MAC 包头的协议类型只使用:
0800
: IP 协议0806
: ARP 协议
ARP 协议会在以太网中以广播的形式,对以太网所有的设备喊出:“这个 IP 地址是谁的?请把你的 MAC 地址告诉我”。
网络设备
网卡
网卡驱动获取网络包之后,会将其复制到网卡内的缓存区中,接着会在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列。
START
Basic
交换机是如何工作的
Back:
交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备。
计算机的网卡本身具有 MAC 地址,并通过核对收到的包的接收方 MAC 地址判断是不是发给自己的,如果不是发给自己的则丢弃;相对地,交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有 MAC 地址。
交换机的 MAC 地址表主要包含两个信息:
- 一个是设备的 MAC 地址,
- 另一个是该设备连接在交换机的哪个端口上。
交换机根据 MAC 地址表查找 MAC 地址,然后将信号发送到相应的端口。仅此而已
如果地址表中没有,就会向除了源端口外的其他端口广播
END
START
Basic
路由器是如何工作的
Back:
路由器是基于 IP 设计的,俗称三层网络设备,路由器的各个端口都具有 MAC 地址和 IP 地址;
路由器的端口具有 MAC 地址,因此它就能够成为以太网的发送方和接收方;同时还具有 IP 地址,从这个意义上来说,它和计算机的网卡是一样的。
完成包接收操作之后,路由器就会去掉包开头的 MAC 头部。(路由器只会接受目标 MAC 为自己的 MAC 的包)
MAC 头部的作用就是将包送达路由器,其中的接收方 MAC 地址就是路由器端口的 MAC 地址。因此,当包到达路由器之后,MAC 头部的任务就完成了,于是 MAC 头部就会被丢弃。
接下来,路由器会根据 MAC 头部后方的 IP
头部中的内容进行包的转发操作。
转发操作分为几个阶段,首先是查询路由表判断转发目标。
首先,我们需要根据路由表的网关列判断对方的地址。
- 如果网关是一个 IP 地址,则这个IP 地址就是我们要转发到的目标地址,还未抵达终点,还需继续需要路由器转发。
- 如果网关为空,则 IP 头部中的接收方 IP 地址就是要转发到的目标地址,也是就终于找到 IP 包头里的目标地址了,说明已抵达终点。
END
RPC
START
Basic
HTTP 和 RPC 有什么区别
Back:
RPC(Remote Procedure Call),又叫做远程过程调用。它本身并不是一个具体的协议,而是一种调用方式
- 适用场景:真正实现的 RPC 协议不同公司、不同框架之间有不同的标准,因此比较适合用在公司内部的系统中,例如微服务系统,而如果要用在 B/S 这种架构的服务上,对所有浏览器统一标准的 HTTP 才比较适合
- 服务发现:在 HTTP 中,你知道服务的域名,就可以通过 DNS 服务去解析得到它背后的 IP 地址,默认 80 端口。而 RPC 的话,就有些区别,一般会有专门的中间服务去保存服务名和 IP 信息,比如 Consul 或者 Etcd,甚至是 Redis。想要访问某个服务,就去这些中间服务去获得 IP 和端口信息。由于 DNS 也是服务发现的一种,所以也有基于 DNS 去做服务发现的组件,比如CoreDNS。
- 连接池
- 序列化
- HTTP:基于 json 序列化,含有非常多的冗余信息,但是目前 HTTP/2 已经有非常大的提升了
- RPC:基于 Protobuf,不用考虑各种浏览器行为,效率较高
END
START
Basic
TCP 是什么,有什么特征
Back:
TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
建立一个 TCP 连接是需要客户端与服务端达成三个信息的共识:
- Socket:由 IP 地址和端口号组成
- 序列号:用来解决乱序问题等
- 窗口大小:用来做流量控制
END
START
Basic
TCP 和 UDP 的区别
Back:
1. 连接
- TCP 是面向连接的传输层协议,传输数据前先要建立连接。
- UDP 是不需要连接,即刻传输数据。
2. 服务对象
- TCP 是一对一的两点服务,即一条连接只有两个端点。
- UDP 支持一对一、一对多、多对多的交互通信
3. 可靠性
- TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按序到达。
- UDP 是尽最大努力交付,不保证可靠交付数据。但是我们可以基于 UDP 传输协议实现一个可靠的传输协议
4. 拥塞控制、流量控制
- TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
- UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。
5. 首部开销
- TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是
20
个字节,如果使用了「选项」字段则会变长的,因此 TCP 有【首部长度】字段。 - UDP 首部只有 8 个字节,并且是固定不变的,开销较小。
6. 传输方式
- TCP 是流式传输,没有边界,但保证顺序和可靠。
- UDP 是一个包一个包的发送,是有边界的,但可能会丢包和乱序。
7. 分片不同
- TCP 的数据大小如果大于 MSS 大小,则会在传输层进行分片,目标主机收到后,也同样在传输层组装 TCP 数据包,如果中途丢失了一个分片,只需要传输丢失的这个分片。
- UDP 的数据大小如果大于 MTU 大小,则会在 IP 层进行分片,目标主机收到后,在 IP 层组装完数据,接着再传给传输层。
TCP 和 UDP 应用场景:
由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:
FTP
文件传输;- HTTP / HTTPS;
由于 UDP 面向无连接,它可以随时发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:
- 包总量较少的通信,如
DNS
、SNMP
等; - 视频、音频等多媒体通信;
- 广播通信;
END
TCP 三次握手四次挥手
START
Basic
TCP 三次握手
Back:
- 客户端会随机初始化序号(
client_isn
),将此序号置于 TCP 首部的「序号」字段中,同时把SYN
标志位置为1
,表示SYN
报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于SYN-SENT
状态。 - 服务端收到客户端的
SYN
报文后,首先服务端也随机初始化自己的序号(server_isn
),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入client_isn + 1
, 接着把SYN
和ACK
标志位置为1
。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于SYN-RCVD
状态。 - 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部
ACK
标志位置为1
,其次「确认应答号」字段填入server_isn + 1
,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于ESTABLISHED
状态。
为什么需要三次握手,而不是两次、四次
- 三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。
- 原因二:同步双方初始序列号
- 原因三:避免资源浪费:如果客户端发送的
SYN
报文在网络中阻塞了,重复发送多次SYN
报文,那么服务端在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。
为什么每次建立 TCP 连接时,初始化的序列号都要求不一样呢?
主要原因有两个方面:
- 为了防止历史报文被下一个相同四元组的连接接收(主要方面);
- 为了安全性,防止黑客伪造的相同序列号的 TCP 报文被对方接收;
TCP 三次握手优化
END
START
Basic
TCP 四次挥手
Back:
- 客户端打算关闭连接,此时会发送一个 TCP 首部
FIN
标志位被置为1
的报文,也即FIN
报文,之后客户端进入FIN_WAIT_1
状态。 - 服务端收到该报文后,就向客户端发送
ACK
应答报文,接着服务端进入CLOSE_WAIT
状态。 - 客户端收到服务端的
ACK
应答报文后,之后进入FIN_WAIT_2
状态。 - 等待服务端处理完数据后,也向客户端发送
FIN
报文,之后服务端进入LAST_ACK
状态。 - 客户端收到服务端的
FIN
报文后,回一个ACK
应答报文,之后进入TIME_WAIT
状态 - 服务端收到了
ACK
应答报文后,就进入了CLOSE
状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL
(MSL 指的是一个数据包在互联网中被认为存活的最大时间,2 倍指的是一来一回) 一段时间后,自动进入CLOSE
状态,如果途中再次收到第三次挥手(FIN 报文)后,就会重置定时器,当等待 2MSL 时长后,客户端就会断开连接。至此客户端也完成连接的关闭。
每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手。
主动关闭连接的,才有 TIME_WAIT 状态。
- 关闭连接时,客户端向服务端发送
FIN
时,仅仅表示客户端不再发送数据了但是还能接收数据j j j。 - 服务端收到客户端的
FIN
报文时,先回一个ACK
应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送FIN
报文给客户端来表示同意现在关闭连接。
为什么需要 TIME_WAIT 状态?
原因一:防止历史连接中的数据,被后面相同四元组的连接错误的接收
这个时间足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。
原因二:保证「被动关闭连接」的一方,能被正确的关闭
如果已经建立了连接,但是服务端的进程崩溃会发生什么?
TCP 的连接信息是由内核维护的,所以当服务端的进程崩溃后,内核需要回收该进程的所有 TCP 连接资源,于是内核会发送第一次挥手 FIN 报文,后续的挥手过程也都是在内核完成,并不需要进程的参与,所以即使服务端的进程退出了,还是能与客户端完成 TCP 四次挥手的过程。
TCP 四次挥手优化
END
START
Basic
既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?
Back:
当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传。因为 IP 层无法获得完整的 TCP 头部,就不知道这是个 TCP 包,从而交给 TCP 负责高效的超时重传
为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。
END
START
Basic
TCP 的重传机制有哪些
Back:
超时重传
RTT
:数据发送时刻到接收到确认的时刻的差值- RTO:超时重传时间
Linux 系统估计 RTO,通常需要采样以下两个:
- 需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个平滑 RTT 的值,而且这个值还是要不断变化的,因为网络状况不断地变化。
- 除了采样 RTT,还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。
多次重传:每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。
快速重传
快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。
SACK(选择性确认) 方法
这种方式需要在 TCP 头部「选项」字段里加一个 SACK
的东西,它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
D-SACk
D-SACK
有这么几个好处:
- 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
- 可以知道是不是「发送方」的数据包被网络延迟了;
- 可以知道网络中是不是把「发送方」的数据包给复制了;
END
START
Basic
什么是TCP 滑动窗口
Back:
滑动窗口的本质是维护异步队列,增加消息发送和接收的效率
TCP 头里有一个字段叫 Window
,也就是窗口大小。
这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。这个模式就叫累计确认或者累计应答。
发送方的滑动窗口
接收方的滑动窗口
END
START
Basic
TCP 是怎么实现流量控制的
Back:
TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。
所以“滑动窗口”的首要作用是异步,而不是控制流量,但是也同时可以做到流量控制
TCP 的流量控制是由操作系统缓冲区完成的
- 发送窗口和接收窗口中所存放的字节数,都是放在操作系统内存缓冲区中的,而操作系统的缓冲区,会被操作系统调整。当应用进程没有办法及时读取缓冲区的内容时,也会对我们的缓冲区造成影响
- 发送方和接收方会协商双方的缓冲区大小,以调整滑动窗口大小,以完成流量控制
- 当服务端系统资源非常紧张的时候,操作系统可能会直接减少了接收缓冲区大小,这时应用程序又无法及时读取缓存数据,并且发送出去的协商数据也遭到了延迟,发送方没有及时知会接收方的缓冲区调整,那么这时候就有严重的事情发生了,会出现数据包丢失的现象。
窗口关闭
TCP 通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制。
如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭。
如果窗口非 0 的协商包丢失了,那么发送和接收双方会进入死锁状态
TCP 解决这个问题的办法是在收到窗口关闭协商的时候开启一个计时器,如果计时器超时则向接收方发送一个探测报文,收到探测报文的一方要回复自己现在的接收窗口大小
糊涂窗口综合症
如果窗口大小只有几个字节,那么为了发送这个几个字节的 TCP 包而要附带上 40 个字节的 TCP+IP 头,显然是非常不经济的
要解决糊涂窗口综合症,就要同时解决上面两个问题就可以了:
- 让接收方不通告小窗口给发送方:当「窗口大小」小于 min( MSS,缓存空间/2 ) ,也就是小于 MSS 与 1/2 缓存大小中的最小值时,就会向发送方通告窗口为
0
,也就阻止了发送方再发数据过来。 - 让发送方避免发送小数据:使用 Nagle 算法,该算法的思路是延时处理,只有满足下面两个条件中的任意一个条件,才可以发送数据:
- 要等到窗口大小 >=
MSS
并且数据大小 >=MSS
- 收到之前发送数据的
ack
回包;
接收方得满足「不通告小窗口给发送方」+ 发送方开启 Nagle 算法,才能避免糊涂窗口综合症
- 要等到窗口大小 >=
END
START
Basic
TCP 拥塞控制是什么
Back:
流量控制的作用是防止【发送方】的数据填满【接收方】的缓存,而拥塞控制的作用是防止【发送方】的数据【填满整个网络】,因为网络是一个共享的环境
拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的。
发送窗口的值是 swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。
拥塞窗口 cwnd
变化的规则:
- 只要网络中没有出现拥塞,
cwnd
就会增大; - 但网络中出现了拥塞,
cwnd
就减少;
只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。
拥塞控制主要有以下四个机制
慢启动
当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。
有一个叫慢启动门限 ssthresh
(slow start threshold)状态变量。
- 当
cwnd
<ssthresh
时,使用慢启动算法。 - 当
cwnd
>=ssthresh
时,就会使用「拥塞避免算法」。
拥塞避免算法
每当收到一个 ACK 时,cwnd 增加 1/cwnd。
拥塞发生
慢启动后进入拥塞避免之后,cwnd 会持续增长,当增长到一定值的时候,网络一定会出现丢包现象,从而触发重传,如果发生超时重传,则采用拥塞发生策略进行拥塞控制
ssthresh
设为cwnd/2
,cwnd
重置为1
(是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)
如果发生快速重传,则 TCP 认为这个情况没有那么严重,因为包只丢了一部分,而不是丢了全部,于是会采用以下的策略
cwnd = cwnd/2
,也就是设置为原来的一半;ssthresh = cwnd
;- 进入快速恢复算法
快速恢复
- 拥塞窗口
cwnd = ssthresh + 3
( 3 的意思是确认有 3 个数据包被收到了); - 重传丢失的数据包;
- 如果再收到重复的 ACK,那么 cwnd 增加 1;
- 如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;
END
START
Basic
Socket 通信的流程是怎样的
Back:
END
Listen 时候参数 Backlog 的意义?
在早期 Linux 内核 backlog 是 SYN 队列大小,也就是未完成的队列大小。
在 Linux 内核 2.2 之后,backlog 变成 accept 队列,也就是已完成连接建立的队列长度,所以现在通常认为 backlog 是 accept 队列。
但是上限值是内核参数 somaxconn 的大小,也就说 accpet 队列长度 = min(backlog, somaxconn)。
START
Basic
没有 accept,能建立 TCP 连接吗?
Back:
答案:可以的。
accpet 系统调用并不参与 TCP 三次握手过程,它只是负责从 TCP 全连接队列取出一个已经建立连接的 socket,用户层通过 accpet 系统调用拿到了已经建立连接的 socket,就可以对该 socket 进行读写操作了。
END
Q&A
START
Basic
如何确定最大传输速度以手动调整窗口大小(发送方缓冲区大小)
Back:
发送缓冲区与带宽时延积的关系:
- 如果发送缓冲区「超过」带宽时延积,超出的部分就没办法有效的网络传输,同时导致网络过载,容易丢包;
- 如果发送缓冲区「小于」带宽时延积,就不能很好的发挥出网络的传输效率。
所以,发送缓冲区的大小最好是往带宽时延积靠近。
END
START
Basic
有一个 IP 的服务端监听了一个端口,它的 TCP 的最大连接数是多少?
Back:
对 IPv 4,客户端的 IP 数最多为 2
的 32
次方,客户端的端口数最多为 2
的 16
次方,也就是服务端单机最大 TCP 连接数,约为 2
的 48
次方。
但是每个 TCP 连接是由一个文件描述符 fd
描述的,在 Linux 中能打开的 fd
是有限制的,并且每个连接是会占用一定量的内存的
END
START
Basic
TCP 和 UDP 可以使用同一个端口吗?
Back:
可以的
END
START
Basic
如何解决 TCP 的粘包问题
Back:
- 固定长度的消息(很少用)
- 特殊字符作为边界(HTTP)
- 自定义消息结构(包头+数据,包头是固定大小,携带了后面的数据长度信息)
END