【Linux网络编程】应用层HTTP协议篇

news2024/11/24 12:27:50

应用层

  • 一、应用层
    • 1.1、再谈协议
    • 1.2、HTTP协议
      • 1.2.1、认识URL
      • 1.2.2、urlencode和urldecode
      • 1.2.3、HTTP协议格式
      • 1.2.4、HTTP的方法
      • 1.2.5、HTTP的状态码
      • 1.2.6、HTTP常见的Header
  • 二、结合代码理解HTTP通信流程

一、应用层

程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.

1.1、再谈协议

协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些
“结构化的数据” 怎么办呢?

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.

约定方案一:

客户端发送一个形如"1+1"的字符串; 这个字符串中有两个操作数, 都是整形; 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
数字和运算符之间没有空格;


约定方案二:

定义结构体来表示我们需要交互的信息; 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转
化回结构体; 这个过程叫做 “序列化" 和 "反序列化

// proto.h 定义通信的结构体
typedef struct Request {
int a;
int b;
} Request;

typedef struct Response {
int sum;
} Response;

// client.c 客户端核心代码
Request request;
Response response;
scanf("%d,%d", &request.a, &request.b);
write(fd, request, sizeof(Request));
read(fd, response, sizeof(Response));

// server.c 服务端核心代码
Request request;
read(client_fd, &request, sizeof(request));
Response response;
response.sum = request.a + request.b;
write(client_fd, &response, sizeof(response));

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解
析, 就是ok的. 这种约定, 就是 应用层协议

1.2、HTTP协议

虽然我们说, 应用层协议是我们程序猿自己定的.

但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用.HTTP(超文本传输协议)就是其中之一.

1.2.1、认识URL

在这里插入图片描述

1.2.2、urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
在这里插入图片描述

编码工具

1.2.3、HTTP协议格式

HTTP请求:
在这里插入图片描述

  • 首行: [方法] + [url] + [版本]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

HTTP响应:

在这里插入图片描述

  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个
  • Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.

1.2.4、HTTP的方法

在这里插入图片描述
其中最常用的就是GET方法和POST方法.

1.2.5、HTTP的状态码

在这里插入图片描述
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

1.2.6、HTTP常见的Header

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

二、结合代码理解HTTP通信流程

完整版代码在码云

main函数调用

//  ./httpServer 8080
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]); //不按照要求输入则报错提示
        exit(0);
    }
    uint16_t port = atoi(argv[1]);
    unique_ptr<HttpServer> httpsvr(new HttpServer(Get, port));  //智能指针指向Httpserver对象  
    httpsvr->initServer();  //调用方法初始化并启动
    httpsvr->start();

    return 0;
}

初始化Httpserver对象 调用他的方法

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include"Protocol.hpp"


namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    using func_t=std::function<bool(const HttpRequest&,HttpResponse &)>;

    class HttpServer
    {
    public:
        HttpServer(func_t func,const uint16_t &port = gport) : _func(func),_listensock(-1), _port(port)
        {}
                 
        void HandlerHttp(int sock)
        {
            // 1. 读到完整的http请求
            // 2.反序列化
            //3.httprequest,httpresponse    _func(req,resp)
            // 4.resp 反序列化
            // 5.send
        
            char buffer[4096];
            HttpRequest req;
            HttpResponse resp;

            size_t n=recv(sock,buffer,sizeof(buffer)-1,0);  //通过sock套接字读数据保存到buffer
            if(n>0)
            {
                buffer[n]=0;
                req.inbuffer=buffer;
                req.parse();
                _func(req,resp);    //通过req请求得到 resp响应
                send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);  //发送
            }
        }

        void initServer()
        {
            // 1. 创建socket文件套接字对象
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                
                exit(SOCKET_ERR);
            }
       
            // 2. bind绑定自己的网络信息
            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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                exit(BIND_ERR);
            }

            // 3. 设置socket 为监听状态
            if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog
            {
                exit(LISTEN_ERR);
            }
         }
        void start()
        {
            for (;;)
            {
                // 4. server 获取新链接
                // sock, 和client进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    continue;
                }

                // version 2 多进程版(2)
                pid_t id = fork();
                if (id == 0) // child
                {
                    close(_listensock);
                    if(fork()>0) exit(0);
                    HandlerHttp(sock);
                    close(sock);
                    exit(0);
                }
                close(sock);

                // father
                waitpid(id, nullptr, 0);

            }
        }
        ~HttpServer() {}

    private:
        int _listensock; // 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
        uint16_t _port;
        func_t _func;
    };

} // namespace server

