Linux:http协议初步认识

文章目录

  • 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;
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

CMake学习(下)

1. 嵌套的CMake 如果项目很大&#xff0c;或者项目中有很多的源码目录&#xff0c;在通过CMake管理项目的时候如果只使用一个CMakeLists.txt&#xff0c;那么这个文件相对会比较复杂&#xff0c;有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件&#xff…

携程旅行web逆向

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章…

C语言:volatile关键字讲解

读音&#xff1a;vaoletail C语言中的volatile关键字是一个重要的类型修饰符&#xff0c;它用于声明一个变量具有“易变性”&#xff0c;即可能在编译器无法察觉的情况下被改变其值。Volatile意思是“易变的”&#xff0c;应该解释为“直接存取原始内存地址”比较合适。 “易变…

【高质快刊】中科院1区TOP,最新案例仅2个月14天录用!进展超顺,即将截稿!

&#xff08;一&#xff09;期刊简介概况 【期刊类型】能源工程类SCIE&EI 【出版社】ELSEVIER出版社 【期刊概况】IF&#xff1a;11.0-12.0&#xff0c;JCR1区&#xff0c;中科院1区TOP 【预警情况】2020-2024年无预警记录 【收录年份】1977年被WOS数据库收录 【年发…

【python绘图colorbar对齐】

[Toc]# 1、问题描述 python在绘图过程中&#xff0c;可能会出现colorbar高度与主图不匹配情况&#xff0c;需要进行调整&#xff0c;使得与主图高度对齐&#xff0c;使图像更美观。示例&#xff1a;colorbar位置高于主图 2、解决方法 通过调整shrink参数匹配对齐,pad调整x轴…

【CPP】C++11多线程

thread类 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如windows和linux下各有自己的接口&#xff0c;这使得代码的可移植性比较差。C11中最重要的特性就是对线程进行支持了&#xff0c;使得C在并行编程时不需要依赖第三方库&#xff0c…

ARM中断实验

key_inc.c #include"key_inc.h"void key1_it_config(){//使能GPIOF外设时钟RCC->MP_AHB4ENSETR | (0x1<<5);//将PF9设置为输入模式GPIOF->MODER & (~(0x3<<18));//设置由PF9管脚产生EXTI9事件EXTI->EXTICR3 & (~(0XFF<<8));EXTI-…

Linux-线程同步

文章目录 前言一、为什么要线程同步&#xff1f;二、线程同步pthread_cond_initpthread_cond_destroypthread_cond_wait、pthread_cond_signal和 pthread_cond_broadcast 三、示例代码 前言 上节课学习了线程互斥&#xff0c;这节课针对线程互斥内容在做进一步的补充和完善&am…

鸿蒙Harmony应用开发—ArkTS(@State装饰器:组件内状态)

State装饰的变量&#xff0c;或称为状态变量&#xff0c;一旦变量拥有了状态属性&#xff0c;就和自定义组件的渲染绑定起来。当状态改变时&#xff0c;UI会发生对应的渲染改变。 在状态变量相关装饰器中&#xff0c;State是最基础的&#xff0c;使变量拥有状态属性的装饰器&a…

Elasticsearch:让你的 Elasticsearch 索引与 Python 和 Google Cloud Platform 功能保持同步

作者&#xff1a;来自 Elastic Garson Elasticsearch 内的索引 (index) 是你可以将数据存储在文档中的位置。 在使用索引时&#xff0c;如果你使用的是动态数据集&#xff0c;数据可能会很快变旧。 为了避免此问题&#xff0c;你可以创建一个 Python 脚本来更新索引&#xff0…

VMWare虚拟机使用openmediavault搭建NAS服务器完整步聚

下载: gopenmediavault - The open network attached storage solution 下载好openmediavault的ISO镜像后,打开虚拟机并安装 系统类型选择Debian 启动虚拟机并安装openmediavault 选择中文 地区选中国 键盘配置选汉语 开始安装 配置网络信息 配置root密码 确认密码 系统安装中…

LeetCode # 199. 二叉树的右视图

199. 二叉树的右视图 题目 给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 示例 1: 输入: [1,2,3,null,5,null,4] 输出: [1,3,4] 示例 2: 输入: [1,null,3] 输出: [1,3] 示例 3…

最新域名出售交易平台源码修复版

最新域名出售交易平台源码修复版 测试正常分享&#xff0c;保障你使用愉快&#xff0c;源码测试修复不易&#xff0c;拿走留痕是美德 无需到处找教程或修复&#xff0c;教程一次性到位&#xff0c;无需藏着掖着 最新修复版域名出售交易平台源码&#xff0c;搭建即可正常使用…

【群晖】Docker Compose部署 Emby Server

【群晖】Docker Compose部署 Emby Server 本来群晖上面的 Emby 是用套件安装的&#xff0c;但是不巧的是前两天脑袋一抽装了两个插件&#xff0c;导致 Emby Server被当肉鸡了&#xff0c;还找不到脚本代码在哪儿&#xff0c;一天时间上传了3T的流量。无奈之下&#xff0c;只能尝…

头条网盘拉新项目该怎么选择授权

作为十二月份首发上线的项目——头条网盘拉新。一经上线就受到很多想要做这行业人的关注&#xff0c;光是佣金已经是业内比较高的了&#xff01;每拉新一位新用户就可以获取到价格为9元一单的佣金&#xff0c;拉失活用户也可以获取价格为4元的佣金&#xff0c;推广方式和其他网…

真机笔记(2)项目分析

目录 1. 项目&#xff1a; 2. 网络工程师工作流程 3. 实验 设备命名 登录密码 使用SSH协议 1. 项目&#xff1a; 竞标方&#xff1a;集成商、厂商、代理商、服务商、监理检测公司 在一个网络项目中&#xff0c;不同的角色承担着不同的职责和任务。以下是集成商、厂商、代…

JavaScript-Web学习笔记01

一、Web APIs 1、Web API Web API 是浏览器提供的一套操作浏览器功能和页面元素的API&#xff08;BOM 和 DOM&#xff09;。 2、总结 API 是为我们提供的一个接口&#xff0c;帮助我们实现某种功能Web API 主要是针对浏览器提供的接口&#xff0c;主要针对浏览器做交互效果。W…

腾讯云COS - 前端上传文件到 COS 跨域问题

问题描述 原因分析 因为我本地的地址是&#xff1a;http://localhost:9528 而发送请求时的地址是&#xff1a;http://132-1307119153.cos.ap-beijing.myqcloud.com/tu.jpg 域名不同&#xff0c;自然而然就出现了跨域的问题&#xff01; 解决方案 先点击对象存储 - 安全设置…

吃瓜Grok大模型

段子区 今年当地时间2月29日晚&#xff0c;马斯克闹出来一件大事——正式起诉OpenAI和Sam Altman&#xff0c;并要求OpenAI 恢复开源GPT-4等模型。国际流量大师我只付服马斯克和川宝!&#xff01; 当大家觉得这扯皮的故事就此结束后&#xff0c;马斯克“不负众望”的整了一个大…

算法-图的强连通分量,图的最小生成树

1.图的强连通分量 (1). 定义 图的强连通分量是图论中的一个重要概念&#xff0c;主要在有向图中进行讨论。具体来说&#xff0c;如果在一个有向图G中&#xff0c;任意两个顶点vi和vj&#xff08;其中vi大于vj&#xff09;之间都存在一条从vi到vj的有向路径&#xff0c;同时也存…