深入浅出计算机网络OSI模型之应用层(此文重点剖析http协议)

应用层概述

应用层是OSI模型中的最高层。它为应用程序的通信提供服务并且规定应用程序在通信时的协议。

应用层协议定义的内容:

  • 1、应用程序的报文类型,是请求还是响应?
  • 2、各种报文类型的语法,例如各报文中的各个字段及其详细描述
  • 3、字段的语义(包含在字段中的信息的含义)
  • 4、进程何时发送、如何发送报文,以及对报文进行响应的规则。

应用层的功能:

  • 文件的传输、访问和管理
  • 电子邮件
  • 虚拟终端
  • 查询服务和远程作业登录

应用层的重要协议:

  • FTP
  • SMTP、POP3
  • HTTP
  • DNS

域名和DNS

用户和因特网上的某个主机进行通信的时候,IP地址是难以记忆的。不要妄图将需要通信的主机IP地址记住,因为不可能只和一台主机进行通信(而且一台主机可能会有多个IP地址)。所以用户更愿意去叫主机的名字。类似于日常生活中,人们不会去特意地记忆和称呼对方的身份证号,而是去叫对方的名字。

早在21世纪以前,使用一个名为hosts的文件存储着IP地址与主机名的一一对应关系,到现在还依然沿用。
Linux系统中也有hosts文件。可使用命令cat /etc/hosts可以查看
实质上,主机名的含义是机器本身的名字,域名
在局域网中,主机名基本上都是不同的。但在整个因特网中可能会出现主机名相同的情况,为了能够在整个因特网中标识唯一一台主机,所以引入了域名。域名是包含了主机名和该主机所属组织机构的一种分层结构的名字。

//一个域名
www.baidu.com

DNS域名系统是互联网的一项服务,它被设计成一个域名和IP地址相互映射的联机分布式数据库系统,采用客户服务器方式。主要作用是将域名解析成IP地址(由域名系统中的域名服务器完成)。

主机通过域名访问的过程
在这里插入图片描述

首先是一台主机得到了一个需要访问的域名,其次:
【第一步】:应用进程调用解析程序,成为DNS的一个客户,把该域名放入DNS请求报文,以UDP用户数据报方式发送给域名服务器。
【第二步】:域名服务器查找域名和域名对应的IP地址,并将IP地址放在回答报文中,返回给客户。
【第三步】:应用进程获得目的主机的IP地址,开始进行和目的主机的通信,发送请求报文给目的主机。
【第四步】:目的主机提供服务,发送响应报文给主机。

整个因特网不仅仅只有一个域名服务器,如果只使用一个域名服务器,因特网的庞大规模一定会造成域名服务器负荷而无法正常工作,一旦这个域名服务器出现了故障,那么整个网络也就瘫痪了。为什么瘫痪,因为域名服务器瘫痪,我们无法通过域名访问到目的主机。所以要访问的域名有可能没有装入这个域名服务器,就会按照如下的方式去查找:
在这里插入图片描述

域名结构

在这里插入图片描述
在这里插入图片描述
域名内,子域与子域之间使用.分隔。
DNS关于域名的规定:

  • 不区分大小写,BAIDU和baidu是等效的
  • 域名由英文字母和数字组成,并且每一个标号(分隔的一段)不超过63个字符。
  • 标号不能使用标点符号,除了连字符-
  • 域名的级别从左到右逐渐增高
  • 一个完整域名不少于1个字符且最多不超过255个字符。

【顶级域名】
顶级域名TLD(Top Level Domain)有265个
划分成三大类:

  • (1) 国家顶级域名nTLD

据2006年统计,国家顶级域名共有247个
常记为ccTLD(cc 为country-code缩写,表示国家代码)
采用的是ISO3166规定:cn表示中国;us表示美国,uk表示英国……

  • (2) 通用顶级域名gTLD

据2006年统计,通用顶级域名共有18个
可自行查阅

  • (3) 基础结构域名

基础结构域名只有一个:arpa
用于反向域名解析,所以又称反向域名

【二级域名】
二级域名由各国国家规定,我国规定的二级类域有类别域名行政域名

  • 类别域名

类别域名共有7个:ac(科研机构)、com(工、商、金融等企业)、edu(中国教育机构)、gov(中国政府机构)、mil(中国国防机构)、net(提供互联网络服务的机构)、org(非营利性的组织)

  • 行政域名

适用于我国各省、自治区、直辖市。基本以拼音的缩写表示,例如:gz(贵州省)

