云计算百科
云计算领域专业知识百科平台

HTTP 协议底层 TCP 传输原理——从输入 URL 到看到页面发生了什么?

HTTP 协议底层 TCP 传输原理——从输入 URL 到看到页面发生了什么?

前言

HTTP(超文本传输协议)是万维网的基石,打开浏览器就离不开它。但 HTTP 位于应用层,真正帮它把数据从服务器搬到浏览器的是传输层的 TCP。

在浏览器输入网址并按下回车的那一瞬间,实际发生的不是"请求-响应"这么简单,而是一条完整的链路:DNS 解析 → TCP 三次握手 → HTTP 请求报文 → HTTP 响应报文 → TCP 四次挥手。

由于涉及大量网络协议,可以非常直观地考察对计算机网络体系的整体把握程度,所以自然成为了各大公司的面试常客。

下面我们就来详细拆解这个网络通信过程。


1. DNS 解析:域名怎么变成 IP

互联网上的每一台主机都有唯一的 IP 地址,但 IP 地址是一串数字,比如 110.242.68.66,没人愿意记住它。

于是有了域名系统(DNS)。你在浏览器输入 baidu.com,浏览器会先向 DNS 服务器查询这个域名对应的 IP 地址,然后用 IP 去建立连接。在浏览器按 F12 打开开发者工具,Network 面板里能看到每个请求对应的真实 IP。

