网络是一种把不同计算机或网络设备连接到一起的技术,它本质上是一种进程间通信方式,特别是跨系统的进程间通信,必须要通过网络才能进行。
七层负载均衡,四层负载均衡,三层设备,二层设备等等。实际上,这些层都来自国际标准化组织制定的开放式系统互联通信参考模型(open system interconnection reference model),简称为OSI网络模型。
TCP
为了解决网络互联中异构设备的兼容性问题,并解耦复杂的网络包处理流程,OSI模型把网络互联的框架分为应用层,表示层,会话层,传输层,网络层,数据链路层以及物理层等七层,每个层负责不同的功能。其中,
.应用层,负责为应用程序提供统一的接口
.表示层,负责把数据转换成兼容接受系统的格式
.会话层,负责维护计算机之间的通信连接
.传输层,负责为数据加上传输表头,形成数据包
.网络层,负责数据的路由和转发
.数据链路层,负责MAC寻址,错误侦测和改错
.物理层,负责在物理网络中传输数据帧
但是OSI模型还是太复杂了,也没能提供一个可实现的方法。所以,在Linux中,实际上使用的是另一个更实用的四层模型,即TCP/IP网络模型
TCP/IP模型,把网络互联的框架分为应用层,传输层,网络层,网络接口层等四层,其中,
.应用层,负责向用户提供一组应用程序,比如HTTP,FTP,DNS等
.传输层,负责端到端的通信,比如TCP,UDP等
,网络层,负责网络包的封装,寻址和路由,比如IP,ICMP等
.网络接口层,负责网络包在物理网络中的传输,比如MAC寻址,错误侦测以及通过网卡传输网络帧等。 如下图所示
虽说linux实际按照TCP/IP模型,实现了网络协议栈,但在平时的学习交流中,我们还是习惯用OSI七层来描述。比如,说的到七层和四层负载均衡,对应的分别是OSI模型中的应用层和传输层(而它们对应到TCP/IP模型中,实际上是四层和三层)
linux网络栈
有了TCP/IP模型后,在进行网络传输时,数据包就会按照协议栈,对上一层发来的数据进行逐层处理;然后封装上该层的协议头,再发送给下一层。
网络包在每一层的处理逻辑,都取决于各层采用的网络协议。 比如在应用层,一个提供REST API的应用,可以使用HTTP协议,把它需要传输的JSON数据封装到HTTP协议中,然后向下传递给TCP层。
而封装做的事情就很简单了,只是在原来的负载前后,增加固定格式的元数据,原始的负载数据并不会被修改。
所下图所示,通过TCP协议通信的网络包为例,可以看到,应用程序数据在每个层的封装格式。
.传输层在应用程序数据前面增加了TCP头;
.网络层在TCP数据包前增加了IP头
.而网络接口层,又在IP数据包前后分别增加了帧头和帧尾
这些新增的头部和尾部,都按照特定的协议格式填充。
这些新增的头部和尾部,增加了网络包的大小,但是,物理链路中并不能传输任意大小的数据包。网络接口配置的最大传输单元(MTU),就规定了最大的IP包大小。在我们最常用的以太网中,MTU默认值是1500(这也是linux的默认值)
一旦网络包超过MTU的大小,就会在网络层分片,以保证分片后的IP包不大于MTU值。显然,MTU越大,需要的分包也就越少,自然,网络吞吐能力就越好。
linux内核中的网络栈,其实也类似于TCP/IP的四层结构。就是linux通用IP网络栈的示意图:
从上到下来看这个网络栈,可以发现,
.最上层的应用程序,需要通过系统调用,来跟套接字接口进行交互;
.套接字的下面,就是我们前面提到的传输层,网络层和网络接口层;
.最底层,则是网卡驱动程序以及物理网卡设备
网卡是发送和接收网络包的基本设备。在系统启动过程中,网卡通过内核中的网卡驱动程序注册到系统中。而在网络收发过程中,内核通过中断跟网卡进行交互。
再结合前面提到的linux网络栈,可以看出,网络包的处理非常复杂。所以,网卡硬中断只处理最核心的网卡数据读取或发送,而协议栈中的大部分逻辑,都会放到软中断中处理。
linux网络收发流程
(注意,以下内容都以物理网卡为例。事实上,linu还支持众多的虚拟网络设备,而它们的网络收发流程会有一些差别)。
网络包的接收流程
当一个网络帧到达网卡后,网卡会通过DMA方式,把这个网络包放到收包队列中;然后通过硬中断,告诉中断处理程序已经收到了网络包。
接着,网卡中断处理程序会为网络帧分配内核数据结构(sk_buff),并将其拷贝到su_buff缓冲区中;然后再通过软中断,通知内核收到了新的网络帧。
接下来,内核协议栈从缓冲区中取出网络帧,并通过网络协议栈,从下到上逐层处理这个网络帧。比如,
.在链路层检查报文的合法性,找出上层协议的类型(比如IPV4还是IPV6),再去掉帧头,帧尾,然后交给网络层。
.网络层取出IP头,判断网络包下一步的走向,比如是交给上层处理还是转发。当网络层确认这个包是要发送到本机后,就会取出上层协议的类型(比如TCP还是UDP)。去掉IP头,再交给传输层处理。
.传输层取出TCP头或者UDP头后,根据<源IP,源端口,目的IP,目的端口>四元组作为标识,找出对应的socket,并把数据拷贝到socket的接收缓存中。
最后,应用程序就可以使用socket接口,读取到新接收到的数据了。 整个流程如下图所示:
网络包的发送流程
网络包的发送流程就是上图的右半部分,很容易发现,网络包的发送方向,正好跟接收方向相反。
首先,应用程序调用socket API(比如sendmsg)发送网络包。
由于这是一个系统调用,所以会陷入到内核态的套接字层中。套接字层会把数据包放到socket发送缓冲区中。
接下来,网络协议栈从socket发送缓冲区中,取出数据包;再按照TCP/IP栈,从上到下逐层处理。比如,传输层和网络层,分别为其增加TCP头和IP头,执行路由查找确认下一跳的IP,并按照MTU大小进行分片。
分片后的网络包,再送到网络接口层,进行物理地址寻址,以找到下一跳的MAC地址。然后添加帧头和帧尾,放到发包队列中。这一切完成后,会有软中断通知驱动程序:发包队列中有新的网络帧需要发送。
最后,驱动程序通过DMA,从发包队列中读出网络帧,并通过物理网卡把它发送出去。
补充
第一点,在网络收发过程中,收发队列和缓冲区位置。
在linux网络的收发流程,这个流程设计到了多个队列和缓冲区 ,包扣:
.网卡收发网络包时,通过DMA方式交互的环形缓冲区;
.网卡中断处理程序为网络帧分配的,内核数据结构sk_buff缓冲区;
.应用程序通过套接字接口,与网络协议栈交互时的套接字缓冲区
首先,这些缓冲区的位置在哪里?网卡硬件?还是在内存中?其实这些缓冲区都处于内核管理的内存中。
其中,环形缓冲区,由于需要DMA与网卡交互,理应属于网卡设备驱动的范围。 sk_buff缓冲区,是一个维护网络帧结构的双向链表,链表中的每一个元素都是一个网络帧(packet)虽然TCP/IP协议栈分了好几层,但上下不同层之间的传递,实际上只需要操作这个数据结构中的指针,而无需进行数据复制。
套接字缓冲区,则允许应用程序,给每个套接字配置不同大小的接收或发送缓冲区。用于程序发送数据,实际上就是将数据写入缓冲区;而接收数据,其实就是从缓冲区中读取。至于缓冲区中数据的进一步处理,则由传输层的TCP或UDP协议来完成。
实际上,sk_buff,套接字缓冲,连接跟踪等,都通过slab分配器来管理。可以通过/proc/slabinfo来查看它们占用的内存大小。
性能指标
我们通常用带宽,吞吐量,延时,pps(packet per second)等指标衡量网络的性能。
.带宽,表示链路的最大传输速率,单位通常为b/s(比特/秒)
.吞吐量,表示单位时间内成功传输的数据量,单位通常为b/s(比特/秒)或者B/s(字节/秒)。吞吐量受带宽限制,而吞吐量/带宽,也就是该网络的使用率
.延时,表示从网络请求发出后,一直收到远端响应,所需要的时间延迟。在不同场景中,这一指标可能会有不同含义。比如,它可以表示,建立连接需要的时间(比如TCP握手延时),或一个数据包往返所需的时间(比如RTT)
.pps,是用Packet Per Secod(包/秒)的缩写,表示以网络包为单位的传输速率。pps通常用来评估网络的转发能力,比如硬件交换机,通常可以达到线性转发(即PPS可以达到或者接近理论最大值)。而基于linux服务器的转发,则容易受到网络包大小的影响。
除了这些指标,网络的可以行(网络能否正常通信),并发连接数(TCP连接数量),丢包率(丢包百分比),重传率(重新传输的网络包比例)等也是常用的性能指标。