可使用域名树表示域名系统:
在这里插入图片描述

域名服务器的分类

DNS服务器的管辖是以区为单位的。一个服务器负责管辖的范围称为,每一个区设置相应的权限域名服务器。

根据域名服务器提供的作用,可分为以下四类:

  • 根域名服务器
    根域名服务器是整个DNS系统中最高层次的域名服务器。
    本地域名服务器如果要进行域名解析,而自己无法解析(没有装入这个要解析的域名和IP地址的映射关系),首先该本地域名服务器就会把自己当作根域名服务器的一个客户,求助于根域名服务器。

    目前为止共有13个不同IP地址的根域名服务器,它们是13套装置并且域名有一定的规则。中国有3个根域名服务器,分别在北京、香港和台北。根域名服务器采用了任播技术,可以加快域名解析的过程。
    在这里插入图片描述

    注意:本地域名服务器如果无法解析某个域名,求助于根域名服务器,根域名服务器给出的帮助是:告诉本地域名服务器下一步要去找哪一个顶级域名服务器。

  • 顶级域名服务器
    也称TLD域名服务器,负责管理该顶级域名服务器注册的所有二级域名。当收到DNS查询请求,TLD域名服务器就会查找该域名,找到了就发送该域名对应的IP地址回去;如果没有找到,就给出下一步应当去找哪一个域名服务器的IP地址。
    在这里插入图片描述

  • 权限域名服务器
    权限域名服务器负责一个区的域名服务器,如果NDS客户求助于权限域名服务器,而权限域名服务器没有查询到,就会告诉客户,下一步应该找的权限域名服务器的IP地址。

  • 本地域名服务器
    也称为默认域名服务器,进行域名解析时,客户首先就会把请求报文发送给本地域名服务器。

为了提高域名服务器的可靠性,DNS域名服务器会把数据复制到几个域名服务器来保存,其中一个是主域名服务器,另外的是辅助域名服务器

文件传送协议

文件传送协议提供不同种类主机系统(软、硬件体系结构等都可不同)之间文件的传输能力。

文件传送协议有:

  • 基于TCP的文件传送协议FTP(File Transfer Protocol)
  • 基于UDP的简单文件传送协议TFTP(Trivial File Transfer Protocol)

它们都是文件共享协议中的一大类,拷贝整个文件,特点:如果要存取某个文件,必须先获得一个本地的文件副本。如果尝试修改文件,只能对文件的副本进行修改,然后再将修改后的文件副本传回原节点。
在这里插入图片描述

  • 从客户端拷贝文件到服务器称为上传
  • 从服务器拷贝文件到客户端称为下载

FTP的工作原理

FTP只提供文件传输的一些基本服务,采用客户/服务器工作方式。使用TCP协议 实现可靠传输。

依照FTP协议提供服务,进行文件传送的计算机就是FTP服务器;连接FTP服务器,遵循FTP协议与FTP服务器传送文件的计算机称为FTP客户端。一个FTP服务器可以同时给多个进程提供服务。服务器采用的是多进程版本基于TCP的通信,所以有一个进程,负责接收请求,该进程称为主进程;其他的进程有多个,负责处理请求,称为从属进程

服务器的主进程工作过程:

  • (1)打开孰知端口号(端口号21),使客户进程能够连接
  • (2)等待客户进程发出请求
  • (3)启动从属进程处理请求。处理完毕后,从属进程终止
  • (4)回到等待的状态,继续处理其他客户进程的请求

由于采用多进程版本的TCP通信,所以主进程和从属进程是并发进行的。
在这里插入图片描述
FTP的客户/服务器相较于其他客户/服务器的独特优势就是:文件传输时,客户和服务器之间建立两个并行的TCP连接。
在这里插入图片描述
优点在于:使得协议更加简单和易于实现,并且提高了FTP的效率。

注意:
FTP服务器进程使用自己传送数据的熟知端口20和客户进程提供的端口号建立数据连接。事实上,具体是否使用端口20还取决于文件的传输模式。主动的方式使用的是端口20,而被动的方式由FTP服务器和客户端协商决定(端口 >1024 && 端口 < 65535)。

FTP传输方式

  • ASCLL传输方式
  • 二进制传输方式

FTP匿名服务器

使用FTP时用户必须首先进行登录(用户名 & 密码),在远程主机上获取到相应权限之后,才有资格上传和下载文件。但是互联网上有如此多的FTP主机,不可能要求用户在每一台主机上都有账号。所以引入了匿名服务器,有匿名服务器后用户可以不需要进行登记注册和获取FTP服务器的授权,就能够建立连接,可以从匿名服务器上下载文件。