DNS 查询的简化流程:

  • 浏览器先查本地缓存,看看这个域名最近解析过没有
  • 没命中缓存,就向本地 DNS 服务器发起查询
  • DNS 服务器返回 IP 地址给浏览器
  • 浏览器拿到 IP,开始建立 TCP 连接
  • 配合下图直观理解:

    浏览器缓存 → 系统缓存 → 本地域名服务器 → 根域名服务器 → 顶级域名服务器 → 权限域名服务器

    需要注意的是,DNS 使用的是 UDP 协议,也就是说上面各种请求的转发,都是基于 UDP 这个无连接协议的。


    2. TCP 三次握手:为 HTTP 报文铺路

    HTTP 选择 TCP 作为传输层协议,是因为 HTTP 要求数据完整、有序到达,而 TCP 的确认应答和重传机制正好满足这个需求。

    在 HTTP 报文传输之前,TCP 必须先完成三次握手,确认双方的收发能力都正常:

  • 客户端 → 服务端:SYN=1, seq=x(请求建立连接)
  • 服务端 → 客户端:SYN=1, ACK=1, seq=y, ack=x+1(确认收到,发送自己的序列号)
  • 客户端 → 服务端:ACK=1, seq=x+1, ack=y+1(确认服务端的序列号,连接建立)
  • 三次握手之后,双方进入 ESTABLISHED 状态,HTTP 报文就可以开始传输了。

    用 Wireshark 抓包观察一次最简单的 HTTP 请求-响应,TCP 协议一共收发了 11 个包,可以归纳为 4 个过程:

  • 三次握手(3 个包):建立连接
  • HTTP 请求 + ACK(2 个包):浏览器发送请求报文,服务器确认收到
  • HTTP 响应 + ACK(2 个包):服务器返回响应报文,浏览器确认收到
  • 四次挥手(4 个包):释放连接
  • 除去握手和挥手,真正传输 HTTP 数据的只有 4 个包——浏览器发送请求报文、服务器回复 ACK、服务器发送响应报文、浏览器回复 ACK。


    3. HTTP 请求报文:浏览器怎么向服务器要数据

    HTTP 请求报文由三部分组成:起始行、头部字段、消息正文。

    起始行是请求行,包含三个要素:

    • 请求方法:一个动词,GET 获取资源,POST 提交数据,PUT 更新,DELETE 删除
    • 请求目标:URI,标记要操作的资源路径
    • 协议版本:HTTP/1.1

    一个完整的请求行长这样:

    GET /index HTTP/1.1

    这行指令和你在浏览器地址栏输入 http://127.0.0.1:80/index 是等价的。

    头部字段以 key-value 格式描述请求的附加信息,比如客户端接受的编码格式、语言偏好等,这里不展开。消息正文在 GET 请求中通常为空,POST 请求则携带表单数据或 JSON。

    下面用 Go net 包发送一个最小的 HTTP GET 请求,直观感受一下:

    conn, err := net.Dial("tcp", "localhost:8001")
    if err != nil {
    log.Fatal(err)
    }
    defer conn.Close()

    // 发送 HTTP 请求行
    conn.Write([]byte("GET /index HTTP/1.1\\r\\n\\r\\n"))
    // 关闭写端,告诉服务器"我发完了"
    conn.(*net.TCPConn).CloseWrite()

    // 读取服务器响应
    buffer := make([]byte, 2048)
    length, err := conn.Read(buffer)
    if err != nil {
    log.Fatal(err)
    }
    fmt.Println(string(buffer[:length]))

    关键点拆解:

    • net.Dial(“tcp”, “localhost:8001”) 建立 TCP 连接到服务器的 8001 端口
    • conn.Write(…) 把 HTTP 请求行以字节流形式写出去,底层走的就是 TCP
    • CloseWrite() 告诉服务器"我发完了",服务器才会开始处理并返回响应
    • 响应也是一个字节流,通过 conn.Read() 接收

    你在浏览器输入 URL 的过程,本质上就是这段代码在底层做的事。


    4. HTTP 响应报文:服务器怎么把页面返回

    服务器收到 HTTP 请求后,解析请求行,找到对应的资源,构造响应报文返回。

    响应报文同样由三部分组成:起始行(响应行)、头部字段、消息正文。

    响应行包含三个要素:

    • 协议版本:HTTP/1.1
    • 状态码:三位数字,200 表示成功,404 表示资源不存在,500 表示服务器内部错误
    • 原因短语:状态码的文字说明,比如 OK、Not Found

    一个让开发者最安心的响应行:

    HTTP/1.1 200 OK

    常见状态码分类:

    • 1xx:信息提示,请求已收到,继续处理
    • 2xx:成功,200 OK 最常见
    • 3xx:重定向,资源换了位置
    • 4xx:客户端错误,404 Not Found(路径写错)、400 Bad Request(参数不对)
    • 5xx:服务端错误,500 Internal Server Error(代码崩了)

    服务器的响应分为静态和动态两种。静态响应直接返回一个 HTML 文件,动态响应需要根据请求参数(比如用户 ID)生成个性化内容。

    下面是一个处理静态响应的最小 TCP 服务端代码:

    listener, err := net.Listen("tcp", ":8001")
    if err != nil {
    log.Fatal(err)
    }
    defer listener.Close()

    fmt.Println("启动服务器,监听端口:8001")

    for {
    conn, err := listener.Accept()
    if err != nil {
    log.Println(err)
    continue
    }

    go func(c net.Conn) {
    defer c.Close()

    buffer := make([]byte, 4096)
    length, _ := c.Read(buffer)
    requestLine := string(buffer[:length])

    // 简单解析请求行,提取路径
    parts := strings.Fields(requestLine)
    path := parts[1]

    // 构造响应
    body := fmt.Sprintf("<h1>Hello, you requested: %s</h1>", path)
    response := fmt.Sprintf(
    "HTTP/1.1 200 OK\\r\\n"+
    "Content-Length: %d\\r\\n"+
    "Content-Type: text/html\\r\\n"+
    "\\r\\n%s",
    len(body), body)

    c.Write([]byte(response))
    }(conn)
    }

    关键点拆解:

    • net.Listen(“tcp”, “:8001”) 监听 8001 端口,这个端口就是浏览器里 localhost:8001 指向的目标
    • listener.Accept() 阻塞等待客户端连接,每个连接返回一个 Conn 对象
    • 用 go 关键字启动 goroutine 处理每个连接,天然支持并发
    • 解析请求行,提取出请求路径
    • 构造完整的 HTTP 响应报文(状态行 + 头部 + 空行 + 响应体),通过 Conn 写出去
    • HTTP 协议不关心文件内容是什么——HTML、图片、JSON,最终都转成字节流通过 TCP 发出去

    总结

    屏蔽掉底层细节,笼统总结一下上述过程:

    应用层:

    • 浏览器封装 HTTP 请求报文
    • DNS 解析域名获得目标服务器地址

    传输层:

    • TCP 三次握手建立连接
    • 把应用层传过来的 HTTP 请求报文进行分割,并在各个报文上打上标记序号及端口号
    • 转发给网络层

    网络层:

    • 利用 ARP 协议根据 IP 地址获取作为通信目的地的 MAC 地址
    • 转发给链路层

    服务端在链路层收到数据,按序往上层发送,一直到应用层接收到浏览器发送来的 HTTP 请求报文,然后处理该请求并返回 HTTP 响应报文,浏览器接收到响应报文之后解析渲染界面。最后 TCP 四次挥手断开连接。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » HTTP 协议底层 TCP 传输原理——从输入 URL 到看到页面发生了什么?
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!