# 理解 http 传输过程

# TCP/IP 协议族

TCP/IP 协议不是指 TCP 和 IP 两种协议，而是指利用 IP 进行通信时所需要用到的协议群的统称。

|OSI中的层|功能|TCP/IP协议族|
|----------|----|--------------|
|应用层|文件传输，电子邮件，文件服务，虚拟终端|TFTP，HTTP，SNMP，FTP，SMTP，DNS，Telnet|
|表示层|数据格式化，代码转换，数据加密|没有协议|
|会话层|解除或建立与别的接点的联系|没有协议|
|传输层|提供端对端的接口|TCP，UDP|
|网络层|为数据包选择路由|IP，ICMP，RIP，OSPF，BGP，IGMP|
|数据链路层|传输有地址的帧以及错误检测功能|SLIP，CSLIP，PPP，ARP，RARP，MTU|
|物理层|以二进制数据形式在物理媒体上传输数据|ISO2110，IEEE802。IEEE802.2|


下面的协议都属于TCP/IP协议族：

* TCP(Transport Control Protocol)传输控制协议
* IP(Internetworking Protocol)网间网协议
* UDP(User Datagram Protocol)用户数据报协议
* ICMP(Internet Control Message Protocol)互联网控制信息协议
* SMTP(Simple Mail Transfer Protocol)简单邮件传输协议
* SNMP(Simple Network manage Protocol)简单网络管理协议
* FTP(File Transfer Protocol)文件传输协议
* ARP(Address Resolation Protocol)地址解析协议

其中物理层、数据链路层、网络层、传输层都是由系统提供的能力，我们无法进行处理，也不用太去关心，绝大部分情况下我们都是在应用层对TCP或者UDP进行编程处理。

# TCP 编程

对TCP编程我们一般使用Socket，Socket是对TCP/IP协议的封装，Socket本身并不是协议，而是一个调用接口（API），通过Socket，我们才能使用TCP/IP协议。Socket 包含进行网络通信必须的五种信息：连接使用的协议，本地主机的IP地址，本地进程的协议端口，远地主机的IP地址，远地进程的协议端口。

应用层通过传输层进行数据通信时，TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据。为了区别不同的应用程序进程和连接，通过Socket可以区分来自不同应用程序进程或网络连接的通信，实现数据传输的并发服务。

Socket 提供以下接口：

* `socket`: 创建socket。
* `bind`: 绑定socket到本地地址和端口，通常由服务端调用。
* `listen`: 开启监听模式监听端口。
* `accept`: 服务器等待客户端连接，一般是阻塞态。
* `connect`: 客户端主动连接服务器。
* `send`: 发送数据。
* `recv`: 接受数据。
* `closesocket`: 关闭socket。


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1652414560209/nzTNZC_XS.png align="center")

可以简单理解为通过Socket编程通过TCP协议和另一个IP主机进行数据的交互，在TCP上传输的数据都是明文的。

三次握手和四次挥手在 Socket 之下，不属于 Socket 的内容。

# HTTP 协议

HTTP 协议是在TCP 协议之上建立的，客户端和服务端在通过TCP协议建立连接后，通过明文传输HTTP报文，传输结束后断开TCP 。

HTTP 报文在TCP上是明文传输的，在TCP层可以拦截HTTP请求的报文，任何一个路由器节点可以查看、修改、拦截HTTP请求。

HTTP 协议的请求报文和响应报文的结构基本相同，由四部分组成：

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1652415602763/sd4Cnuzjj.png align="center")

1. 请求行(request line)：由请求方法、URL(包含Query参数)和HTTP协议版本组成。
2. 头部(header)：由多个key-value值组成。
3. 空行：请求报文使用空行将请求头部和请求数据分隔。
4. 实体：POST、PUT 等方法可以携带 Body。

## 请求行

请求报文里的起始行也就是请求行（request line），它简要地描述了客户端想要如何操作服务器端的资源。

请求行由三部分构成：

1. 请求方法：是一个动词，如 GET/POST，表示对资源的操作；
2. 请求目标：通常是一个 URI，标记了请求方法要操作的资源；
3. 版本号：表示报文使用的 HTTP 协议版本。

这三个部分通常使用空格（space）来分隔，最后要用 CRLF 换行表示结束。

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1652415678480/sDX4rt-sP.png align="left")

```
POST /search?q=http HTTP/1.1
```