有了匿名服务器后,我们可以进行匿名登录。登录的用户名是标识符anonymous,密码可以是任意的字符串。

不是所有的FTP主机都能够匿名登录,只有提供了这项服务的FTP主机才支持匿名登录。

电子邮件协议

电子邮件E-mail是应用层的一种异步通信方式,不需要通信双方在场。
收件人和发件人的格式:用户名@邮件服务域名

电子邮件的组成结构

在这里插入图片描述

用户代理:用户与email系统的接口。
发送人用户代理的功能:编写;展示;处理;通信。
SMTP服务器的功能:发送&接收邮件;回报发送结果。

其中的协议

  • SMTP:邮件发送协议,客户端推送邮件到SMTP服务器
  • POP3:邮件接收协议,向SMTP服务器拉取邮件。

电子邮件支持异步通信:发送邮件服务器会把邮件发送到接收方邮件服务器,接收方邮件服务器可以选择接收并删除、接收并保存,如果选择接收并保存,就可以等接收方用户需要时,接收方用户再去请求读取邮件。

SMTP

简单电子邮件传送协议SMTP(Simple Mail Transfer Protocol)属于TCP/IP协议族,它规定了两个相互通信的SMTP进程之间如何交换信息,并且帮助SMTP客户找到下一个目的地。

遵循SMTP协议负责发送邮件的进程就是SMTP客户;遵循SMTP协议负责接收邮件的进程就是SMTP服务器。当然,SMTP服务器也可作为SMTP客户。

采用客户/服务器工作方式、使用TCP协议 实现可靠传输。SMTP服务器进程使用孰知端口25。

【SMTP通信三阶段】

  • (1)建立连接
    在这里插入图片描述
    接收方邮件服务器如果有能力接收邮件,就会“确认”,确认回复的是250 OK。如果现在没有能力接收邮件,就会给发送邮件服务器回复421 Service not available
  • (2)邮件传送
    邮件传送的细节这里不做介绍。
  • (3)释放连接
    邮件发完之后,SMTP客户发送QUIT命令,SMTP如果返回221,就表示同意释放TCP连接

【SMTP的特点】
SMTP是简单电子邮件传输协议,有一些缺点比较明显:

  • 1、不能发送可执行文件或者二进制对象
  • 2、仅仅只能发送7位以内的ASCLL码,并且只能传送英语。
  • 3、SMTP服务器会拒绝超过一定长度的邮件。

然而日常生活中,用户基本上不会发送7位以内的ASCLL码并且发送的邮件长度也是不固定的,所以针对SMTP协议的这些缺点,扩充了MIME

MIME

多用途网际邮件扩充MIME是对SMTP的扩充,定义了传送非ASCLL码的规则。不要混淆了,它没有打破SMTP只能发送7位以内的ASCLL码的规则。
在这里插入图片描述

新增:

1、新增了5个首部字段:MIME版本/内容描述/内容标识/内容传送编码/内部内容
2、定义了邮件内容格式,对多媒体邮件进行了标准化
3、定义了传送编码,可对任意格式内容转换。

MIME使得电子邮件系统可以支持声音、图像、视频、多种国家语言等等,让电子邮件的传送内容变得丰富多彩。

POP3

POP3是简单的邮局读取协议(Post Office Protocol)。
在这里插入图片描述
通信的方式采用拉取(pull)
属于TCP/IP协议族、通过TCP端口110建立连接。

IMAP

互联网信息访问协议IMAP,是一种优于POP3并且也比POP3更复杂的新协议。当用户打开IMAP服务器的邮箱时,可以看到邮箱的首部,通过邮箱首部的内容(比如:邮件标题,发送方名字等)再决定是否下载。

属于TCP/IP协议族、通过TCP端口143建立连接。

电子邮件协议TCP连接端口
SMTP25
POP3110
IMAP143

HTTP

万维网

万维网WWW(World Wide Web )是一个规模庞大,联机式的信息存储所,是无数文档的集合,简称Web。这里的文档就是我们所说的页面

在这里插入图片描述
页面都是一些超文本信息,可用于描述文本、图片、视频等多媒体信息,而这些多媒体信息称为超媒体
Web上的信息由彼此相联的文档组成,相联的方式就是超链接(有时也称链接),可以实现从一个站点跳转到另一个站点。例如,这就是一个超链接点击此处可跳转
在这里插入图片描述
这些文档有可能相互分隔在千里之外,但必须连接在因特网上。整个因特网上的万维网文档是如此之多,它是如何精准地从一个站点跳转到另一个站点的?需要在因特网上唯一的标识一个万维网文档,所以引入了统一资源定位符URL。

