Linux网络编程:HTTP协议

目录

一、HTTP协议介绍

1.认识URL

2.urlencode和urldecode

二、HTTP的协议格式

1.HTTP协议的请求格式

2.HTTP协议的响应格式

​编辑

三、HTTP协议的请求方法

四、HTTP的状态码

302例子

五、HTTP常见的报头属性

六、Connection字段

七、cookie和session


一、HTTP协议介绍

虽然我们说,应用层协议是我们程序猿自己定的.但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用.

**HTTP就是其中之一。在互联网世界中,HTTP(HyperTextTransfer Protocol),又叫超文本传输协议,是一个至关重要的协议。**它定义了客户端(如浏览器)与服务器之间如何通信,以交换或传输超文本(如HTML文档)。

当我们在使用浏览器上网时,比如查阅文档、看视频听音乐等,这些都是以网页的形式呈现出来的,网页本质也是文件,它是以.html为后缀的文件 ,当我们用浏览器浏览网页时,浏览器作为客户端会向服务器发起HTTP请求,服务器会将网页文件响应回给客户端 。所以HTTP协议就是向特定的服务器申请特定资源,把这些资源获取到本地进行展示或操作的。这里所说的资源指的是比如网页资源、视频资源、音频资源,当然它们的本质也是文件。

1.认识URL

平时我们俗称的"网址"其实就是说的URL。

URL(Uniform Resoure Locator)叫作统一资源定位符,一般由下面这几部分组成:协议方案名、登录信息、服务器地址、服务器端口号、带层次的文件路径、查询字符串、片段标识符。

如同我此刻正在写文章的网页一般。

  • **协议方案名:**表示采用的是什么协议。
  • **登录信息:**表示用户的登录信息,这个字段一般可以不需要我们自己输入,现在的网页一般只需要我们输入账号密码,或者快捷登录,浏览器客户端拿到我们的登录信息之后自动帮我们做转换并填充URL中的这一个字段,所以这一字段一般是省略的,不需要我们显式地去写在URL上。
  • 服务器地址: 这个字段是必须有的,它表示的是服务器的IP地址,虽然我们平时浏览的网页看到的这一字段并不是IP地址的形式,但这是因为网页用域名代替了它的IP地址域名方便记忆,浏览器客户端自动会帮我们将域名转换成对应的IP地址。
  • 服务器端口号: 服务器地址后冒号紧跟的是服务器端口号。使用网络服务必须有IP地址和端口号,因为网络通信的本质就是套接字通信。**一般网页的URL我们是看不到端口号的,原因是使用确定协议的时候,端口号是缺省的,虽然URL上没有显示出来,但浏览器客户端在发起网络请求的时候会自动帮我们添加上缺省的端口号。**特定的众所周知的服务,它的端口号必须是确定的比如HTTP服务的端口号是80,HTTPS服务的端口是443。
  • 带层次的文件路径: 表示的是服务器上要返回给客户端的资源的路径,比如服务器要返回网页文件给浏览器,这里就要填上该网页文件在服务器上对应的路径。
  • **查询字符串和片段标识符:**这两个都是在问号后面的,在URL中问号后面的代表的是一些参数,参数的类型非常多样。

2.urlencode和urldecode

在URL中我们经常会看到一些**/、?、:**等这样的字符,他们是拥有特殊含义的。因此这些参数就不能随随便便的出现,一旦出现我们就要对其转义。

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

当我们在百度上搜索C++和CPP的时候

这里wd就是word的缩写,代表的就是我们搜索的关键词,当关键词是C++的时候,由于加号字符在URL中是有特殊含义的,所以它被转义了,当关键词是CPP的时候就没有被转义。

"+"被转义成了"%2B",转义urldecode就是urlencode的逆过程;

urlencode网页测试

我们通过网页测试发现,确实是这样。

二、HTTP的协议格式

1.HTTP协议的请求格式

HTTP是基于行格式的一套协议,第一个部分也是第一行,叫作请求行 ,包括三个字段,每个字段以空格分开,分别是请求方法、URL和HTTP版本请求方法常见的是GET方法和POST方法,URL这里一般是去掉域名和端口号的URL,最后请求行以\r\n结尾。

第二部分由很多行构成,叫作HTTP的请求报头 ,这里面放的都是请求报头的属性。这些属性都是key: value格式的,前面是key值,key值后面紧跟一个冒号,冒号后跟一个空格,空格后跟value值。

第三部分的内容只有\r\n ,它其实是一个空行,这行其实起到的是类似于分隔符的作用,在别人解析HTTP请求的时候,只要读到了这个空行,就代表读取完了HTTP请求前半部分的请求行和报头数据。