## 状态行

响应报文里的起始行，在这里它不叫“响应行”，而是叫“状态行”（status line），意思是服务器响应的状态。

比起请求行来说，状态行要简单一些，同样也是由三部分构成：

1. 版本号：表示报文使用的 HTTP 协议版本；
2. 状态码：一个三位数，用代码的形式表示处理的结果，比如 200 是成功，500 是服务器错误；
3. 原因：作为数字状态码补充，是更详细的解释文字，帮助人理解原因。

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1652415728182/0RQCq5jCe.png align="center")

```
HTTP/1.1 200 OK
```

## 头部字段

请求行或状态行再加上头部字段集合就构成了 HTTP 报文里完整的请求头或响应头：

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1652415871812/V_J7O4L0e.png align="center")

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1652415881945/rAfJQQR2-.png align="center")

请求头和响应头的结构是基本一样的，唯一的区别是起始行。

头部字段是 key-value 的形式，key 和 value 之间用“:”分隔，最后用 CRLF 换行表示字段结束。比如在“Host: 127.0.0.1”这一行里 key 就是“Host”，value 就是“127.0.0.1”。

HTTP 头字段非常灵活，不仅可以使用标准里的 Host、Connection 等已有头，也可以任意添加自定义头，这就给 HTTP 协议带来了无限的扩展可能。

不过使用头字段需要注意下面几点：
* 字段名**不区分大小写**，例如“Host”也可以写成“host”，但首字母大写的可读性更好；
* 字段名里**不允许出现空格**，可以使用连字符“-”，但不能使用下划线“_”。例如，“test-name”是合法的字段名，而“test name”“test_name”是不正确的字段名；
* 字段名后面必须紧接着“:”，不能有空格，而“:”后的字段值前可以有多个空格；
* 字段的顺序是没有意义的，可以任意排列不影响语义；
* 字段原则上不能重复，除非这个字段本身的语义允许，例如 Set-Cookie。

## 实体

实体在请求里是POST、PUT等请求body里携带的内容，大部分情况下是 JSON，也可以是 Protobuf 等进行编码过的内容。

在响应里就是响应的实际内容，例如GET请求返回的HTML页面，或者JSON数据等等。

# HTTPS

上面可以看到 HTTP 报文就是一个文本协议，可以看成一个字符串，这个字符串在 TCP 上是进行明文传输的，非常的不安全，所以为了数据安全传输，需要对这个字符串进行加密传输，这个加密的方法就是 HTTPS。

在请求发送时，还是正常的HTTP报文，然后通过 TLS协商密钥进行加密，传输到服务器上时再通过 协商密钥 进行解密。响应数据一样的加密解密。

具体HTTPS的过程可以看我的博客：[HTTPS小故事](https://foreverz.cn/https)

# 完整过程大致如下

1. 客户端和服务器通过**TCP三次握手**建立连接。
2. 客户端和服务器通过Socket发送和接收数据进行**TLS握手**生成**协商密钥**。
3. 客户端使用**协商密钥**对**HTTP请求报文**进行加密。
4. Socket通过TCP把**加密后的HTTP请求报文**发送到服务器。
5. 服务器通过Socket获取**加密后的HTTP请求报文**。
6. 服务器解密数据获取**HTTP请求报文**，进行解析。
7. 服务器处理**HTTP请求报文**，生成一个**HTTP响应报文**。
8. 服务器使用**协商密钥**对**HTTP响应报文**进行加密。
9. 服务器使用Socket通过**协商密钥**把**加密后的HTTP响应报文**发送到客户端。
10. 客户端收到加密数据进行解密，获取**HTTP响应报文**。
11. 客户端和服务器通过**TCP四次挥手**取消连接。

# 参考

1. [网络篇——七层协议、四层协议、TCP、HTTP、SOCKET、长短连接](https://blog.csdn.net/bjyfb/article/details/6682913)
2. [tcp, socket与http之间有什么关联?](https://cloud.tencent.com/developer/article/1393499)
3. [Socket接口使用方法](https://www.jianshu.com/p/f76217d78744)
4. [使用 Go 进行 Socket 编程 | 始于珞尘](https://juejin.cn/post/6844903575932370952)
5. [HTTP报文格式详解](https://juejin.cn/post/6875936016495345678)
6. [HTTP报文格式简介](https://www.cnblogs.com/huansky/p/14007810.html)
