网络协议——HTTP协议

news2024/11/18 18:17:13

目录

​编辑

一,HTTP协议基本认识

二,认识URL

三,http协议的格式

 1,发送格式

2,回应格式 

四,服务端代码

五,http报文细节

1,Post与Get方法

2,Content_lenth

 3,状态码

4,Location

5,content_type

6,cookie

​编辑


一,HTTP协议基本认识

      在我之前写的文章中,我实现过自定义协议。但是,在实际的网络编程中我们是不太需要定制协议的。因为前辈早就定制好了。而HTTP协议就是其中的一种。 http协议又被叫做超文本传输协议,这是因为http的本质其实就是按照http协议从服务端拿文件资源。而http协议能够拿走所有的文件资源,所以http协议又被叫做超文本传输协议。

二,认识URL

URL:Uniform Resource Locator,中文名叫统一资源定位符。

 URL的样子如下:

http://www.example.com/path/to/resource?query=string#fragment
  • http:// 是协议部分,表示这是一个使用 HTTP 协议的 URL。
  • www.example.com 是域名部分,表示资源所在的服务器的地址。
  • /path/to/resource 是路径部分,表示服务器上资源的具体位置。
  • ?query=string 是查询字符串部分,用于传递参数给服务器。
  • #fragment 是片段标识符部分,用于指定资源的某一部分,通常用于网页的导航。

域名其实就是ip地址,那为什么只要有ip地址就可以访问到主机上的唯一资源呢?

因为http的端口号是默认绑定的,是统一的不需要我们再来绑定。 

urlencode与urldecode

urlencode:在url中有些符号是被url默认使用了的。比如:?://。当我们的用户输入的url中带有这些字符时这些字符就会被encode。encode的规则是:将对应的字符的ASSCALL码转化为16进制,然后在前面加上%。urldecode就是反过来。当然,这个过程并不需要我们来做,有相应的工具可以帮我们完成:urlencode&&urldecode工具

三,http协议的格式

 1,发送格式

当我们以http为协议向服务端发送请求时,我们发送的数据会包含如下数据:

1,请求行

2,请求报文

3,一个空行

4,请求正文

在请求行,包含如下数据:

1,method   2,url   3,http version  4,"\r\n"

图示如下:

2,回应格式 

 当服务器在将数据发送回客户端时,会依照如下何时将数据返回:

1,状态行

2,响应报文

3,空行

4,响应正文

在状态行内,包含如下数据:

1,http version  2,状态码  3,状态码描述  4,“"\r\n"

图示如下:

四,服务端代码

在了解了http发送消息和响应消息的数据发送格式以后,我们便可以来动手写上一个能够按照http协议的格式进行响应的服务端。

实现如下:

1,为了能够更方便的使用创建套接字的接口,我对创建套接字的接口做如下封装

#pragma once
#include<iostream>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include<cstring>
#include<arpa/inet.h>
#include<unistd.h>


//定义一些变量
#define blog 10
#define defaultport 8080

class Socket
{
    public:
    //构造函数
        Socket()
            : sockfd_(0)
        {}

  public:
  //创建套接字
  bool Sock()
  {
      sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字
      if (sockfd_ < 0)
      {
          std::cerr <<  "创建套接字失败" << std::endl;
          return false;
      }

      int opt = 1;
      setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

      return true; // 将创建好的套接字返回
  }

  //bind,服务端只要绑定端口号

  bool Bind(int port = defaultport)
  {
       sockaddr_in server_addr;
       memset(&server_addr, 0, sizeof (server_addr));//清空数据
       server_addr.sin_family = AF_INET;
       server_addr.sin_port = htons(port);
       server_addr.sin_addr.s_addr = INADDR_ANY;

       int r1 = bind(sockfd_,(sockaddr*)&server_addr,sizeof server_addr);
       if(r1<0)
       {
           std::cerr << "bind err" << std::endl;
           return false;
       }

       return true;
  }

  //监听
  bool Listen()
  {
     int r2 =  listen(sockfd_, blog);
     if(r2<0)
     {
         std::cerr << "listen err" << std::endl;
         return 0;
     }

     return true;
  }