第四部分是在空行以后,代表的是有效载荷 。也叫作请求正文,这部分的内容一般比如包括用户的登录账号和密码,用户的个人信息,音频资源、视频资源等等。

我们可以用代码演示一下HTTP协议的请求报文,我们写一个TCP服务器,让浏览器作为客户端以HTTP协议请求服务端,服务端读取客户端发送过来的信息并把它打印出来即可。

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>

using namespace std;

class TcpServer;

struct ThreadData
{
    int _sock;
    TcpServer *_server;

    ThreadData(int sock, TcpServer *server)
        : _sock(sock), _server(server)
    {
    }
};

class TcpServer
{
public:
    TcpServer(uint16_t port, const string &ip = "")
        : _listen_socket(0), _port(port), _ip(ip)
    {
    }

    ~TcpServer()
    {
    }

public:
    void init()
    {
        // 1.创建套接字
        _listen_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_socket < 0)
        {
            cerr << "socket error" << endl;
            exit(1);
        }
        cout << "socket success" << endl;

        // 2.bind
        // 2.1填充网络信息
        sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        // 2.2bind网络信息
        if (bind(_listen_socket, (const sockaddr *)&local, sizeof(local)) < 0)
        {
            cerr << "bind error" << endl;
            exit(2);
        }
        cout << "bind success" << endl;

        // 3.listen
        if (listen(_listen_socket, 5) < 0)
        {
            cerr << "listen error" << endl;
            exit(3);
        }
        cout << "listen success" << endl;
    }

    void start()
    {
        while (true)
        {
            // 1.accept
            sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t peer_len = sizeof(peer);
            int accept_socket = accept(_listen_socket, (sockaddr *)&peer, &peer_len);
            if (accept_socket < 0)
            {
                continue;
            }

            // 获取连接成功,开始提供服务
            // 创建多线程,主线程负责accept获取连接,新线程负责提供服务
            pthread_t tid;
            ThreadData *td = new ThreadData(accept_socket, this);
            pthread_create(&tid, nullptr, httpServer, (void *)td);
        }
    }

public:
    static void *httpServer(void *args)
    {
        ThreadData *td = (ThreadData *)args;
        while (true)
        {
            char buffer[10240];
            ssize_t readRes = read(td->_sock, buffer, sizeof(buffer) - 1);
            if (readRes > 0)
            {
                buffer[readRes] = '\0';
                // 将请求打印出来
                cout << buffer << endl;
            }
        }
    }

private:
    int _listen_socket;
    uint16_t _port;
    string _ip;
};

// ./server port ip
int main(int argc, char *argv[])
{
    // 命令行参数的格式输入错误
    if (argc != 2 && argc != 3)
    {
        cerr << "argc error,usage:./server port ip";
        exit(4);
    }
    uint16_t server_port = (uint16_t)atoi(argv[1]);
    string server_ip = "";
    if (argc == 3)
    {
        server_ip = argv[2];
    }

    TcpServer svr(server_port, server_ip);
    svr.init();
    svr.start();

    return 0;
}

运行代码启动服务器以后,我们让浏览器以HTTP协议请求我们的服务器,就可以看到它发送过来的请求报文:

我们可以看到它是以Get方式发起请求的,HTTP版本是HTTP1.1,Host指的是服务器主机的地址和端口号,Connection指的是请求是所采用的连接方式。

2.HTTP协议的响应格式

HTTP的响应格式也是以行为单位的,并且相应格式也是包含四个部分:

第一部分也是第一行,叫作状态码字段,它用来描述这一次HTTP响应的状态是什么,这一行的具体内容首先是HTTP协议的版本,然后紧跟一个空格,空格后面是状态码,状态码后面紧跟一个空格,空格后面是状态码描述,描述该状态码代表什么含义。

这里的HTTP协议的版本和请求格式中的HTTP协议版本不一定一样 ,举个例子,如果客户端是老的客户端,但我们服务器更新了内容,我们需要保证的是如果客户端没有更新,依然能正常使用但是不能看到新的内容,只有更新了客户端才能看到新的内容。这种操作其实就是在版本号这里做判断,如果客户端发来的请求中,版本号是老的版本号,我们就提供对应老版本号的服务给它,如果版本号是新的,我们就提供新版本好的服务给它。

后三部分和请求格式差不多就不过多赘述了。