URL

URL是统一资源定位符,就是我们俗说的“网址”。它用来在互联网上唯一的标识某个资源的地址,这里的资源指因特网上任何可以被访问的对象。

URL的一般格式:
在这里插入图片描述

  • 最常用的协议是http协议,其次是FTP协议。
  • 主机部分可以是域名,也可以是该资源所在主机的IP地址。
  • 使用http协议,默认端口是80,通常可以省略。
  • 路径也可以省略的,如果省略了路径,那么URL定位的资源就是某个主页。
  • URL的字符串不区分大小写

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现。比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

转义的规则:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
在这里插入图片描述

所以用户浏览页面的方法有两种:

  • 1、在浏览器的地址窗口输入要跳转到的页面的URL。
  • 2、点击页面的超链接

HTTP概述

Web之所以能够完成这样宏大的一项工作,离不开它背后的各种协议,这些协议被称为Web协议族。HTTP协议就是Web协议族中的一个典型代表,HTTP协议定义了浏览器(万维网客户进程)怎样向万维网请求万维网文档,以及服务器怎样把文档传送给浏览器。
在这里插入图片描述
HTTP采用了TCP作为运输层协议,但HTTP协议本身是无连接的(通信双方在交换HTTP报文之前不需要建立HTTP连接)。

HTTP/1.1使用了持续连接:服务器在发送响应后仍然在一段时间内保持着这条连接。
在这里插入图片描述

HTTP报文结构

HTTP的两类报文:

  • 请求报文。客户->服务器
  • 响应报文。服务器->客户
    HTTP报文是面向文本的,所以在HTTP报文中的每一个字段都是一些ASCII码串。
    在这里插入图片描述
  • 开始行
    用于区分该报文是请求报文还是响应报文。请求报文内的开始行称为请求行, 响应报文内的开始行称为状态行。请求行和状态行都分成了三个部分,中间以空格为间隔。
  • 首部行
    首部行采用了键值对的形式首部字段名 : 值

以上就是报文的报头部分,报头和实体主体之间使用一行空行分隔。其中响应报文的状态行内的短语,用于描述状态码的。例如我们常见的404 Not found,404是状态码,而Not found就是状态码描述。

现在我们模拟实现一个http服务器。并且能够查看一个实际的请求报文。
Sock.hpp的代码我放在了server.cc的后面

//server.cc文件
#include <iostream>
#include <cstring>
#include <cstdlib>
#include "sock.hpp"
#include <pthread.h>
#include <unistd.h>

void UsePage();
void *HandleRequest(void *arg);

//命令格式:./server server_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        UsePage();
        return 1;
    }
    uint16_t port = atoi(argv[1]);
    int listen_sockfd = Sock::GetSock();
    Sock::Bind(listen_sockfd, port);
    Sock::Listen(listen_sockfd);

    std::cout << "server ready……" << std::endl;
    while (true)
    {
        int new_sock = Sock::Accept(listen_sockfd);
        if (new_sock > 0)
        {
            //创建一个从属线程,处理客户请求
            int *pram = new int(new_sock);
            pthread_t pid;
            pthread_create(&pid, nullptr, HandleRequest, pram);
        }
    }
    return 0;
}

void UsePage()
{
    std::cout << "命令格式:./server server_port" << std::endl;
}
void *HandleRequest(void *arg)
{
    int sockfd = *(int *)arg;
    delete (int *)arg;
    pthread_detach(pthread_self());

    //接收客户端请求
    char buffer[1024 * 10];
    memset(buffer, 0, sizeof(buffer));
    ssize_t s = recv(sockfd, buffer, sizeof(buffer), 0);

    //成功读取到请求报文,可以构建一个响应报文给客户
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << buffer; //查看http的请求格式

        //开始构建响应报文
        std::string response = "http/1.0 200 OK\n";
        response += "Content-Type : text/plain\n"; // text/plain,表示正文是普通文本
        response += "\n";                          //空行
        response += "LHY \n i love you\n---YDY\n";

        //发送响应报文
        send(sockfd, response.c_str(), response.size(), 0);
    }

    //完成后,关闭用于处理请求的套接字文件描述符
    close(sockfd);
    return nullptr;
}
//Sock.hpp文件
#pragma once
#include <iostream>
#include <cstdlib>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;
class Sock
{
public:
    static int GetSock()
    {
        int fd = socket(AF_INET, SOCK_STREAM, 0); // TCP创建流式套接字
        if (fd < 0)
        {
            std::cout << "create socket failed" << std::endl;
            exit(1); //没必要执行下去,直接终止程序
        }
        std::cout << "create socket success" << std::endl;
        return fd;
    }
    static void Bind(int sockfd, uint16_t port)
    {
        //描述要绑定的套接字地址
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(port);
        server.sin_addr.s_addr = INADDR_ANY;

        if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
        {
            std::cout << " server bind socket address failed" << std::endl;
            exit(2);
        }
        std::cout << "bind socket success" << std::endl;
    }
    static void Listen(int listen_sockfd)
    {
        if (listen(listen_sockfd, 5) < 0)
        {
            std::cout << "server listen client failed" << std::endl;
            exit(3);
        }
        std::cout << "listen success" << std::endl;
    }
    static int Accept(int listen_sockfd)
    {
        //输出型参数,用于获取客户套接字地址
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);