  //接收
  int Accept(std::string* ip,int* port)
  {
      sockaddr_in cli_addr;
      socklen_t len = sizeof(cli_addr);
      int sockfd = accept(sockfd_, (sockaddr *)&cli_addr, &len);
      if(sockfd<0)
      {
          std::cerr << "accept err" << std::endl;
          return -1;
      }

      char buff[64]={0};
      inet_ntop(AF_INET, &cli_addr, buff, sizeof(buff));
      *ip = buff;
      *port = ntohs(cli_addr.sin_port);
      return sockfd;
  }

  //连接
  bool Connect(std::string& ip,int16_t port)
  {
    sockaddr_in addr_;
    addr_.sin_family = AF_INET;
    addr_.sin_port = htons(port);
    inet_pton(AF_INET, ip.c_str(), &(addr_.sin_addr));
    int r = connect(sockfd_, (sockaddr *)&addr_, sizeof (addr_));
    if(r<0)
    {
        std::cerr << "connect err" << std::endl;
        return false;
    }

    return true;
  }

  //关闭

  void Close()
  {
      close(sockfd_);
  }

    public:
     //成员
     int sockfd_;
};

2,服务端代码

 在做好上述封装工作以后便可以来构建服务端了,服务端的创建步骤如下:

1,类成员

1,listensockfd_:  Sock类成员。主要是为了调用创建套接字的接口。

2,port_:端口号,主要是为了绑定时使用。

2,构造函数

初始化port_ 

3,Init函数

创建套接字  bind套接字  监听套接字

4,Start函数

1,循环接收服务端发来的连接请求,然后创建线程执行相应的任务。

2,读出客户端发来的请求。

3,然后根据http协议将请求处理出来到req中。

4,根据url判断客户端想要请求的资源是服务器那个资源。

5,读出资源发送回客户端。 

 在理清上面创建服务端的过程以后,便可以写出如下代码:


struct ThreadData
{
    int sockfd;
};

class HttpServer
{
    public:
      HttpServer(int port)
      :port_(port)
      {}  

      void Init()
      {
          listensocket_.Sock();//创建套接字
          listensocket_.Bind(port_);//绑定套接字端口
          listensocket_.Listen();//监听套接字
      }

      bool Start()
      {
        while (true)
        {
            // 建立连接
            std::string ip;
            int port;
            int sockfd = listensocket_.Accept(&ip, &port_);
            lg(Debug, "link->%s:%d sucess,sockfd:%d", ip.c_str(), port_,sockfd);

            // 创建线程执行任务
            pthread_t td;
            ThreadData ts;
            ts.sockfd = sockfd;

            pthread_create(&td, nullptr, ThreadRun, &ts);
        }

        return true;
      }

  static void* ThreadRun(void*args)
        {
            pthread_detach(pthread_self());//与主线程分离
            ThreadData* ts = static_cast<ThreadData*>(args);

            Helper(ts->sockfd);//处理客户端发来的消息

            delete ts;
            return nullptr;
        }

  private:
      Socket listensocket_;
      int port_;
    
};

 如何处理客户端发来的消息?

代码如下:

static void Helper(int sockfd)
      {

          // 接收消息并打印
          char buff[1024] = {0};
          int n = recv(sockfd, buff, sizeof(buff), 0);

          if (n > 0)
          {   
              //显示读出来的消息
              buff[n] = 0;
              std::cout << buff << std::endl;
              
              //读出来的消息进行解析处理,得到客户端想要访问什么资源
              Request req;
              req.Deserialize(buff);
              req.prase(buff);
              std::string path = req.Select_path();
              path = rootpath + path;
              std::cout << "debug: " << std::endl;
              req.Debugprint();
              
              
              //根据http协议的方式将资源发送会给客户端
              std::string text = Readrequest(path);
              std::string response;
              std::string sep = "\r\n";
              std::string line = "HTTP 1.1  1  OK";
              line += sep;
              std::string head = "contentlenth:";
              std::string len = std::to_string(text.size());
              head += len;
              head += sep;
              head += sep;
              response+=line;//状态行
              response += head;//报头
              response += text;//正文
              send(sockfd, response.c_str(), response.size(), 0);
          }
      }