cpp 复制代码
static void *httpServer(void *args)
{
    ThreadData *td = (ThreadData *)args;
    while (true)
    {
        char buffer[10240];
        ssize_t readRes = read(td->_sock, buffer, sizeof(buffer) - 1);
        if (readRes > 0)
        {
            buffer[readRes] = '\0';
            // 将请求打印出来
            cout << buffer << endl;
        }

        // 开始响应回给客户端
        // 添加状态码行
        string response_package = "HTTP/1.0 200 OK\r\n";
        // 添加空行
        response_package += "\r\n";
        // 添加响应正文
        response_package += "hello world, test for http";

        // 将响应发回给客户端
        send(td->_sock, response_package.c_str(), response_package.size(), 0);
    }
}

我们再在上述代码基础上增加响应代码。

三、HTTP协议的请求方法

我们利用网络上网的行为可以分为两种:

  1. 把远端的资源拿到本地,比如我们上面演示的获取网页资源、获取图片资源等。
  2. 把我们的属性字段提交到远端,比如把我们的搜索关键字提交到远端,把我们的登录信息提交到远端。

网站一般被分为静态网站和动态网站**,静态网站就是只能把远端的东西拿到本地** ,不能提交资源到远端,没有交互式的网站;相反的,动态网站就是既能把远端的东西拿到本地,也可以提交资源到远端,就是具有交互式的网站,比如我们写博客,比如CSDN和GitHub就是一个交互式的动态网站

其中最常用的就是GET方法和POST方法。将远端资源拿到本地可以采用GET方法,将本地资源提交到远端可以采用GET方法或者POST方法。

那么,GET和POST方法有什么区别?

  1. GET方法是通过URL进行传参的,客户端的参数会以明文的形式显示在URL中。
  2. POST方法是通过正文进行传参的,客户端的参数不会显示在URL中,但是会出现在请求正文中。
  3. 所以GET方法是不私密的,也是不安全的,比如我们在网页输入账号密码的时候,这些会作为参数显示在URL中。POST方法因为是通过正文传参,所以相对比较私密,但它也是不安全的,比如我们在网页输入的账号密码,虽然URL中不会显示出来,但如果别人用一些抓包工具抓取到了你的请求正文,也是可以轻而易举地获取你的账户信息的。

四、HTTP的状态码

出自《图解 HTTP》

  • 1XX:以数字1开头的这一类状态码是信息性状态码,表示当前服务器正在处理接收的请求,所以如果等待时间比较长的话,服务器可以响应这一类状态码表示请求正在处理,耐心等待。
  • 2XX:以数字2开头的这一类状态码是成功状态码,表示当前服务器已经正常处理完毕客户端的请求。
  • 3XX:以数字3开头的这一类状态码是重定向状态码 ,表示需要附加操作以完成请求。目前浏览器主流接受的重定向状态码就只有两个,一个是301另一个是302
  • 301是永久重定向,它的应用场景一般是某些网站因为域名更改了,为了让老用户从老域名也能访问到新网站,就使用永久重定向,当别人访问老域名时会自动跳转到新域名。
  • 302是临时重定向,它的应用场景一般是某些网站要更新资源时,为了不让网站停机,会创建一个临时重定向让用户访问原来的资源,当新资源更新好了以后再撤销这个临时重定向。
  • 4XX:以数字4开头的这一类状态码是客户端错误状态码,表示服务器无法处理请求。比如我们经常见到的404状态码。
  • 5XX:以数字5开头的这一类状态码是服务器错误状态码,表示服务器处理请求时服务器出错了。

302例子

302我们生活中经常遇到,但是不会仔细注意,基本上是无感知的。我们举个例子:

当你在淘宝刷到一个「618 限时特惠」的链接,地址是:https://www.taobao.com/618sale

但实际上,淘宝的活动页面可能还在紧急调整细节(比如改价格、加库存),为了不让用户访问时看到 "页面维护中",技术人员会做这样的操作:

  1. 临时重定向 :把 https://www.taobao.com/618sale 用 302 重定向到 原来的、稳定的商品列表页 https://www.taobao.com/list
  2. 用户体验:你点击链接后,会直接进入熟悉的商品列表页,完全感觉不到跳转,网站也不会停机。
  3. 活动上线后 :技术人员撤销 302 重定向,https://www.taobao.com/618sale 直接指向新的活动页,用户点击链接就能看到特惠内容。

五、HTTP常见的报头属性

  • Content-Type:数据类型(text/html等)
  • Content-Length:Body的长度
  • Host:客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上
  • User-Agent:声明用户的操作系统和浏览器版本信息
  • referer:当前页面是从哪个页面跳转过来的
  • location:搭配3xx状态码使用, 告诉客户端接下来要去哪里访问
  • Cookie:用于在客户端存储少量信息. 通常用于实现会话(session)的功能

