初识http协议,简单实现浏览器和服务器通信

文章目录

  • 认识url
  • http协议格式
  • 通信
  • 代码验证
    • 细节
    • Util.hpp
    • protocol.hpp
    • Server.hpp
    • Server.cc
  • 结果分析

认识url

平时俗称的 “网址” 其实就是说的 URL,例如在百度上搜索一个C++

image-20230807201005576

可以看到这段网址前面有个 https 那么这个就代表着使用的是https协议,现在都是使用https协议,不过还是需要认识以下http协议

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现。所以在通信的时候需要先对字符进行转义。

比如上面搜索的c++,在网址里就会将 + 转义,转义规则如下:

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

+的字符码为43,所以转换为16进制为2B。可以通过某些网站来查看转义后的信息,例如:chinaz

那么在http协议中,一串网址各个部分又有什么含义呢,假设现在有这么一串网址

image-20230807202217469

也就是说,网络通信本就是在服务器中找对应的文件资源

http协议格式

了解了网址的意义,那么就来了解一下http协议的格式是什么样的。

image-20230807203150638

通信

那么两端的机器接收到了数据后,怎么样保证读取到一个完整的http协议的数据呢

  1. 并不能确保将整个数据读完,但是可以确保将一行读完,因为其中的数据都是由 \r\n 结尾的
  2. 所以可以将请求行和请求报头读完,可以发现正文和报文是有空行分割的
  3. 一旦读到空行说明请求行和请求报头就读完了
  4. 接着报头里可以存放着正文长度的属性,根据正文长度再去读正文即可
  5. 响应端同理

那么请求和响应怎么样去序列化和反序列化呢,这个过程有http协议去完成即可

代码验证

验证这个过程只需要将服务端实现即可,客户端用浏览器去进行链接

细节

有时候通过网址去访问服务器时,并不会直接去指明去服务器的哪一个路径找资源,这时候不指明的情况就会默认到服务器的默认路径。例如直接输入 baidu.com 就会跳到百度的首页。因此在编写代码时要加上一个默认的路径

Util.hpp

编写一个工具类,将一些调用的方法放到里面

#pragma once

#include <iostream>
#include <string>
using namespace std;

class Util
{
public:
    // 提取并删除首行
    // 读到的首行并不需要处理
    static string GetOneLine(string &inbuffer, const string &sep)
    {
        auto pos = inbuffer.find(sep);
        if(pos == string::npos)
            return "";

        string sub = inbuffer.substr(0, pos);
        inbuffer.erase(0, sub.size() + sep.size());
        return sub;
    }
};

protocol.hpp

编写请求和响应类

#pragma once

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

using namespace std;

class HttpRequest
{
public:
    string _inbuffer; // 接收请求数据
    string _method; // 处理数据方法的名称
    string _url; // url
    string _httpversion; //  http协议版本
    string _path; // 查找资源的路径

    HttpRequest(){}

    // 处理收到的数据
    // 添加默认路径
    void parse()
    {
// 定义分隔符
#define sep "\r\n"
#define default_root "./wwwroot"
#define home_page "index.html"
        // 拿到第一行,并删除
        string line = Util::GetOneLine(_inbuffer, sep);
        if(line.empty())
            return;
        cout << "line: " << line << endl;
        // 拿到第一行中的三个字段
        stringstream ss(line);
        ss >> _method >> _url >> _httpversion;

        // 添加默认路径
        _path = default_root;
        _path += _url;
        // 如果url为/ 则添加默认路径
        if(_path[_path.size() - 1] == '/')
            _path += home_page;
    }
};

class HttpResponse
{
public:
    string _outbuffer;
};

Server.hpp

编写服务端

#pragma once

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

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

class Server
{
public:
    Server(func_t func, uint16_t &port)
        : _port(port), _func(func)
    {
    }

    void Init()
    {
        // 创建负责监听的套接字 面向字节流
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
            exit(1);

        // 绑定网络信息
        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(3);

        // 设置socket为监听状态
        if (listen(_listenSock, 5) < 0)
            exit(4);
    }

    // 服务端读取处理请求方法
    void HttpHandler(int sock)
    {
        // 确保读到完整的http请求
        char buffer[4096];
        size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        HttpRequest req;
        HttpResponse res;
        if (n > 0)
        {
            buffer[n] = 0;
            req._inbuffer = buffer;
            // 处理读到的数据
            req.parse();
            // 调用回调方法反序列化请求并得到响应结果和序列化响应结果
            _func(req, res);
            // 发回客户端
            send(sock, res._outbuffer.c_str(), res._outbuffer.size(), 0);
        }
    }