如何对客户端的发来的消息进行处理呢? 

 创建一个Request类,这个类里面含有如下成员:

1, std::vector<std::string> arr  :对内容进行分割   

2, std::string text   :将正文提取出来

######################################

3,std::string method :接收方法                      

4,std::string url   :显示网址

5,std::string http_version :显示http的版本  

 6, std::string path :表示路径

类方法如下:

1,Deseralize: 对从客户端接收到的消息里的请求行和报文进行打散,打散到我的arr数组里面。并将正文提取出来。

2,prase:打散后,arr[0]便代表着请求行,包含着三个信息:method   url    http version。通过prase函数将这些消息获取出来。

3,DebugPrint:显示相应的消息。用于debug。

4,select_path:选择路径。

实现如下:

class Request
{
    public:
      void Deserialize(std::string message)
      {
           while (true)//循环读取
           {
               int pos = message.find(sep);
               if(pos == std::string ::npos)
               {
                   break;
               }

               std::string str = message.substr(0, pos);
               if(str.empty())
                   break;
               arr.push_back(str);
               message.erase(0, pos + 1);//读一行消一行
           }

           text = message;
      }

      void prase(std::string message)//将状态行的信息散开
      {
          std::stringstream s (message);
          s >> method >> url >> http_version;
      }

      void Debugprint()
      {
          for(auto e:arr)
          {
              std::cout << e <<" "<<std::endl ;
          }

          std::cout << "method:" << method << std::endl;
          std::cout << "url:" << url << std::endl;
         std::cout<< "http_version:" << http_version << std::endl;
      }

      std::string Select_path()
      {
           if(url == "/"||url == "/html.index")
           {
               path = rootpath+"/";
           }
           else 
           {
               path = url;
           }
           
           return path;
      }

    private:
        std::vector<std::string> arr;
        std::string text;
        std::string method;
        std::string url;
        std::string http_version;
        std::string path;
};

返回消息:

当我们处理了客户端发来的消息以后,便可以得到客户端想要的资源在那个路径下。于是我们便可以将该路径对应的文件的内容读出来并返回给客户端显示。

读取文件的函数如下:

std::string Readrequest(std::string path)//从文件内读取
{   

    std::ifstream in(path);
    if(!in.is_open())
        return "404";

    std::string line;
    std::string content;

    while(getline(in,line))
    {
        content += line;
    }

    return content;//这个消息会拼接到正文,也就是text
}

五,http报文细节

1,Post与Get方法

 在http的请求报头当中,经常使用的请求方法有如下两个:1,Post   2,Get

Post:

Post方法在提取form表单的参数时,通常会将参数放到正文部分来提交参数。

Get:

Get方法在提取表单的参数时,通常会将参数放到url的后面来提交参数。

相对于Get方法,Post方法提交参数的方式比较隐蔽。但是两种提交参数的方式都是不安全的。因为通过抓包都可以将报文抓取然后获取报文的所有内容。如果想要安全就得加密,加密协议就是https协议。

2,Content_lenth

在相应报文的报头里面有一个Content_lenth的字段,代表着正文的长度。报头与正文之间通过一个空行来分隔,分割以后,正文内容的大小由Content_lenth来指定,进而进行读取。

 3,状态码

在http报文当中,相应报文内的状态行中会有一串数字表示响应的状态。

比如,200就代表正常,状态码描述便是ok。

通常,状态码对应的消息如下

1xx:信息型状态码。

2xx:代表请求ok,如200。

3xx:代表重定向,如301(永久重定向) 302(临时重定向)。

4xx:代表客户端请求错误,如404。

5xx:代表服务端内部错误,如501。

4,Location

Location字段一般都是和重定向状态3xx搭配使用的。当我的客户端申请访问我们已经移动后的资源时,服务端的Location字段便会指引客户端去访问移动后的资源。

5,content_type

content_type标识的是正文的文件类型。这个,content_type放在报头,指明文件类型,进而让客户端以正确的类型收取文件资源显示文件资源。