        int new_fd = accept(listen_sockfd, (struct sockaddr *)&peer, &len);
        if (new_fd < 0)
        {
            //创建新的套接字,并取出请求队列中的队头套接字地址连接失败,则返回-1
            return -1;
        }
        std::cout << "get a new link" << std::endl;
        return new_fd;
    }
    //客户请求连接服务器
    static int Connect(int sockfd, std::string ip, uint16_t port)
    {
        //描述服务器的套接字地址
        struct sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(port);
        server.sin_addr.s_addr = inet_addr(ip.c_str());

        if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0)
        {
            std::cout << "client connect server failed" << std::endl;
        }
    }
};

有了http服务器,那么http客户端呢?http的客户进程就是浏览器!!!

所以要测试,直接在浏览器的地址窗口输入:运行服务端进程的公网IP:服务端进程绑定的端口号,例如:1.14.103.25:8080

这里需要注意的是,运行我们模拟的http服务端进程时,要开放你绑定的端口。如何开放端口请自行查阅。

我们就可以在服务器上看到浏览器发送的请求报文:
在这里插入图片描述
http请求报文的结构果然一一对应
浏览器显示内容:
在这里插入图片描述

请求方法

在上面的代码测试中,会发现服务器一次不止接收到一个请求报文,而是多个。日常工作中,http服务器一次接收到的报文也不止一个,那么它如何做到精确读取到一个完整的报文?

  • 报头的读取:判断是否读取到空行,读取到空行代表报文部分到此为止。
  • 正文(实体主体)的读取:如果有正文内容,首部行会包含一个字段Content-Length,表示该报文的正文有多少个字节,按照这个字段给的值读取字节数,就可以做到精确地读取正文了。
    在这里插入图片描述

在这里插入图片描述

方法说明支持的HTTP版本
GET获取资源1.0 、1.1
POST传输实体主体1.0 、1.1
HEAD获取报文首部1.0、1.1
PUT传输文件1.0、1.1
DELETE删除文件1.0、1.1
OPTIONS询问支持的方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0
UNLINE断开连接关系1.0

其中最常用的是GET、POST,其次是HEAD

为了测试GET和POST请求,我们将改写上面的代码。
在改写之前,我们需要补充一些预备知识。

浏览器发给服务器:请求报文的请求行的第二部分(URL),是Web目录。
在这里插入图片描述

在这里插入图片描述
http请求的/是Web根目录,如果请求/,意味着要访问该网站的首页。

在这里插入图片描述
而我们一般要请求的不是首页,而是具体的某个资源。

现在我们给浏览器响应一个实体主体内容是文件的报文。并且引入一些HTML,控制浏览器给我们模拟的服务器发送GET、POST方法,方便查看这两种方法出现的现象和总结。
在这里插入图片描述
其中Sock.hpp,代码在文章前部分已经给出。

//http.cc
#include <iostream>
#include <cstring>
#include <cstdlib>
#include "sock.hpp"
#include <pthread.h>
#include <unistd.h>
#include <fstream>
#include <sys/stat.h>

#define WWWROOT "./WebRoot/"
#define HOME_PAGE "index.html"

void UsePage();
void *HandleRequest(void *arg);

