Linux:http协议初步认识

news2024/12/24 1:03:44

文章目录

  • OSI七层模型
  • http协议
    • 域名
    • 路径信息
    • 请求和响应
  • 编写一个httpserver

OSI七层模型

在结束了前面对于序列化反序列化等内容的学习后,重新回到对于OSI模型的部分

在这里插入图片描述
在这里插入图片描述
如上所示的是对于OSI接口的示意图,在这当中可以看到会话层的概念,会话层的作用主要是建立断开连接管理的,相当于是在网络服务器中在TCP server中获取链接的过程,如果不需要这个链接,就在TCP server中把对应的链接关掉,而下面的表示层中写的是固有的数据格式和网络数据的转换,比如有接受不同形式的信息,文字,图像,声音等等,这个表示层其实就是在前面的部分中定义的协议

这个协议表示的就是固有的数据格式,比如说是序列化和反序列化这些内容,再比如有说网络序列转换为主机序列,或者是在缓冲区中定义了各种视频的名称格式等等,这些都是属于自定义协议的范畴,因此这些内容都是属于是表示层当中的信息

最后是应用层,应用层中有针对于特定应用的协议,比如说有电子邮件远程传输的问题,再比如有传输数据解析的问题,这些其实都是在应用层需要解决的问题

所以说这个OSI的这些层次结构就是已经提前定制好了未来的发展方式,把网络中需要的各个功能和网络中每一层的能力就都定制好了,那在表示层和应用层层相对比较好理解一些,无非就是定制协议的过程,那对于会话层的理解该如何深入理解呢?

对于会话的概念并不陌生,在将Linux终端的时候就有提及过这个session的概念,而当客户端链接到服务端后,如果一直不给服务器发送数据,那么对应的服务端就会一直阻塞在这里,那未来可以设置对应的一些策略,比如说超过5秒钟部进行发送消息,就把对应的链接自动关闭

因此,在定制OSI模型的时候,实际上会话表示应用这三层是不能定义在内核当中的,原因就是无论是这些当中的那一层,最终都必须要根据使用场景的不同,由程序员自己定义出对应的协议信息,然后才能有对应的序列化和反序列化的方法,如果用操作系统来全部实现,自然会引起一定的问题,所以就都交给用户自己来进行实现

http协议

域名

在前面的认知中,对于访问网络服务的概念是,可以使用一个ip地址和端口号来访问唯一一台主机上的唯一的一个进程,所以我们才可以用ip加端口号的方式来进行访问,但是现在的问题是,平时使用访问的都是使用一个网址来进行访问,没有使用端口号,这该如何理解呢?

事实上,的确在技术上用ip地址和端口号就可以实现这样的功能,但是在日常生活中作为用户会直接使用一个域名的内容来进行访问网站,因为域名的名字相比起来更加的好记忆,尤其是对于一些大型的网站都是直接用拼音来进行标注,方便用户来进行访问信息

所以就诞生了域名的概念,域名实际上在背后会被解析为对应的服务,这个技术上叫做是域名解析的概念,通过这个技术就能实现把域名转换为ip地址,因此当浏览器访问了特定的域名的时候,浏览器内部会内置一些地址,就能访问对应的域名解析的服务,这样就能转换为ip地址,我们以下面的这个例子为例

当访问百度一下这个网址的时候,都是直接访问这个www.baidu.com这个网址,而当在浏览器中访问的时候,其实在背后已经把这个域名转换成ip地址了,那这个怎么看呢?

在命令行中可以使用这样的命令来进行查看

在这里插入图片描述
由此我们就可以根据这个信息来进行访问百度了

在这里插入图片描述
https协议默认的端口号是80,所以本质上访问的是39.156.66.18:80/,关于这个问题后续再进行详谈

路径信息

对于每一个网址来说,其实是有固定的规则的:

在这里插入图片描述
未来会对带层次的文件路径进行解析,其他的字段不进行额外的讲解

请求和响应

下一步要谈论的主题是,关于http中的请求和响应的问题

http请求

http的请求是有固定格式的,下图画出http请求的具体格式:

在这里插入图片描述

下图展示的是http的一个请求:

在这里插入图片描述
如图所示的是http的一个请求,第一行表示的是POST方法,后面带有对应的URL网址信息,还有http协议版本,还有换行分割符这些信息,后续的内容都是对应的是请求报头,其中包含有对应的Key和Value信息,在结束之后是一个空行,也是作为一个分割的作用,最后一行的信息表示的是请求的正文,在这里表示的是一个用户名和密码的信息,后续会实现这个内容

这里对于这个\r\n进行一个理解:这两个表示的是,对于请求来说都是按行读取,这样可以读取到完整的一行,读取到完整的一行之后就能对于报头和有效载荷进行分离,进而进行后续的一些操作,那么现在的问题是在对于读取网络请求的时候,如何保证读取到的是一个完整的http请求呢?具体的做法是分析它的长度,并进行提取,这是我们之前的做法,而我们之前的做法本质上是一个自定义协议,而在对应的http协议如何保证?答案是保证不了,但是因为有这个\r和\n的存在,所以可以保证的是可以完整的读取到一个http的报头,之后就能进行解析,而在解析内容中就包含有长度这样的字段,所以说想要实现一个http的请求正确读取是一个非常繁琐的过程

http响应

那http响应该如何理解呢?在http的响应和请求几乎是完全一样的:

在这里插入图片描述

所以说此时对于请求和响应已经有了一个基本的视线了,那么下一步就是要进行序列化和反序列化,然后再进行对应的分割就可以,这都是可以解决的问题,所以在未来进行使用http实现网络通信的时候,就是一个发送请求,进行响应,再发送请求,再进行响应的一个过程

编写一个httpserver

那么下一步就直接进行编写一个httpserver来进行测试,这里我就直接用一份已经完备的httpserver来进行测试,主要想法是根据实验现象来理解http背后的细节原理

// socket.hpp
#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"

enum
{
    SocketErr = 2,
    BindErr,
    ListenErr,
};

// TODO
const int backlog = 10;

class Sock
{
public:
    Sock()
    {
    }
    ~Sock()
    {
    }

public:
    void Socket()
    {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        {
            lg(Fatal, "socker error, %s: %d", strerror(errno), errno);
            exit(SocketErr);
        }
        int opt = 1;
        setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    }
    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
            exit(BindErr);
        }
    }
    void Listen()
    {
        if (listen(sockfd_, backlog) < 0)
        {
            lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
            exit(ListenErr);
        }
    }
    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
        if(newfd < 0)
        {
            lg(Warning, "accept error, %s: %d", strerror(errno), errno);
            return -1;
        }
        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
        *clientip = ipstr;
        *clientport = ntohs(peer.sin_port);

        return newfd;
    }
    bool Connect(const std::string &ip, const uint16_t &port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

        int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
        if(n == -1) 
        {
            std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
            return false;
        }
        return true;
    }
    void Close()
    {
        close(sockfd_);
    }
    int Fd()
    {
        return sockfd_;
    }

private:
    int sockfd_;
};

#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"

const std::string wwwroot = "./wwwroot"; // web 根目录
const std::string sep = "\r\n";
const std::string homepage = "index.html";

static const int defaultport = 8082;

class HttpServer;

class ThreadData
{
public:
    ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
    {
    }

public:
    int sockfd;
    HttpServer *svr;
};

// 定义一个http的请求信息
class HttpRequest
{
public:
    // 对信息进行反序列化
    void Deserialize(std::string req)
    {
        while (true)
        {
            std::size_t pos = req.find(sep);
            if (pos == std::string::npos)
                break;
            std::string temp = req.substr(0, pos);
            if (temp.empty())
                break;
            req_header.push_back(temp);
            req.erase(0, pos + sep.size());
        }
        // 存储请求体的信息
        text = req;
    }
    // .png:image/png
    // 对信息进行解析
    void Parse()
    {
        // 解析请求的方法,URL,http版本
        std::stringstream ss(req_header[0]);
        ss >> method >> url >> http_version;
        // 设置默认寻找的路径是wwwroot这个目录
        file_path = wwwroot; // ./wwwroot

        // 对于URL文件的路径进行一定的拼接
        if (url == "/" || url == "/index.html")
        {
            file_path += "/";
            file_path += homepage; // ./wwwroot/index.html
        }
        else
            file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html

        // 查找文件的后缀,默认是html,否则就进行合适的拼接
        auto pos = file_path.rfind(".");
        if (pos == std::string::npos)
            suffix = ".html";
        else
            suffix = file_path.substr(pos);
    }