6,cookie

cookie字段的作用是储存少量信息,用于搭建临时会话。

在我们的浏览器上,其实每次访问一个资源都是需要认证的。比如你在一个浏览器上第一次访问b站,我们是不是要进行登录认证呢?是的吧,那我们看b站上的视频需不需要进行认证呢?其实也是需要的,但是cookie已经帮你吧认证信息存储起来了,所以会帮你自动登录认证。

分类:

cookie文件一般分为内存文件和硬盘文件。为了安全,一般都会设置为内存文件(会定时清理)。比如在edge 浏览器上便是内存文件。 

更完整细节: 

HTTP方法:

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

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

相关文章

基于Springboot的航班进出港管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的航班进出港管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结…

C++的 stack和queue 的应用和实现【双端队列的理解和应用】

文章目录 stack的理解和应用栈的理解栈的模拟实现string实现stackvector实现stack queue的理解和应用队列的理解队列的模拟实现 双端队列原理的简单理解deque的缺陷为什么选择deque作为stack和queue的底层默认容器STL标准库中对于stack和queue的模拟实现stack的模拟实现queue的…

智能停车场物联网远程监控解决方案

智能停车场物联网远程监控解决方案 智能停车场物联网远程监控解决方案是一种集成了现代物联网技术、大数据分析以及云计算等先进技术手段&#xff0c;对停车场进行全面智能化管理的综合系统。它通过实时感知、精准采集和高效传输各类停车数据&#xff0c;实现对停车场运营状态…