//命令格式:./server server_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        UsePage();
        return 1;
    }
    uint16_t port = atoi(argv[1]);
    int listen_sockfd = Sock::GetSock();
    Sock::Bind(listen_sockfd, port);
    Sock::Listen(listen_sockfd);

    std::cout << "server ready……" << std::endl;
    while (true)
    {
        int new_sock = Sock::Accept(listen_sockfd);
        if (new_sock > 0)
        {
            //创建一个从属线程,处理客户请求
            int *pram = new int(new_sock);
            pthread_t pid;
            pthread_create(&pid, nullptr, HandleRequest, pram);
        }
    }
    return 0;
}

void UsePage()
{
    std::cout << "命令格式:./server server_port" << std::endl;
}
void *HandleRequest(void *arg)
{
    int sockfd = *(int *)arg;
    delete (int *)arg;
    pthread_detach(pthread_self());

    //接收客户端请求
    char buffer[1024 * 10];
    memset(buffer, 0, sizeof(buffer));
    ssize_t s = recv(sockfd, buffer, sizeof(buffer), 0);

    //成功读取到请求报文,可以构建一个响应报文给客户
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << buffer; //查看http的请求格式

        //将要打开的文件描述一下,便于后续操作
        std::string html_file = WWWROOT;
        html_file += HOME_PAGE;
        std::ifstream in(html_file);

        if (!in.is_open()) //打开文件失败
        {
            //构建响应报文
            std::string http_response = "http/1.0 404 NOT FOUND\n"; //状态行
            http_response += "Content-Type: text/html; charset = utf8\n";
            http_response += "\n"; //空行
            http_response += "<html><p>The resource you want to access does not exist</p></html>";
            //发送响应报文
            send(sockfd,http_response.c_str(), http_response.size(), 0);
        }
        else //打开文件成功
        {
            //构建响应报文
            // 1、获取要打开文件的字节数,这个信息在获取到st中
            //     用于增加报头中的字段Content-Length
            struct stat st;
            stat(html_file.c_str(), &st);

            // 2、构建响应报文的报头
            std::string http_response = "http/1.0 200 OK\n"; //状态行
            http_response += "Content-Type: text/html; charset = utf8\n";
            http_response += "Content-Length: ";
            http_response += std::to_string(st.st_size);
            http_response += "\n";
            http_response += "\n"; //空行

            // 3、获取实体主体
            std::string content;
            std::string line;
            while (std::getline(in, line))
            {
                content += line;
            }
            // 4、将实体主体加入响应报文
            http_response += content;

            in.close();
            //发送响应报文
            send(sockfd,http_response.c_str(), http_response.size(), 0);
        }
    }

    //完成后,关闭用于处理请求的套接字文件描述符
    close(sockfd);
    return nullptr;
}

【浏览器请求GET方法时的现象】

在这里插入图片描述浏览器提交表单时,浏览器的变化:
在这里插入图片描述
提交表单,服务器接收到的请求报文:
在这里插入图片描述

【浏览器请求POST方法的现象】

在这里插入图片描述
浏览器提交表单时,浏览器的变化:
在这里插入图片描述
提交表单,服务器接收到的请求报文:
在这里插入图片描述

GET、POST方法的总结】
总结一(概念):

  • GET方法,作用是获取资源(下载),是最常用的获取资源方法;默认获取所有的网页,都是GET方法。如果GET需要提交参数,通过URL进行参数拼接,从而提交给服务端。
  • POST方法,作用是推送资源(上传),是最常用的提交参数方法;如果GET需要提交参数,不通过URL的参数拼接,而是直接通过正文(实体主体)提交。不要忘记首部行的字段Content-Length

总结二(区别):

  • 参数提交的位置不同,POST方法比较私密(私密 != 安全),不会回显到浏览器的URL输入框;GET方法不私密,会把重要的信息回显到浏览器的URL输入框。
  • GET通过URL传参,URL有大小限制,不同的浏览器可能会有不同的限制;POST方法由实体主体传参,无大小限制。

状态码