请求类和响应类

#pragma once

#include <iostream>
#include <string>
#include <vector>
#include<sstream>
#include "Util.hpp"

const std::string sep = "\r\n";
const std::string default_root = "./wwwroot";
const std::string home_page = "index.html";
const std::string html_404="wwwroot/404.html";

class HttpRequest
{
public:
    HttpRequest() {}
    ~HttpRequest() {}
    void parse()
    {
        // 1.从inbuffer中拿到第一行  分隔符\r\n
        std::string line = Util::getOneLine(inbuffer, sep);
        if(line.empty()) return ;
        
        // 2.从请求行中提取三个字段
        std::cout<<"line :"<<line<<std::endl;
        std::stringstream ss(line);     //流 以空格作为分隔符
        ss>>method>>url>>httpversion;   //直接写入

        // 3.添加web默认路径
        path=default_root;
        path+=url;
        if(path[path.size()-1]=='/') path+=home_page;
    }

public:
    std::string inbuffer; // 读到的所有东西都在这
    // std::string reqline;        //请求行
    // std::vector<std::string> reqheader; //请求头
    // std::string body;       //正文

    std::string method;      // 请求头里面的 请求方法
    std::string url;         // 请求头里面的 访问路径
    std::string httpversion; // 请求头里面的 版本号
    std::string path;   
};

class HttpResponse
{
public:
    std::string outbuffer;
};

手写的get方法

bool Get(const HttpRequest &req, HttpResponse &resp)
{
    // 测试
    cout << "-------------------http start---------------------" << endl;
    std::cout << req.inbuffer<<std::endl;
    std::cout << "method: " << req.method << std::endl;
    std::cout << "url: " << req.url << std::endl;
    std::cout << "httpversion: " << req.httpversion << std::endl;
    std::cout << "path: " << req.path << std::endl;
    cout << "-------------------http endl---------------------" << endl;
    std::string respline = "HTTP/1.1 200 OK\r\n";          // 状态行
    std::string respheader = "Content_Type:text/html\r\n"; // 响应报头

    std::string respblank = "\r\n";                        // 空行
    std::string body = "<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>hello world</h1></head><body><p>给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。</p></body></html>";
    
    // std::string body;
    // if(!Util::readFile(req.path,&body))
    // {
    //     Util::readFile(html_404,&body); //一定成功
    // }

    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer += respblank;
    resp.outbuffer += body;
    return true;
}

  1. 流程就是运行./httpserver 8080 ,输入正确,
  2. 创建智能指针httpsvr指向Httpserver对象,这个对象会在创建智能指针的时候new出来,传入的是手写的Get方法和port端口号,
  3. 通过智能指针调用初始化函数和start函数 对该对象进行操作
  4. 初始化服务器会通过一系列的操作完成
    4.1. 创建socket套接字对象,
    4.2. bind自己的网络信息
    4.3.设置socket为监听状态
  5. 运行start函数
    5.1.创建结构体对象用来接收对方发过来的请求链接
    5.2.通过accept获取新的sock通信文件描述符
    5.3.通过新的文件描述符来通信(本例子用的多进程版本)
  6. 在多进程的过程中会创建子进程,子进程创建孙子进程同时关闭子进程 让孙子进程被托管,让孙子进程去执行通信流程 HandlerHttp(sock);
  7. 执行的流程是
    // 1. 读到完整的http请求
    // 2.反序列化
    //3.httprequest,httpresponse _func(req,resp)
    // 4.resp 反序列化
    // 5.send
  8. 在函数执行期间,会创建请求对象和接收对象通过套接字revc来读取数据,期间会调用func函数,也就是传入的get函数来打印数据便于查看,同时调用send函数来把处理后的数据返回给请求方