六、Connection字段

在HTTP协议的请求和响应报文中,有一个Connection字段,它表示HTTP协议是长链接还是短链接 ,在HTTP/1.0版本中是基于短链接的,所谓的短链接(closed)就是客户端发起一次请求,服务端响应之后就关闭链接了,短链接一次只处理一个HTTP请求。 我们日常看到的网页,它可能这一张网页就发起了几十次上百次的HTTP请求,HTTP协议底层采用的是TCP协议每一次链接都需要在底层经历三次握手,断开链接都需要经历四次挥手。

所以短链接就不再适合现在主流的HTTP请求了,我们采用主流的长链接。

采用长链接的方案,客户端和服务端在建立网络连接之前先要进行HTTP版本和连接方式协商,如果版本号一致并且Connection 字段是keep-alive代表双方采用长链接的方式

双方建立链接之后,服务端不会在处理完一个请求之后就断开链接,而是接收客户端的多次HTTP请求,按顺序处理之后再响应回给客户端。当所有的请求完毕时才会断开链接。

  • 在HTTP/1.1协议中,默认使用持久连接。当客户端和服务器都不明确指定关闭连接时,连接将保持打开状态,以便后续的请求和响应可以复用同一个连接。
  • 在HTTP/1.0协议中,默认连接是非持久的。如果希望在HTTP/1.0上实现持久连接,要在请求头中显式设置Connection:keep-alive。

七、cookie和session

HTTP是无状态,无连接的,HTTP协议有一个特点就是,用户的访问行为,HTTP协议不会记录。但是我们会发现,我们在使用浏览器上网的时候,浏览器是会有记录我们的访问行为的,当我们第一次登入B站的时候需要输入账号密码,但是第二次就不需要了,那么B站是如何认识我这个登录用户的?或者说HTTP是怎么能够记住我的?

本质是通过cookie实现的。

当我们用浏览器作为客户端访问服务器的时候,如果需要登录,第一次登录我们需要在客户端输入用户名和密码,客户端再将用户名和密码发送回给服务端,服务端拿到用户名和密码后去数据库中比对,如果查找到了该用户且密码正确就可以成功登录,成功登录以后服务端会将成功登录的信息响应回给客户端,客户端拿到这些信息就会生成对应的cookie文件 ,该文件中就保存着用户名和密码。在下一次客户端发起HTTP请求的时候,它会自动携带cookie文件的内容,也就不再需要我们输入用户名和密码了。

但是cookie策略是有安全隐患的,如果cookie文件保存了过于私密的信息,一旦cookie文件泄露出去,可能会对我们的数据安全或者财产安全产生很大影响。所以现在主流的会话保持策略是cookie+session方案。

cookie+session的方案是当用户第一次登录输入用户名和密码时,服务端会帮我们生成一份session文件用来保存用户的私密信息,并且会为这个session文件生成唯一的文件名,也就相当于生成了一份唯一的文件编号。然后服务端再将成功登录的信息响应回给客户端,并且会将session文件的对应编号值发送给客户端,客户端收到之后就会生成cookie,此时客户端的cookie文件就不再保存用户的私密信息了,而是将session文件的编号值保存在cookie文件中。下一次访问服务器的时候,客户端发送cookie文件中的编号值即可。也就是说,这种方案是将原来保存在客户端本地的用户私密信息保存到了远端的服务器上。

相当于去游泳馆,你的贵重物品不再带在身上,而是寄存在柜子里,你拿到的只是一个手环带着柜子的编号,当需要认证的时候,你只要去柜子处用手环认证就可以了。

相关推荐
duration~2 小时前
IPv6 详解
网络·网络协议·ip
Minecraft红客2 小时前
ai_dialogue_framework项目1.0(纯原创)
c++·测试工具·电脑
广东大榕树信息科技有限公司2 小时前
动环监控如何有效提升机房环境管理的可靠性与响应速度?
运维·网络·物联网·国产动环监控系统·动环监控系统
txzz88882 小时前
CentOS-Stream-10 搭建NTP服务器(一)
linux·服务器·centos·ntp服务
Java中文社群2 小时前
避坑指南!别再被N8N循环节点“调戏”了!为什么你的Done分支执行了多次?
人工智能·后端
superman超哥2 小时前
仓颉元编程进阶:编译期计算能力的原理与深度实践
开发语言·后端·仓颉编程语言·仓颉·仓颉语言·仓颉元编程·编译器计算能力
凌览2 小时前
2025年,我和AI合伙开发了四款小工具
前端·javascript·后端
挖矿大亨2 小时前
C++中的赋值运算符重载
开发语言·c++·算法