状态码类别原因短语(描述状态码)
1XXXInformational(信息性状态码)接收的请求正在处理
2XXXSuccess(成功状态码)请求正常处理完毕
3XXXRdirection(重定向状态码)需要进行附加操作以完成请求
4XXXClient Error(客户端错误状态码)服务器无法处理请求
5XXXServer Error(服务器错误状态码服务器处理请求出错

重点提及3XXX。
在这里插入图片描述
如果响应报文的状态码是3XXX,那么响应报文的首部行就会有一个字段Location,这个字段包含重定向到哪一个文档的URL。重定向是浏览器支持的,使用的浏览器必须能够识别301、302、307状态码。
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdlib>
#include "sock.hpp"
#include <pthread.h>
#include <unistd.h>
#include <fstream>
#include <sys/stat.h>

#define WWWROOT "./WebRoot/"
#define HOME_PAGE "index.html"

void UsePage();
void *HandleRequest(void *arg);

//命令格式:./server server_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        UsePage();
        return 1;
    }
    uint16_t port = atoi(argv[1]);
    int listen_sockfd = Sock::GetSock();
    Sock::Bind(listen_sockfd, port);
    Sock::Listen(listen_sockfd);

    std::cout << "server ready……" << std::endl;
    while (true)
    {
        int new_sock = Sock::Accept(listen_sockfd);
        if (new_sock > 0)
        {
            //创建一个从属线程,处理客户请求
            int *pram = new int(new_sock);
            pthread_t pid;
            pthread_create(&pid, nullptr, HandleRequest, pram);
        }
    }
    return 0;
}

void UsePage()
{
    std::cout << "命令格式:./server server_port" << std::endl;
}
void *HandleRequest(void *arg)
{
    int sockfd = *(int *)arg;
    delete (int *)arg;
    pthread_detach(pthread_self());

    //接收客户端请求
    char buffer[1024 * 10];
    memset(buffer, 0, sizeof(buffer));
    ssize_t s = recv(sockfd, buffer, sizeof(buffer), 0);

    //成功读取到请求报文,可以构建一个响应报文给客户
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << buffer; //查看http的请求格式
        //构建响应报文
        std::string response = "http/1.1 301 Permanently moved\n";
        response += "Location: https://www.qq.com/\n";
        response += "\n";
        send(sockfd, response.c_str(), response.size(), 0);
    }
    //完成后,关闭用于处理请求的套接字文件描述符
    close(sockfd);
    return nullptr;
}

上面发送的响应报文就是:
在这里插入图片描述
当我们运行服务器,然后在浏览器的URL输入框中输入IP地址:端口号,就会跳转到https://www.qq.com/这个网站。

首部行字段

上面解释了部分首部行字段,这里给出较为常见首部行字段的清单:

  • Content-Type:数据类型
  • Content-Length:(正文)实体主体的字节数
  • Location:配合3XXX状态码使用,告诉客户端接下来要去哪里访问
  • Host:客户端告诉服务器,它所请求的资源在哪一台主机上的哪一个端口
  • User-Agent:声明请求用户的操作系统、浏览器的版本信息
  • Referer:表明当前页面是从哪一个页面跳转过来的
  • Cookie:用于在客户端存储少量信息。
  • Connect:表明使用的连接方式是持续连接还是短链接。

Cookie和Session

HTTP协议是无状态的(HTTP服务器是无状态服务器),HTTP服务器会把每一次客户请求作为与之前任何请求都无关的独立事务,不需要记住曾经访问过的某个客户信息。无状态简化了HTTP服务器的设计,使得它更为容易支持大量并发的HTTP客户请求。
在这里插入图片描述
当我们进行各种页面跳转的时候,本质是浏览器进行各种请求。http协议本身是无状态的,但我们发现网站是“认识我”的,这提高了用户访问网站或者平台的体验。这项工作并不是http协议本身要解决的,但http可以提供一些技术支持,来保证网站具有“会话保持”的功能。

这个工作由Cookie完成。

  • 对于浏览器:
    Cookie是存储在用户主机的文本文件,记录了一段时间内某用户的私密信息。
  • 对于http
    一旦该网站对应有Cookie,在发起任何请求的时候,都会在request中携带Cookie信息。

响应报文的首部行可以包含一个字段Set-Cookie,将Cookie信息设置进浏览器的Cookie文本文件。
在这里插入图片描述
多次请求:
第一次浏览器请求的时候,没有Cookie信息,所以请求时没有携带Cookie信息。
第一次请求后,服务器将Cookie信息设置进浏览器的Cookie文件。
在这里插入图片描述

后续请求的时候,浏览器有Cookie信息了,请求的时候就把Cookie信息携带上
在这里插入图片描述
单纯地使用Cookie是有安全隐患的,如果被人盗取了Cookie文件:
1、盗取者可以我的身份进行认证访问特定的资源。
2、如果保存的是我们的用户名和信息,那么情况就更加糟糕。

所以Cookie一般和Session配套使用,Session是保存在服务器上的文本文件。
在这里插入图片描述
每当有一个用户认证的时候服务器就会生成一个唯一的Session文件,用户保存一段时间内用户的私密信息。然后服务器将Session文件的id等设置进用户的Cookie文件。

