深入浅出 HTTPS:从原理到实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.3 网络模型

HTTP是应用层协议,应用层协议是TCP/IP的一部分,了解HTTP在TCP/IP中的定位,能更好地明白HTTP的职责。

1.3.1 TCP/IP概述

OSI模型是一个通用的网络协议标准,但实际使用的标准却是TCP/IP标准,TCP/IP包含的不仅仅是TCP或者IP,它是一个协议族,网络应用开发都需要掌握TCP/IP。

TCP/IP是标准的互联网网络协议,没有该协议就没有互联网,互联网上的终端必须配置TCP/IP才能进行通信。

任何协议都是一种标准,标准的含义就是通信双方需要遵循相同的规则,才能互相协作。想象两个人互相打电话,拨打电话的人首先要知道对方的手机号(IP地址),然后拨打电话确保连接上对方(TCP),通过IP选择一条最优的传输路径,最终应用层数据(人的语言)通过终端(网卡)、网络设备(电话线)传输给对方。

TCP/IP有两个最大的特点,分别是分层和封包/拆包机制。TCP/IP对网络进行了抽象,共划分为四层,每一层的特点不同,完成各自的任务。分层的好处就是清晰描述了每一层的职责,当网络应用程序出现问题后,能够快速定位到是哪一层出现问题并予以解决。

另外每一层和它的上下层都有标准的接口规范,每一层无须关心上下层是如何工作的,它只关心上下层是否正确基于规范实现了接口。

通过图1-3可直观了解各个层之间的关系。

图1-3 TCP/IP层次结构

每一层工作大概如下。

1)应用层

如果没有应用层,那么网络中传输的数据没有任何意义,因为人类无法理解数据的含义。而有了应用层,软件就能解释应用层数据的含义。在Web应用中,有了HTTP和HTML标准,浏览器才能呈现对用户有意义的内容。

应用层协议有很多,比如HTTP、FTP、邮件协议,开发者开发的软件一般都是应用层协议软件。

2)传输层

客户端传输层接收到应用层消息后,负责连接服务器,但服务器有很多服务,服务器如何知晓客户端需要连接的服务呢?

传输层中通过端口来区分服务,通过IP地址和端口号才能构建一条传输通道,对于HTTP来说,服务器端口号默认是80,而客户端的端口是随机产生的。

传输层主要有TCP和UDP, TCP能够保证数据正确地到达,一旦出现错误,会有一系列处理机制,比如重发和校验机制,保证数据正确地传输到对端。HTTP构建在TCP之上,在连接阶段,TCP使用三次握手机制确保可靠传输。

三次握手过程如下(见图1-4):

图1-4 三次握手

◎初始化连接,客户端发送SYN消息(随机值x)请求一个新连接。

◎服务器接收SYN消息,发送SYN ACK响应消息。

◎客户端发送ACK消息确认本次连接成功。

UDP不能保证数据正确传达,比如客户端收到数据后,不会向服务器确认本次接收到的数据有多少,所以服务器也无法确认客户端是否正确收到了数据,UDP的优点就是性能高,减少了很多开销。

3)网络层

网络层主要是IP这个协议,客户端和服务器传输的时候,会经过很多节点,IP就是选择一条最优的路径。每个终端上都有一张路由表,路由表负责将数据传输到下一个节点,下一个节点再传输到下下个节点,最终到达目的地址。

4)链路层

应用层、传输层、网络层都是虚拟的,只有链路层才是实体设备,包括光纤、网卡等设备。基于这些设备,数据最终才能到达终端。

接下来简单描述封包/拆包机制,对于客户端请求来说,传输层接收到应用层消息后,在HTTP数据包前面增加TCP包头,然后发送给网络层;网络层在TCP数据包前面加上IP包头发送给链路层;链路层在IP数据包前面加上以太网包头;最终服务器接收到完整的数据包。

然后服务器进行拆包:首先在网络层去除链路层包头;在传输层去除IP包头;在应用层去除TCP包头;最终得到完整的HTTP应用层数据。

1.3.2 Socket和TCP

为了通信,每个终端设备必须支持TCP/IP,比如Windows计算机上都安装了TCP/IP驱动程序。浏览器和服务器如何利用TCP/IP的能力呢?TCP/IP需要通过Socket接口提供自身的能力,或者说Socket对TCP/IP进行了封装。

有了Socket API接口,开发者并不需要深入理解TCP/IP就可以开发各类应用,这也是协议的好处之一。

很多Web应用开发者很疑惑,自己并没有接触到Socket编程,那是因为在Web应用中,由服务器和客户端(浏览器)完成数据传输和解析任务,开发者只需关心业务层HTML数据的输出。

开发者实际上也可以基于Socket API接口编写应用,比如PHP语言可以使用PHP Socket包编写一个客户端,PHP Socket包对底层的Socket API进行了一个简单的包装。

下面通过一个PHP示例,让读者清晰地明白如何调用Socket API,如何实现一个简单的HTTP客户端调用。

        //创建一个Socket句柄
        if (! ($sock = socket_create(AF_INET, SOCK_STREAM, 0))) {
            echo socket_last_error();
            exit;
        }

        echo "创建Socket句柄 \n";
        //连接服务器的某个端口
        if (! socket_connect($sock, 'www.example.com', 80)) {
            echo socket_last_error();
            exit;
        }

        echo "连接服务器 \n";

        //发送一个简单的HTTP消息
        $message = "GET /index.html HTTP/1.1\r\n\r\n";

        //发送数据至服务器
        if (! socket_send($sock, $message, strlen($message), 0)) {
            echo socket_last_error();
            exit;
        }
        echo "请求发送成功 \n";

        //接收客户端的响应
        if (socket_recv($sock, $buf, 1024, MSG_WAITALL) === false) {
            echo socket_last_error();
            exit;
        }

        //输出服务器响应
        echo $buf;

PHP开发者在编写HTTP应用的时候一般使用Curl扩展,那么Curl扩展和PHP Socket包的区别是什么?Curl扩展做了更多的抽象,完全忽略了网络的存在,而PHP Socket包和底层的Socket API更类似,灵活度更大,更能理解TCP/IP的本质。