机器之间通信的网络包与网络分层的概念有非常明显的对应关系,接下来从一段go http
请求代码开始对网络分层和网络包的分析。
首先我用gin
启动了一个web服务端:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
//注册接口
router.GET("/hello/get", func(c *gin.Context) {
c.Request.URL.String()
value, exist := c.GetQuery("name")
if !exist {
value = "the name is not exist!"
}
c.JSON(http.StatusOK, value)
return
})
//监听端口
http.ListenAndServe(":8080", router)
}
- 服务监听在8080端口之上,对外暴露path为
/hello/get
的GET
请求 - 接收到客户端请求后,取出Query类型的参数
name
,并将值返回给客户端
然后使用http
标准库实现客户端请求:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("http://xxx.xxx.xxx.xxx:8080/hello/get?name=wang")
if err != nil {
panic(err)
}
defer resp.Body.Close()
c, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
fmt.Println(string(c))
}
客户端发起请求后,是怎么到服务端去的。就从网络分层开始聊起。
网络分层
分层对于计算机的影响力太大了, 对于工程化的好处就不再赘述了。网络分层,有大名鼎鼎的OSI七层模型,也有TCP/IP四层模型。不打算从概念上去探索,当我们提到网络分层时,基本就是指如下分层:
- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理层
之所以分层,就是为了划分工作范围,使得每一层都只专注自己的目的。而达到自己的目的,只需要利用下一层提供的能力。
从这张流程图可以感受到,每一层都会把网络包做好本层特有的处理后,交给下一层继续处理,下面就是对每一层具体分析。
应用层
程序员写的代码一般都是使用应用层协议。以HTTP
为例:
对于这一行代码:resp, err := http.Get("http://xxx.xxx.xxx.xxx:8080/hello/get?name=wang")
,期望的结果是,客户端机器A发起请求后,得到服务端机器B的响应。
这是一去一回两次通信,也就是HTTP的"请求响应模型",请求时,客户端是发送方,响应时,服务端是发送方,此次的抓包结果:
HTTP作为应用层的协议,它定义了两端的沟通方式
[协议] 两台机器通信,如果都按照自己的语言习惯表达,那对方肯定是看不懂的。协议的出现就是为了制定一种双方都能遵守的沟通法则。
客户端想要告诉客户端,我的是GET请求,参数是Query形式的name=wang,于是就按照HTTP的规定,把正文信息用一个HTTP头给包起来。
- 这里模拟的场景比较简单,Query形式的内容是放在url里面的,所以正文是空的,实际的使用中,特别是
POST/PUT
请求,正文大多是有内容的。
客户端发送的HTTP头是:
服务端收到请求做完业务逻辑之后,会给客户端一个响应,同样会在正文之上,添加HTTP头:
通过HTTP头,程序就能知道这次请求是个什么状态。
客户端和服务端是怎么有自信,自己发出的网络包,对方就一定能收到呢。光从HTTP层面的协议定义,是做不了这个保证的。它干不了的事情只能找人帮忙了,于是把网络包交给了传输层。
传输层
应用层把包交给传输层了,并且嘱咐了"你一定得帮我送到对方手上,对方的回信也得及时告诉我"。
[!] 一次网络请求,到了传输层开始,往下都是操作系统在干活了。
传输层协议有两个选择,一个是UDP
,一个TCP
。众所周知,HTTP
是基于TCP
的,而且TCP
是可靠的,可靠是基于维护了连接基础上保证的。
那么在发送这个HTTP
请求之前,需要建立一个连接。
- 应用层的这次请求,看起来是两台机器之间的通信,更微观的来说是两个进程之间的通信。对于操作系统来说,进程可以靠端口来区分。服务端进程的端口自然是8080,而客户端进程的端口是操作系统给随机分配的。
- 所以这个连接,是这两个进程之间的连接。
- 这是三次握手建立连接的抓包,可以看到给客户端进程随机分配的端口是60450
连接建立后,应用层交代的网络包,就可以用到这个连接了,于是传输层给网络包加上了TCP
头,最关键的信息就是源端口以及目标端口:
- 在原来的
HTTP头
的基础上,加上了TCP头
关于TCP协议的原理,以及更多的抓包细节分析,放在了TCP部分的文章中。
传输层不负责真实的发包,它负责维护状态。对于以上提到的任意一个TCP包,包括握手部分和应用传下来的包,真正的发出过程还得依赖网络层。
- 为什么说是维护状态?它向应用层保证可靠稳定,但是它的下层网络层可保证不了。它得自己设计一套机制,盯着每一个发出去的包,是不是得到了对方的回复,不行就得重发。算是替应用层“负重前行”,respect!。
网络层
传输层给的包发给谁?自然要填上对方的IP地址。网络层为它加上了IP头
,主要包括自己和对方的IP地址:
- Src是自己的IP,Dst是对方的IP
这里看上去没做什么事情。网络层的核心作用,是规定网络世界数以亿计的终端设备的连接方式。这些会在下一篇中提到。
数据链路层
有了对方的IP地址,就能找到它吗?答案是还不够,还需要MAC地址,数据链路层会再给网络包加上一层头,填上自己和对方的MAC地址。
地址都是用来找人的,但是为什么有了IP地址,还需要MAC地址呢?先看看MAC地址的定义
MAC地址
MAC地址,全称 Media Access Address(媒体存取控制位址)。每一个网卡都有自己的MAC地址。和网卡的IP地址不同的是,网卡的MAC地址是唯一的,硬件制造商层面给它们锁死了,钥匙都给吞了。注:也有一些修改网卡MAC地址的方法,但是那不是真正的修改,可以去搜索了解。
需要用于找人的地址,一定是要唯一的,但是MAC地址唯一,IP地址就不唯一吗?
- IP地址用32位二进制数表达,世界上的终端设备早就超过上限了,不可能是唯一的。
- IP地址又可以分为两大类:公网IP和私网IP。这个是以网段范围来划分的。公网上的IP是唯一的,意思是同一时刻,只有一张网卡是这个IP。而私网IP,只能保证在网段内是唯一的,然而在不同的区域,可能网段是重复的。这种情况下的通信还需要NAT,具体的解释都放在下一篇中。
那至少在同一个网段内,IP地址是唯一的,为什么同一网段内也必须要MAC地址?
- IP地址虽然是唯一的,但是是可变的,可变指的是地址与网卡的关系。
既然MAC即是唯一的,又是不可变的,那就干脆不用IP地址了不可以吗?
- MAC地址虽然唯一又不可变,但是不可定位。
将IP地址、MAC地址做一个类比。IP地址像是我的住址,xx省xx市xx区xx街道xx小区xx栋xx号,有明显的定位功能,但是我可以搬家。MAC地址像是我的身份证号,没具体的定位功能,但是和我的关系是绑死的。
当机器A给机器B发送网络包:
-
当只有MAC地址,没有IP地址时
- 如果访问A,B属于不同网段,A根本找不到B。就好像拿着我的身份证号找我,我在附近还好,跨市跨省了几乎不可能找到。
-
当只有IP地址,没有MAC地址时
- 给机器B发送网络包的这个阶段,原来B的IP地址被C使用了,这样我发送的包就到了机器C上了。就好像其他人拿着我的住址找到我,但是我已经搬家了,他也不能确定找到的人到底是不是我,这时候如果能对一下身份证就好了。
推荐一个对MAC地址解释的比较形象的视频:www.zhihu.com/question/21…
分析完MAC地址的必要性,接下来就是怎么获取它了。
自己的MAC地址,操作系统很容易查到,但是目标机器的MAC地址怎么获取呢?
- 可以根据对方的IP地址,换来对方的MAC地址。
这有两种情况
-
当对方的IP地址和自己是同一个网段时
- 会直接使用
ARP
协议,即ddress Resolution Protocol
,地址解析协议。ARP
协议能获取到同一网段内的机器的MAC地址,它的细节后面会具体分析。
- 会直接使用
-
另一种情况,发现对方IP和自己不是同一网段
- 这属于跨网段通信,没办法直接获取到对方MAC地址
- 在聊IP地址时,提到过每一个网段会有自己的网关地址,这是一台三层交换机,是跨网段通信的窗口。它的详细作用会放在下一篇文章中描述。
- 所以当跨网段时,先把网络包交给网关。这样对方的MAC地址就填网关的MAC地址,这又属于
ARP
协议的饭碗了。 - 当网关收到网络包时,它依次拆下MAC头、IP头,会发现目标IP地址不是自己,需要转发,至于转发给谁,这就是网络层路由相关的协议了。
终于到ARP
协议本身了
ARP协议
这个协议是从数据链路层发起的,它的目的是,通过对方的IP地址,换到对方的MAC地址。手段是,向同一个网段内的所有机器,广播一个网络包,之所以要广播,因为它不知道该发给谁,只能当海王广撒网。
图中144是请求,可以看到是Broadcast类型,说明该网段内所有的机器都能收到这个网络包。但是只有一台机器会回复它,也就是145。
144的具体内容是:
-
Sender MAC Address/Sender IP Address
- 发送方信息很好获得,也是必填项,对方要根据这个才知道它给谁回复。
-
Target MAC Address/Target IP Address
- Target MAC Address不知道,Target IP Address是换MAC地址的“参数”
网段内每台机器都能收到,但是只有192.168.31.46会做出反应:
-
Sender MAC Address/Sender IP Address
- 这个MAC地址是自己的,也是
ARP
发起者期望的结果
- 这个MAC地址是自己的,也是
-
Target MAC Address/Target IP Address
- target相关的值都来自
ARP
请求包的sender部分
- target相关的值都来自
数据链路层加上MAC头,也不负责真正的发送,它依靠物理层。
物理层
物理层就不太属于程序员关心的范围了,它会通过各种介质,将网络包送到目的地。
对网络分层做一个总结
- 应用层负责定义应用程序的通信规则
- 传输层负责保证传输的特性,比如可靠,流量控制等
- 网络层负责组织网络世界机器之间的连接方式
- 数据链路层负责小范围内(同一网段内)的定位
- 上层的网络包一定会交给下层去处理
接收方收到网络包后,数据链路层先处理网络包,它取出MAC头,发现目标MAC地址是自己,确认是发给自己的,不是就放弃。
确认无误后,将网络包向上抛给网络层,网络层取出IP头,发现目标IP也是自己,不是就放弃。
确认无误后,继续往上抛到传输层,传输层取下TCP头,它会把网络包转给对应端口的进程,这时,应用程序就拿到数据了。
应用程序根据应用层协议解析数据包中的正文数据,做完处理后。开始做响应,响应则又是一次新的网络请求。
全过程解析
整个流程:
-
应用层发起请求
-
需要传输层提供
TCP
连接,于是TCP
开始三次握手与对方建立连接 -
在握手之前应该有一次
ARP
请求,获取对方(也可能是网关)的MAC地址,但是如果本地ARP
表中有目标的MAC地址,则不会请求。 -
连接建立后,开始逐层处理应用层的网络包
- 服务端收到请求后,给出响应,客户端收到响应
TCP
连接断开