基于Springboot的学校防疫物资管理平台(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的学校防疫物资管理平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系…

Data-efficient Fine-tuning for LLM-based Recommendation

目录 Introduction 利用大型语言模型&#xff08;LLM&#xff09;进行推荐最近引起了相当大的关注&#xff0c;其中微调在 LLM 的适应中发挥着关键作用。然而&#xff0c;在快速扩展的推荐数据上微调LLMs的成本限制了其实际应用。为了应对这一挑战&#xff0c;小样本微调提供了…

RabbitMQ3.13.x之十_流过滤的内部结构设计与实现

RabbitMQ3.13.x之十_流过滤的内部结构设计与实现 文章目录 RabbitMQ3.13.x之十_流过滤的内部结构设计与实现1. 概念1. 消息发布2. 消息消费 2. 流的结构1. 在代理端进行过滤2. 客户端筛选3. JavaAPI示例4. 流过滤配置5. AMQP上的流过滤6. 总结 3. 相关链接 1. 概念 流过滤的思…

linux练习-交互式传参

在shell脚本中&#xff0c;read 向用户显示一行文本并接受用户输入 #!/bin/bash read -p 依次输入你的姓名、年龄、家乡 name age home echo 我是$name,年龄$age,我来自$home

C++数据结构与算法——二叉树公共祖先问题

C第二阶段——数据结构和算法&#xff0c;之前学过一点点数据结构&#xff0c;当时是基于Python来学习的&#xff0c;现在基于C查漏补缺&#xff0c;尤其是树的部分。这一部分计划一个月&#xff0c;主要利用代码随想录来学习&#xff0c;刷题使用力扣网站&#xff0c;不定时更…

优衣库门店可视化与顾客拜访数据分组-Python数据分析项目

文章目录 项目背景1 引言2 数据说明 一、数据导入及预处理1 数据导入2 数据观察2.1 查看数据形状2.2 检查缺失值2.3 有无重复值 3 数据预处理3.1 获取详细地址3.2 批量获取经纬度3.2.1 安装geopy包3.2.2 批量获取经纬度 二、优衣库门店可视化1 数据获取1.1 读取地点数据1.2 筛选…

c语言数据结构(10)——冒泡排序、快速排序

欢迎来到博主的专栏——C语言数据结构 博主ID&#xff1a;代码小豪 文章目录 冒泡排序冒泡排序的代码及原理快速排序快速排序的代码和原理快速排序的其他排序方法非递归的快速排序 冒泡排序 相信冒泡排序是绝大多数计科学子接触的第一个排序算法。作为最简单、最容易理解的排序…

创新性的智慧公厕技术研发与应用

智慧公厕&#xff0c;作为城市基础设施的重要组成部分&#xff0c;扮演着提供舒适便捷卫生服务的角色。智慧公厕源头实力厂家广州中期科技有限公司&#xff0c;通过技术创新与应用升级&#xff0c;打造标杆性的智慧公厕整体解决方案。通过创新性的技术应用&#xff0c;智慧公厕…

字节新作:图像生成质量超越DiT

&#x1f31f;每日更新最新高质量论文&#xff0c;关注我&#xff0c;时刻关注最新大模型进展。&#x1f31f; &#x1f4cc; 元数据概览&#xff1a; 标题&#xff1a;Visual Autoregressive Modeling: Scalable Image Generation via Next-Scale Prediction作者&#xff1a…

Jupyter IPython帮助文档及其魔法命令

1.IPython 的帮助文档 使用 help() 使用 ? 使用 &#xff1f;&#xff1f; tab 自动补全 shift tab 查看参数和函数说明 2.运行外部 Python 文件 使用下面命令运行外部 Python 文件&#xff08;默认是当前目录&#xff0c;也可以使用绝对路径&#xff09; %run *.py …

数据湖概述:大数据演进阶段-数据湖

文章目录 一. 大数据发展过程1. 离线大数据平台2. Lambda架构&#xff1a;速度层批层3. Kappa架构&#xff1a;流批一体4. 大数据架构痛点总结 二. 数据湖助力于解决数据仓库痛点问题1. 数据湖特点2. 开源数据湖的架构 三. 数据湖和数据仓库理念的对比1. 数据湖和数据仓库对比2…

LeetCode 热题 100 | 贪心算法

目录 1 121. 买卖股票的最佳时机 2 55. 跳跃游戏 3 45. 跳跃游戏 II 4 763. 划分字母区间 菜鸟做题&#xff0c;语言是 C 1 121. 买卖股票的最佳时机 解题思路&#xff1a; 维护一个变量 max_pricemax_price 用于存储排在 i 天之后的股票最高价格第 i 天的最高利润 …

Day5-Hive的结构和优化、数据文件存储格式

Hive 窗口函数 案例 需求&#xff1a;连续三天登陆的用户数据 步骤&#xff1a; -- 建表 create table logins (username string,log_date string ) row format delimited fields terminated by ; -- 加载数据 load data local inpath /opt/hive_data/login into table log…

armlinux-外部中断

s3c2440的中断框图 如果我们单纯配置一个按键的外部中断&#xff0c;就不存在子中断与优先级的问题。 由于是按键的外部中断&#xff0c;通过引脚的高低电平来触发。所以我们要先配置引脚的功能。 我们使用按键1&#xff0c;终端源为EINT8&#xff0c;对应引脚GPG0 通过用户手…

Stable diffusion 加载扩展列表报错解决方法

项目场景&#xff1a; 在使用Stable diffusion webui时&#xff0c;使用扩展列表出现错误 问题描述 点击loadfrom后&#xff0c;出现加载扩展列表报错 原因分析&#xff1a; 下载的扩展的时候&#xff0c;都是github 的url&#xff0c;需要科学上网&#xff0c;如果不能科学…

深澜计费管理系统 任意文件读取漏洞复现

0x01 产品简介 深澜计费管理系统是是一套完善的领先的具有复杂生物型特征的弹性认证计费系统。系统主要由 AAA 认证计费平台、系统运营维护管理平台、用户及策略管理平台、用户自助服务平台、智能客户端模块、消息推送模块、数据统计模块组成。目前在全球为超过 2500 家客户提…

MicroPython 树莓派 RP2 入门教程

系列文章目录 前言 Raspberry Pi Pico 开发板&#xff08;图片来源&#xff1a;Raspberry Pi 基金会&#xff09;。 以下是 Raspberry Pi RP2xxx 板的快速参考资料。如果您是第一次使用该开发板&#xff0c;了解微控制器的概况可能会对您有所帮助&#xff1a; 一、关于 RP2xxx…