以上为流程。

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

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

相关文章

Stable Diffusion原理说明

本文参考&#xff1a;深入浅出讲解Stable Diffusion原理&#xff0c;新手也能看明白 - 知乎 目录 1、Stable Diffusion能做什么&#xff1f; 2、扩散模型&#xff08;Diffusion model&#xff09; &#xff08;1&#xff09;前向扩散&#xff08;Forward Diffusion&#xff…

Simple-BEV:多传感器BEV感知中真正重要的是什么?

文章&#xff1a;Simple-BEV: What Really Matters for Multi-Sensor BEV Perception? 作者&#xff1a;Adam W. Harley &#xff0c; Zhaoyuan Fang&#xff0c;Jie Li&#xff0c;Rares Ambrus &#xff0c; Katerina Fragkiadaki 编辑&#xff1a;点云PCL 代码&#xff1a;…

华为OD机试之查找接口成功率最优时间段(Java源码)

查找接口成功率最优时间段 题目描述 服务之间交换的接口成功率作为服务调用关键质量特性&#xff0c;某个时间段内的接口失败率使用一个数组表示&#xff0c;数组中每个元素都是单位时间内失败率数值&#xff0c;数组中的数值为0~100的整数&#xff0c;给定一个数值(minAverag…

【Web服务应用】Apache配置与应用

Apache配置与应用 一、构建虚拟Web主机1.1httpd服务支持的虚拟主机类型包括以下三种 二、基于域名的虚拟主机三、基于IP地址的虚拟主机四、基于端口的虚拟机五、Apache连接保持六、构建Web虚拟目录与用户授权限制七、日志分割 一、构建虚拟Web主机 虚拟Web主机指的是在同一台服…

手把手教你部署FreeYOLO

作者:Kissrabbit 原文链接: https://zhuanlan.zhihu.com/p/578830729 本章将讲解如何将torch训练好的权重文件转换为ONNX文件&#xff0c;并如何部署回到OpenVINO、TensorRT等框架下。笔者将以自己的FreeYOLO项目为例&#xff0c;来完成本章的内容讲解&#xff0c;相关代码如下…

企业数字化转型转什么?怎么转?这份攻略请收好...

数字化转型&#xff0c;转什么&#xff1f;怎么转&#xff1f;这些问题仍在困扰不少企业&#xff0c;也是每个企业转型升级不得不思考的重要问题。 对此&#xff0c;中关村数字经济产业联盟、元年研究院、《管理会计研究》联合发布了《成就数据驱动型企业 中国企业数字化转型白…

ROS订阅与发布话题

目录 一、新建一个ROS工作空间并创建功能包 二、创建一个msg消息 三、发布话题 四、订阅话题 前言 Ubuntu18.04 ROS Melodic 一、新建一个ROS工作空间并创建功能包 mkdir -p catkin_ws/src cd ~/catkin_ws/src/ catkin_init_workspace cd ~/catkin_ws/ catkin_make echo &…

USB接口的演变与升级

USB接口是计算机与外部设备之间传输数据的重要接口之一&#xff0c;它的演变和升级经历了多年的发展。本文将详细介绍USB接口的发展历程、应用领域、标准化进程以及未来趋势。 USB接口最早出现在1994年&#xff0c;当时是由英特尔公司、微软公司和惠普公司共同开发的。这个接口…

React中useEffect的源码解读

对源码的解读有利于搞清楚Hooks到底做了什么&#xff0c;如果您觉得useEffect很“魔法”&#xff0c;这篇文章也许对您有些帮助。 本篇博客篇幅有限&#xff0c;只看useEffect&#xff0c;力求简单明了&#xff0c;带您到React Hooks的深处看看 按图索骥找到Hook相关源码&…

JavaWeb之Servlet

1、什么是JavaWeb&#xff1f; Servlet 是 JavaEE 规范之一。规范就是接口 Servlet 就 JavaWeb 三大组件之一。三大组件分别是&#xff1a;Servlet 程序、Filter 过滤器、Listener 监听器。 Servlet 是运行在服务器上的一个 java 小程序&#xff0c;它可以接收客户端发送过来的…