    // 打印调试信息
    void DebugPrint()
    {
        for (auto &line : req_header)
        {
            std::cout << "--------------------------------" << std::endl;
            std::cout << line << "\n\n";
        }

        // 打印当前识别出来的请求信息等等
        std::cout << "method: " << method << std::endl;
        std::cout << "url: " << url << std::endl;
        std::cout << "http_version: " << http_version << std::endl;
        std::cout << "file_path: " << file_path << std::endl;
        std::cout << text << std::endl;
    }

public:
    // 请求信息中至少要包含下面的内容
    std::vector<std::string> req_header;
    std::string text;

    // 解析之后的结果
    std::string method;
    std::string url;
    std::string http_version;
    std::string file_path; // ./wwwroot/a/b/c.html 2.png
    // 文件后缀
    std::string suffix;
};

// http服务端
class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport) : port_(port)
    {
        content_type.insert({".html", "text/html"});
        content_type.insert({".png", "image/png"});
    }
    bool Start()
    {
        // 进行套接字的创建,绑定,设置监听
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for (;;)
        {
            std::string clientip;
            uint16_t clientport;
            // 从监听信息中提起对应的ip和端口号信息,当有用户连接的时候会进行接受对应的信息
            int sockfd = listensock_.Accept(&clientip, &clientport);
            if (sockfd < 0)
                continue;
            lg(Info, "get a new connect, sockfd: %d", sockfd);
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this);
            // 创建线程去执行对应的handle响应方法
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }

    // 读取html的内容
    static std::string ReadHtmlContent(const std::string &htmlpath)
    {
        std::ifstream in(htmlpath, std::ios::binary);
        if (!in.is_open())
            return "";

        in.seekg(0, std::ios_base::end);
        auto len = in.tellg();
        in.seekg(0, std::ios_base::beg);

        std::string content;
        content.resize(len);

        in.read((char *)content.c_str(), content.size());
        in.close();

        return content;
    }

    // 将后缀名替换为描述信息
    std::string SuffixToDesc(const std::string &suffix)
    {
        auto iter = content_type.find(suffix);
        if (iter == content_type.end())
            return content_type[".html"];
        else
            return content_type[suffix];
    }

    // 处理http的请求信息
    void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        // 进行收消息的过程
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bug
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer << std::endl;

            // 将接受到的请求信息进行反序列化
            HttpRequest req;
            req.Deserialize(buffer);
            // 对于反序列化后的内容进行解析,解析为当前所需要的信息内容
            req.Parse();

            // 返回响应的过程
            std::string text;
            bool ok = true;
            text = ReadHtmlContent(req.file_path); // 失败?
            if (text.empty())
            {
                ok = false;
                std::string err_html = wwwroot;
                err_html += "/";
                err_html += "err.html";
                text = ReadHtmlContent(err_html);
            }

            // 设置响应状态行,默认失败是404状态
            std::string response_line;
            if (ok)
                response_line = "HTTP/1.0 200 OK\r\n";
            else
                response_line = "HTTP/1.0 404 Not Found\r\n";

            // 进行字符串的拼接
            // response_line = "HTTP/1.0 302 Found\r\n";
            std::string response_header = "Content-Length: ";
            response_header += std::to_string(text.size()); // Content-Length: 11
            response_header += "\r\n";
            response_header += "Content-Type: ";
            response_header += SuffixToDesc(req.suffix);
            response_header += "\r\n";
            response_header += "Set-Cookie: name=haha&&passwd=12345";
            response_header += "\r\n";

            // response_header += "Location: https://www.qq.com\r\n";
            std::string blank_line = "\r\n"; // \n

            std::string response = response_line;
            response += response_header;
            response += blank_line;
            response += text;

            // 把拼接好的字符串响应再发送回去,这样就完成了一次通信
            send(sockfd, response.c_str(), response.size(), 0);
        }
        close(sockfd);
    }
    static void *ThreadRun(void *args)
    {
        // 对该线程进行设置,使得主线程不必关心这个线程
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        // 去执行对应的handle方法
        td->svr->HandlerHttp(td->sockfd);
        delete td;
        return nullptr;
    }
    ~HttpServer()
    {
    }