    void start()
    {
        while (1)
        {
            // server获取建立新连接
            struct sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            // 创建通信的套接字
            // accept的返回值才是真正用于通信的套接字
            _sock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (_sock < 0)
                continue;
            cout << "sock: " << _sock << endl;

            // 利用多进程实现
            pid_t id = fork();
            if (id == 0) // child
            {
                close(_listenSock);
                // 调用方法包括读取、反序列化、计算、序列化、发送
                HttpHandler(_sock);
                close(_sock);
                exit(0);
            }
            close(_sock);

            // father
            pid_t ret = waitpid(id, nullptr, 0);
        }
    }

private:
    int _listenSock; // 负责监听的套接字
    int _sock;       // 通信的套接字
    uint16_t _port;  // 端口号
    func_t _func;
};

Server.cc

#include "Server.hpp"
#include <memory>

// 输出命令错误函数
void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}

// 服务端处理的回调函数
bool func(const HttpRequest &req, HttpResponse &res)
{
    // 打印方便调试查看接收到的数据是否正确
    cout << "---------------http--------------" << endl;
    cout << req._inbuffer;
    cout << "_method: " << req._method << endl;
    cout << " _url: " << req._url << endl;
    cout << " _httpversion: " << req._httpversion << endl;
    cout << " _path: " << req._path << endl;
    cout << "---------------end---------------" << endl;

    // 状态行
    string resline = "HTTP/1.1 200 ok\r\n";
    // 响应报头
    string rescontet = "contet-type: text/html\r\n";
    // 空行
    string resblank = "\r\n";
    // 响应正文:html代码格式,浏览器自动识别
    string body = "<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>My html</title><h1>hello world</h1></head><body><p>这是我的网页</p></body></html>";

    // 写回响应的数据,后续要发送回客户端
    res._outbuffer += resline;
    res._outbuffer += rescontet;
    res._outbuffer += resblank;
    res._outbuffer += body;

    return true;
}

int main(int argc, char *argv[])
{
    // 启动服务端不需要指定IP
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = atoi(argv[1]);

    unique_ptr<Server> server(new Server(func, port));

    // 服务端初始化
    server->Init();
    // 服务端启动
    server->start();

    return 0;
}

结果分析

image-20230807205605889

可以看到,用浏览器充当客户端后,浏览器会向服务器发起请求,因为代码里有写了将收到的数据打印,就按照格式将内容打印了出来。

从内容里可以看出客户端的系统和版本。服务端响应后发回数据到浏览器,因为代码中用html的代码去传送,所以浏览器自动识别显示出了网页。

这篇文章主要是将如果能够通信的了,并没有业务逻辑。

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

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

相关文章

八、Spring 整合 MyBatis

文章目录 一、Spring 整合 MyBatis 的关键点二、Spring 整合 MyBatis 的步骤2.1 创建 Maven 项目&#xff0c;并导入相关依赖2.2 配置 Mybatis 部分2.3 配置 Spring 部分2.3 配置测试类 一、Spring 整合 MyBatis 的关键点 1、 将 Mybatis 的 DataSource (数据来源)的创建和管理…

单片机外部晶振故障后自动切换内部晶振——以STM32为例

单片机外部晶振故障后自动切换内部晶振——以STM32为例 作者日期版本说明Dog Tao2023.08.02V1.0发布初始版本 文章目录 单片机外部晶振故障后自动切换内部晶振——以STM32为例背景外部晶振与内部振荡器STM32F103时钟系统STM32F407时钟系统 代码实现系统时钟设置流程时钟源检测…

郭盛华:npm 软件包窃取开发人员的敏感数据

网络安全研究人员在 npm 软件包注册表中发现了一系列新的恶意软件包&#xff0c;这些软件包旨在窃取敏感的开发人员信息。 所有模块的一个共同功能是能够启动 JavaScript&#xff08;“index.js”&#xff09;&#xff0c;该 JavaScript 可以将有价值的信息泄露到远程服务器。…

HCIP 三层交换机

一、实现VLAN间通信 在传统的交换机组网中&#xff0c;默认所有网络都处于同一个广播域&#xff0c;带来了许多问题&#xff0c;VLAN技术的提出&#xff0c;满足了二层组网隔离广播域需求&#xff0c;使得属于不同的VLAN间网络无法通信&#xff0c;但不同VLAN之间又存在着互相…

HTML之表单标签

目录 表单标签 Form表单 定义&#xff1a; 基本语法结构&#xff1a; form属性&#xff1a; enctyoe属性 fieldeset标签 fieldeset属性 legend标签 label标签 优势 label属性 input标签 input属性 input标签中的type属性 text text输入框有以下配套属性 searc bu…

MySQL ROUND、FORMAT数值格式化,以及 返回版本、返回数据库等信息