Python plt; ax 设置tick

Python中绘图可以基于plt&#xff1b;也可基于ax 在 Matplotlib 中&#xff0c;Axes 对象&#xff08;常简写为 ax&#xff09;是在图&#xff08;Figure&#xff09;中进行大部分的绘图操作的地方。一个 Axes 对象代表了一个具体的绘图区域。 利用 plt 绘图 简单的图像测试…

继续探索Roop(单张图视频换脸)的各方面:比如喜闻乐见的“加速”

文章目录 &#xff08;一&#xff09;Roop项目的特点&#xff08;二&#xff09;Roop也能加速***&#xff08;三&#xff09;Roop更新和依赖&#xff08;3.1&#xff09;飞速更新&#xff08;3.2&#xff09;依赖问题&#xff08;3.3&#xff09;需要CUDA么 前两天写了&#x1…

如何修复vcruntime140.dll文件?多种解决vcruntime140.dll的方法分享

在使用Windows操作系统时&#xff0c;经常会遇到一些错误提示&#xff0c;比如缺少vcruntime140.dll文件。这个文件是Visual C Redistributable Package的一部分&#xff0c;它负责运行C程序。如果你在运行某些软件或游戏时收到了“缺少vcruntime140.dll文件”的错误提示&#…

电子科技大学计算机系统结构半期考试参考答案

2023 答案-半期试题&#xff08;15分&#xff09; 1、试分析采用哪种设计方案实现求浮点数乘法FPMUL对系统性能提高更大。假定FPMUL操作占整个测试程序执行时间的10%。 一种设计方案是增加专门的FPMUL硬件&#xff0c;可以将FPMUL操作的速度加快到10倍&#…

IP协议与ethernet协议

一、IP协议 1.IP协议作用和意义 &#xff08;1&#xff09;计算机网络体系结构 &#xff08;2&#xff09;网络互联使用路由器 &#xff08;3&#xff09;IP网的意义 当互联网上的主机进行通信时&#xff0c;就好像在一个网络上通信一样&#xff0c;看不见互连的各具体的网络…

Vue.js 中的渲染函数是什么?如何使用渲染函数?

Vue.js 中的渲染函数是什么&#xff1f;如何使用渲染函数&#xff1f; Vue.js 是一款流行的前端框架&#xff0c;它提供了许多方便的工具和 API&#xff0c;用于构建交互式的用户界面。其中&#xff0c;渲染函数是 Vue.js 中一个强大的工具&#xff0c;它可以让我们以编程的方…

深度解析java异步多线程优化版

快速使用需求&#xff1a;我不要理解一堆理论想直接用 操作说明 ITask.java PutEsTask.java TaskExecutor.java TaskQueue.java TestMain.java请把这几个类文件复制下去&#xff0c;运行testMain的方法&#xff0c;根据TestMain的运行日志&#xff0c;【1】-> 【8】不同需求…

2.3 网络设计与redis、memcached、nginx组件

目录 一、网络模块需要处理哪些事情二、reactor网络设计模型三、网络模块与业务的关系四、redis、memcached、nginx1、redis2、memcached3、ngnix4、总结 一、网络模块需要处理哪些事情 网络编程主要关注客户端与服务端交互的四个问题&#xff1a; 1、连接建立 2、消息到达 3、…

《不要挑战人性》笔记(一)

恒河猴实验 代母实验 将刚出生的小猴子与母亲分开&#xff0c;让它与绒布妈妈跟铁丝妈妈生活在一起。铁丝妈妈身上有食物&#xff0c;绒布没有食物&#xff0c;小猴子更喜欢绒布妈妈&#xff0c;即使它在铁丝妈妈那里得到了食物。绒布妈妈身上设置机关&#xff0c;攻击小猴子&a…

OAuth2 工作流程详解

我们之前谈到了使用saml作为SSO单点登录认证。本文讲解oauth2.0协议&#xff0c;oauth2.0协议避免了客户端直接访问受保护资源 什么是OAuth OAuth是一种安全的开放协议&#xff0c;用于在不相关的服务之间授权用户。换句话说&#xff0c;它使一个服务能够访问托管在其他服务上…