private:
    Sock listensock_;
    uint16_t port_;
    std::unordered_map<std::string, std::string> content_type;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1538611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

简介:KMeans聚类算法

在机器学习中&#xff0c;无监督学习一直是我们追求的方向&#xff0c;而其中的聚类算法更是发现隐藏数据结构与知识的有效手段。聚类是一种包括数据点分组的机器学习技术。给定一组数据点&#xff0c;我们可以用聚类算法将每个数据点分到特定的组中。 理论上&#xff0c;属于同…

SQL Server 2008R2 日志文件大小设置及查询

SQL Server 2008R2 建立数据库存在日志无限增长问题&#xff0c;造成磁盘内存不足。本文解决这个问题&#xff0c;如下&#xff1a; 1.设置日志文件的最大大小 USE master; GO ALTER DATABASE [D_total] MODIFY FILE (NAME D_total_log, -- 日志文件的逻辑名称MAXSIZE 200…

LeetCode Python - 69. x 的平方根

目录 题目描述解法运行结果 题目描述 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&#xff0c;例如 pow(x, 0.…

稀碎从零算法笔记Day21-LeetCode:单词规律

题型&#xff1a;哈希表、字符串 链接&#xff1a;290. 单词规律 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给定一种规律 pattern 和一个字符串 s &#xff0c;判断 s 是否遵循相同的规律。 这里的 遵循 指完全匹配&#xff0c;例如&am…

先进电机技术 —— 何为轴电压?

一、特定场景举例 长线驱动指的是在电动机与变频器之间存在较长的连接电缆&#xff0c;尤其是在大型工业应用中&#xff0c;电机可能远离变频器几十米甚至上百米。这种情况下&#xff0c;变频器通过长线向电动机传输功率时&#xff0c;可能会加剧电机轴电压和轴电流的产生&…

Flutter-仿携程首页类型切换

效果 唠叨 闲来无事&#xff0c;不小心下载了携程app&#xff0c;还幻想可以去旅游一番&#xff0c;奈何自己运气不好&#xff0c;自从高考时第一次吹空调导致自己拉肚子考试&#xff0c;物理&#xff0c;数学考了一半就交卷&#xff0c;英语2B铅笔除了问题&#xff0c;导致原…

ensp静态路由综合实验(一)

实验拓扑&#xff1a; 实验目的&#xff1a; 1、R6为ISP&#xff0c;接口IP地址均为公有地址&#xff0c;该设备只能配置IP地址&#xff0c;之后不能再对其进行任何配置&#xff1b; 2、R1-R5为局域网&#xff0c;私有IP地址192.168.1.0/24&#xff0c;请合理分配&#xff1b;…

【图解物联网】第7章 物联网与可穿戴设备

7.1 可穿戴设备的基础 顾名思义&#xff0c;可穿戴设备就是指穿戴在身上的设备&#xff0c;因此&#xff0c;比起单独使用前面说的那些设备&#xff0c;可穿戴设备能够令服务更加贴近人们的生活。如果你想率先实现物联网服务&#xff0c;那么就可以选择使用可穿戴设备。 …

java业务需求——爆金币

假设我们要模拟金铲铲中塔姆的爆金币需求&#xff0c;我们该如何实现该需求呢&#xff1f; 所以假设下面具体场景&#xff1a; 1.在每一回合的15s中&#xff0c;该棋子不断被攻击。 2.该棋子被攻击时有十分之三的概率的会爆出一个金币&#xff0c; 3.每被攻击10次必爆一个金币…

HAL STM32G4 +TIM1 3路PWM互补输出+VOFA波形演示

HAL STM32G4 TIM1 3路PWM互补输出VOFA波形演示 ✨最近学习研究无刷电机驱动&#xff0c;虽然之前有使用过&#xff0c;但是在STM32上还没实现过。本文内容参考欧拉电子例程&#xff0c;从PWM驱动开始学习。 欧拉电子相关视频讲解&#xff1a; STM32G4 FOC开发实战—高级定时器发…