FORMAT VS ROUND ROUND(数值&#xff0c;n) FORMAT(数值&#xff0c;n) 当 n < 0, FORMAT 整数部分&#xff0c;不会变化&#xff0c; ROUND&#xff0c;整数部分&#xff0c; 根据n的位数&#xff0c;把数值替换成0 当 n>0, 两个效果一样

【数据结构OJ题】移除元素

原题链接&#xff1a;https://leetcode.cn/problems/remove-element/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 方法一&#xff1a;暴力删除&#xff0c;挪动数据覆盖。即遍历整个nums[ ]数组&#xff0c;遇到值等于val的元素&#xff0c;就将整…

模块高可用性部署概述

高可用三种模式 Ⅰ. 主从热备Ⅱ. 哨兵模式Ⅲ. 集群模式Ⅳ. 番外 总结了Redis的几种高可用方式&#xff0c;对于做后端模块的同学来说&#xff0c;可以根据/或是借鉴其思路做自己的模块可用性部署。 Ⅰ. 主从热备 主从热备&#xff0c;是高可用性中最基础的解决方案&#xff0…

详解Quest 2积分与奖励规则

7月28日&#xff0c;在万众期待中&#xff0c;Mysten Labs在Quest门户网站上宣布了Quest 2的到来。经过严密的筹划&#xff0c;本着真实、公平以及用户至上的原则&#xff0c;现在向大家介绍Quest 2的积分规则以及奖励规则。 温馨提示&#xff1a;第一轮Bullshark Quest是一次精…

模拟实现消息队列项目(系列2) -- 项目前期的准备

目录 前言 1. 需求分析 1.1 核心概念 1.2 核心API 1.3 交换机类型 1.4 持久化 1.5 网络通信 1.6 消息应答 2. 模块划分 结语 前言 我们在上一个系列对于消息队列有了初步的认识,那我们明白了消息队列的用途之后,我们就开始进行我们的项目了,首先我们的项目是仿照Rabb…

【Spring Boot】(三)深入理解 Spring Boot 日志

文章目录 前言一、日志文件的作用二、Spring Boot 中的日志2.1 查看输出的日志信息2.2 日志格式二、Spring Boot 中的日志2.1 查看输出的日志信息2.2 日志格式 三、自定义日志输出3.1 日志框架3.2 日志对象的获取3.3 使用日志对象打印日志 四、日志级别4.1 日志级别的作用4.2 日…

springboot配置文件的使用

目录 1.application.properties是springboot默认的配置文件&#xff0c;但是比较繁琐&#xff0c;一般用.yml文件 2. 配置文件的作用 3.配置文件的使用 1.application.properties是springboot默认的配置文件&#xff0c;但是比较繁琐&#xff0c;一般用.yml文件 ①、properti…

我在leetcode用动态规划炒股

事情是这样的&#xff0c;突然兴起的我在letcode刷题 121. 买卖股票的最佳时机122. 买卖股票的最佳时机 II123. 买卖股票的最佳时机 III 以上三题。 1. 121. 买卖股票的最佳时机 1.1. 暴力遍历&#xff0c;两次遍历 1.1.1. 算法代码 public class Solution {public int Ma…

webpack基础知识八:说说如何借助webpack来优化前端性能?

一、背景 随着前端的项目逐渐扩大&#xff0c;必然会带来的一个问题就是性能 尤其在大型复杂的项目中&#xff0c;前端业务可能因为一个小小的数据依赖&#xff0c;导致整个页面卡顿甚至奔溃 一般项目在完成后&#xff0c;会通过webpack进行打包&#xff0c;利用webpack对前…

使用事件侦听器和 MATLAB GUI 查看 Simulink 信号研究

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Linux(三):Linux服务器下日常实操命令 (常年更新)

基础命令 cd命令&#xff1a;切换目录 cd &#xff1a;切换当前目录百至其它目录&#xff0c;比如进入/etc目录&#xff0c;则执行 cd /etccd / &#xff1a;在Linux 系统中斜杠“/”表示的是根目录。cd / ,即进入根目录.cd ~&#xff1a;进入用户在该系统的home目录&#…

Linux——设备树

目录 一、Linux 设备树的由来 二、Linux设备树的目的 1.平台识别 2.实时配置 3.设备植入 三、Linux 设备树的使用 1.基本数据格式 2.设备树实例解析 四、使用设备树的LED 驱动 五、习题 一、Linux 设备树的由来 在 Linux 内核源码的ARM 体系结构引入设备树之前&#x…

【CSS】圆形放大的hover效果

效果 index.html <!DOCTYPE html> <html><head><title> Document </title><link type"text/css" rel"styleSheet" href"index.css" /></head><body><div class"avatar"></…

机器学习常用Python库安装

机器学习常用Python库安装 作者日期版本说明Dog Tao2022.06.16V1.0开始建立文档 文章目录 机器学习常用Python库安装Anaconda简介使用镜像源配置 Pip简介镜像源配置 CUDAPytorch安装旧版本 TensorFlowGPU支持说明 DGL简介安装DGLLife RDKitscikit-multilearn Anaconda 简介 …

英语使用场景口语

HOTEL ENGLISH hotel motel inn b&b Process 1.booking a room can i reserve a room? reservation do you have and singles? double room standard room deluxe room presidential suite do you have a pick-up service? 2.checking in where is the recept…