(1)从go http发起请求聊聊网络分层

107 阅读11分钟

机器之间通信的网络包与网络分层的概念有非常明显的对应关系,接下来从一段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/getGET请求
  • 接收到客户端请求后,取出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四层模型。不打算从概念上去探索,当我们提到网络分层时,基本就是指如下分层:

  • 应用层
  • 传输层
  • 网络层
  • 数据链路层
  • 物理层

之所以分层,就是为了划分工作范围,使得每一层都只专注自己的目的。而达到自己的目的,只需要利用下一层提供的能力。

网络数据包构建过程.png

从这张流程图可以感受到,每一层都会把网络包做好本层特有的处理后,交给下一层继续处理,下面就是对每一层具体分析。

应用层

程序员写的代码一般都是使用应用层协议。以HTTP为例:

对于这一行代码:resp, err := http.Get("http://xxx.xxx.xxx.xxx:8080/hello/get?name=wang"),期望的结果是,客户端机器A发起请求后,得到服务端机器B的响应。

这是一去一回两次通信,也就是HTTP的"请求响应模型",请求时,客户端是发送方,响应时,服务端是发送方,此次的抓包结果:

请求响应模型.png

HTTP作为应用层的协议,它定义了两端的沟通方式

[协议]  两台机器通信,如果都按照自己的语言习惯表达,那对方肯定是看不懂的。协议的出现就是为了制定一种双方都能遵守的沟通法则。

客户端想要告诉客户端,我的是GET请求,参数是Query形式的name=wang,于是就按照HTTP的规定,把正文信息用一个HTTP头给包起来。

  • 这里模拟的场景比较简单,Query形式的内容是放在url里面的,所以正文是空的,实际的使用中,特别是POST/PUT请求,正文大多是有内容的。

客户端发送的HTTP头是:

2HTTP响应头.png

服务端收到请求做完业务逻辑之后,会给客户端一个响应,同样会在正文之上,添加HTTP头:

2HTTP请求头.png

通过HTTP头,程序就能知道这次请求是个什么状态。

客户端和服务端是怎么有自信,自己发出的网络包,对方就一定能收到呢。光从HTTP层面的协议定义,是做不了这个保证的。它干不了的事情只能找人帮忙了,于是把网络包交给了传输层。

传输层

应用层把包交给传输层了,并且嘱咐了"你一定得帮我送到对方手上,对方的回信也得及时告诉我"。

[!]  一次网络请求,到了传输层开始,往下都是操作系统在干活了。

传输层协议有两个选择,一个是UDP,一个TCP。众所周知,HTTP是基于TCP的,而且TCP是可靠的,可靠是基于维护了连接基础上保证的。

那么在发送这个HTTP请求之前,需要建立一个连接。

  • 应用层的这次请求,看起来是两台机器之间的通信,更微观的来说是两个进程之间的通信。对于操作系统来说,进程可以靠端口来区分。服务端进程的端口自然是8080,而客户端进程的端口是操作系统给随机分配的。
  • 所以这个连接,是这两个进程之间的连接。

2TCP三次握手.png

  • 这是三次握手建立连接的抓包,可以看到给客户端进程随机分配的端口是60450

连接建立后,应用层交代的网络包,就可以用到这个连接了,于是传输层给网络包加上了TCP头,最关键的信息就是源端口以及目标端口:

2TCP头.png

  • 在原来的HTTP头的基础上,加上了TCP头

关于TCP协议的原理,以及更多的抓包细节分析,放在了TCP部分的文章中。

传输层不负责真实的发包,它负责维护状态。对于以上提到的任意一个TCP包,包括握手部分和应用传下来的包,真正的发出过程还得依赖网络层。

  • 为什么说是维护状态?它向应用层保证可靠稳定,但是它的下层网络层可保证不了。它得自己设计一套机制,盯着每一个发出去的包,是不是得到了对方的回复,不行就得重发。算是替应用层“负重前行”,respect!。

网络层

传输层给的包发给谁?自然要填上对方的IP地址。网络层为它加上了IP头,主要包括自己和对方的IP地址:

2IP头.png

  • Src是自己的IP,Dst是对方的IP

这里看上去没做什么事情。网络层的核心作用,是规定网络世界数以亿计的终端设备的连接方式。这些会在下一篇中提到。

数据链路层

有了对方的IP地址,就能找到它吗?答案是还不够,还需要MAC地址,数据链路层会再给网络包加上一层头,填上自己和对方的MAC地址。

2MAC头.png

地址都是用来找人的,但是为什么有了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地址。手段是,向同一个网段内的所有机器,广播一个网络包,之所以要广播,因为它不知道该发给谁,只能当海王广撒网。

arp请求概要.png

图中144是请求,可以看到是Broadcast类型,说明该网段内所有的机器都能收到这个网络包。但是只有一台机器会回复它,也就是145。

144的具体内容是:

arp-req.png

  • Sender MAC Address/Sender IP Address

    • 发送方信息很好获得,也是必填项,对方要根据这个才知道它给谁回复。
  • Target MAC Address/Target IP Address

    • Target MAC Address不知道,Target IP Address是换MAC地址的“参数”

网段内每台机器都能收到,但是只有192.168.31.46会做出反应:

arp-reply.png

  • Sender MAC Address/Sender IP Address

    • 这个MAC地址是自己的,也是ARP发起者期望的结果
  • Target MAC Address/Target IP Address

    • target相关的值都来自ARP请求包的sender部分

数据链路层加上MAC头,也不负责真正的发送,它依靠物理层。

物理层

物理层就不太属于程序员关心的范围了,它会通过各种介质,将网络包送到目的地。

对网络分层做一个总结

  • 应用层负责定义应用程序的通信规则
  • 传输层负责保证传输的特性,比如可靠,流量控制等
  • 网络层负责组织网络世界机器之间的连接方式
  • 数据链路层负责小范围内(同一网段内)的定位
  • 上层的网络包一定会交给下层去处理

接收方收到网络包后,数据链路层先处理网络包,它取出MAC头,发现目标MAC地址是自己,确认是发给自己的,不是就放弃。

确认无误后,将网络包向上抛给网络层,网络层取出IP头,发现目标IP也是自己,不是就放弃。

确认无误后,继续往上抛到传输层,传输层取下TCP头,它会把网络包转给对应端口的进程,这时,应用程序就拿到数据了。

应用程序根据应用层协议解析数据包中的正文数据,做完处理后。开始做响应,响应则又是一次新的网络请求。

全过程解析

整个流程:

2完整流程.png

  • 应用层发起请求

  • 需要传输层提供TCP连接,于是TCP开始三次握手与对方建立连接

  • 在握手之前应该有一次ARP请求,获取对方(也可能是网关)的MAC地址,但是如果本地ARP表中有目标的MAC地址,则不会请求。

  • 连接建立后,开始逐层处理应用层的网络包

2逐层包装.png

  • 服务端收到请求后,给出响应,客户端收到响应
  • TCP连接断开