【光标精灵】让您享受鼠标皮肤多样化快捷更换

鼠标作为我们日常使用频率最高的“小伙伴”&#xff0c;扮演着至关重要的角色。尤其是在女生群体中&#xff0c;对于打造一个个性化、可爱的电脑桌面和软件界面的需求日益增长。然而&#xff0c;尽管电脑默认提供了一些可更换的光标图案&#xff0c;但仍显得有些单调和呆板。想…

Java练习题目3:输入一个学生的5门课成绩及对应的学分,计算该同学的加权平均分(WeightedAverageScore3)

每日小语 我们没有意识到惯用语言的结构有多大的力量。可以说&#xff0c;它通过语义反应机制奴役我们。 ——阿尔弗雷德科日布斯基 思考 输入5门课成绩&#xff0c;学分&#xff0c;加权平均分公式 [&#xff08;课程A成绩*课程A学分&#xff09;&#xff08;课程成绩B*课程…

GitHub gpg体验

文档 实践 生成新 GPG 密钥 gpg --full-generate-key查看本地GPG列表 gpg --list-keys关联GPG公钥与Github账户 gpg --armor --export {key_id}GPG私钥对Git commit进行签名 git config --local user.signingkey {key_id} # git config --global user.signingkey {key_id} git…

C语言 自定义类型:结构体

目录 前言 一、结构体类型 1.1 结构体的声明 1.2 结构体变量的创建和初始化 1.3 结构体的特殊声明 1.4 结构体的自引用 二、结构体的对齐 2.1 对齐规则 2.2 内存对齐的原因 2.3 修改默认对齐数 2.4 结构体传参 三、结构体实现位段 3.1 位段的内存分配 3.2 段的跨平…

打造校园环保展厅,让环保知识触手可及,环保意识自然生成!

环保问题是当下备受关注的话题&#xff0c;各类垃圾分类、绿色环保知识的推广宣传&#xff0c;也让社会公众开始加强对环境保护的意识&#xff0c;同时越来越多的学校也开始加强对学生环保理念的培养&#xff0c;并通过建设校园环保展厅来宣传绿色环保知识&#xff0c;引导学生…

使用专属浏览器在国内直连GPT教程

Wildcard官方推特发文说他们最近推出了一款专门为访问OpenAI设计的浏览器。 根据官方消息&#xff0c;这是一款专门为访问OpenAI优选网络设计的浏览器&#xff0c;它通过为用户提供专用的家庭网络出口&#xff0c;确保了快速、稳定的连接。 用这个浏览器的最大好处就是直接用浏…

免费PDF转换和编辑工具 PDFgear 2.1.4

PDFgear是一款功能强大的 PDF 阅读及转换软件。 它支持多种文件格式的转换和编辑&#xff0c;同时还提供了丰富的功能模块&#xff0c;如签名、表单填写等&#xff0c;方便用户进行多样化的操作。 该软件界面简洁美观&#xff0c;操作简单易懂&#xff0c;适合不同层次的用户…

Spark-Scala语言实战(3)

在之前的文章中&#xff0c;我们学习了如何在来如何在IDEA离线和在线安装Scala&#xff0c;想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&#xff0c;请留下你宝贵的点赞&#xff0c;谢谢。 Spark-Scala语言实…

SpringBoot 集成支付宝支付,看这篇就够了!

SpringBoot 集成支付宝支付,看这篇就够了! 1.进入支付宝开发平台—沙箱环境 2.点击沙箱进入沙箱环境 3.进入沙箱,配置接口加签方式 在沙箱进行调试前需要确保已经配置密钥/证书用于加签,支付宝提供了 系统默认密钥 及 自定义密钥 两种方式进行配置。这里我采取的是默认方…

现行有效外汇管理主要法规目录(截至2023年12月31日)

现行有效外汇管理主要法规目录 &#xff08;截至 2023 年 12 月 31 日&#xff09; 一、综合&#xff08;27 项&#xff09; &#xff08;一&#xff09;基本法规&#xff08;9 项&#xff09; 1 中华人民共和国外汇管理条例 国务院令第 532 号 2 个人外汇管理办法 中国…