虽然这样避免了Cookie明文地保存用户的私密信息,但还是有被盗取的风险,虽然没有办法避免被盗取,但现在已经衍生出了许多应对的办法。例如,认证的时候,可以检查IP归属地,如果不在用户经常登录的IP归属地,就禁止登录访问/要求重新认证。

HTTPS

HTTPS是HTTP+TLS/SSL的结合。数据在网络中总是要进行加密的,而TLS/SSL是http数据的加密解密层。
在这里插入图片描述

加密方式

有两种加密的方式:

  • (1)对称加密。
    对称加密,只有一个密钥M,使用M对数据加密,也用M解密得到数据。

做个假设:data是传送的数据。
加密:result = data ^ M;
解密:data = result ^ M;
这不是具体过程,仅仅作为一个抽象的理解。

  • (2)非对称加密。
    非对称加密,使用一对密钥:公钥私钥
    如果使用公钥对数据加密,那么就用私钥解密;如果使用私钥对数据加密,那么就用公钥解密。一般而言,公钥对于全世界是公开的,而私钥必须自己私有保存。

如何选择加密算法?
假设单纯使用对称加密:
在这里插入图片描述
这并不是安全的,因为在将密钥传给对方的时候,密钥此时是数据并且没有被加密,盗取者能够拿到密钥,并使用密钥对数据解密,那么后续的加密工作就毫无作用。

假设单纯使用非对称加密:
在这里插入图片描述
在将公钥传给对方的时候,公钥此时是数据并且没有被加密,盗取者能够拿到公钥,但我们并不使用公钥解密,所以没有多大关系。这种方式数据被盗取的风险就远小于单纯使用对称加密的方式,但是它相对于前者要消耗了太多的时间。

一种结合了上述优点的加密算法:对称加密 + 非对称加密。
在这里插入图片描述

只需要一对公钥+私钥,其目的是将密钥加密传给对方。后续只需要这一个密钥就可以安全地传送数据了。

注意:
安全的含义指,即使盗取者拿到了加密后的内容,也没有办法拿到里面的数据。

补充知识

如何防止文本在传输的过程中数据被篡改?以及识别到文本内容是否被篡改。

对文本加密过程:
在这里插入图片描述
不同的文本,哪怕只是一些标点符号不同,形成的Hash结果都会不同。

识别文本内容是否被篡改:
收到文本方的校验
在这里插入图片描述

证书

采用上述的对称加密+非对称加密的方式仍然是不安全的,因为可能会有中间人获取到数据。中间人就是哪些试图在别人进行网络通信中截取数据的“人”。

在这里插入图片描述
在密钥协商阶段,其中一方server将自己的公钥放在报文内,试图传送给另一方client。这个过程报文内的数据是没有被加密的,所以中间人可以截取到报文,拿到公钥G并将原本要发送的公钥G,修改成了中间人自己的公钥G2, 这样client拿到的就是公钥G2而且它对此并不知情。client方拿到公钥G2后就开始使用公钥G2对密钥进行加密,然后把含有加密后的密钥的报文发送给另一方server。这个过程中,中间人就可能会将该报文截取,并用中间人自己的私钥S2对数据解密,拿到client方的密钥。中间人再用之前截取到的公钥G对密钥重新加密,并重新封装成报文发送给server方,server方对此也并不知情。密钥都被中间人拿到了!!! 那么数据就不安全了。

上面情况的本质就是:client无法判定发来的密钥协商报文是不是从合法的服务方发来的,也无法判定发来的密钥协商报文有没有被其他人修改过。

针对这种可能出现的问题引入了证书

证书由名叫“CA”的机构负责签发、认证和管理,所以人们也将证书称为CA证书
证书的流程:
在这里插入图片描述

  • 第一步:服务方向CA机构申请证书。

  • 第二部:CA机构根据服务方的基本信息创建一个证书。例如,域名、公钥等。
    创建证书的过程如下:
    在这里插入图片描述
    CA机构有自己的公钥和私钥,使用CA自己的私钥对数据摘要加密形成数字签名。CA的私钥只有它自己知道,换而言之,整个世界上只有CA能够重新形成对应的数字签名。

  • 第三步:将生成的证书颁发给申请的服务方。

证书能够保证数据的安全的原理:
在这里插入图片描述
client能够对数字签名解密,是因为它有CA的公钥(数字签名用CA的私钥加密,而解密用CA的公钥)。
1、CA的公钥一般是内置的。
2、访问网址的时候,浏览器会提示用户进行安装。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小酥诶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值