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

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

认识url

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

2c359f0d1606603427e1b056bdb2039a.png

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

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

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

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

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

022605c80251ee919e9ee8e06753ac48.gif

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


2d8da093cfdd81cdc1d8569ed8983c9a.png

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

http协议格式

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


250d9a8a53bced084f8eda832a130bc7.png

通信

那么两端的机器接收到了数据后,怎么样保证读取到一个完整的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;
}

结果分析

1c6faaa0cbb03e4667e7dd668923e06d.png


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

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

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


目录
相关文章
|
6月前
|
C# 图形学 开发者
Unity开发中使用UnityWebRequest从HTTP服务器下载资源。
总之,UnityWebRequest就是游戏开发者手中的万能钓鱼竿,既可以获取文本数据,也能钓上图片资源,甚至是那声音的涟漪。使用UnityWebRequest的时候,你需要精心准备,比如确定URL、配置请求类型和头信息;发起请求;巧妙处理钓获的数据;还需要机智面对网络波澜,处理各种可能出现的错误。按照这样的过程,数据的钓取将会是一次既轻松愉快也效率高效的编程钓鱼之旅。
317 18
|
6月前
|
应用服务中间件 网络安全 数据安全/隐私保护
网关服务器配置指南:实现自动DHCP地址分配、HTTP服务和SSH无密码登录。
哇哈哈,道具都准备好了,咱们的魔术秀就要开始了。现在,你的网关服务器已经魔法满满,自动分配IP,提供网页服务,SSH登录如入无人之境。而整个世界,只会知道效果,不会知道是你在幕后操控一切。这就是真正的数字世界魔法师,随手拈来,手到擒来。
364 14
|
5月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
5月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
408 0
|
2月前
|
弹性计算 运维 安全
阿里云轻量应用服务器与云服务器ECS啥区别?新手帮助教程
阿里云轻量应用服务器适合个人开发者搭建博客、测试环境等低流量场景,操作简单、成本低;ECS适用于企业级高负载业务,功能强大、灵活可扩展。二者在性能、网络、镜像及运维管理上差异显著,用户应根据实际需求选择。
276 10
|
2月前
|
运维 安全 Ubuntu
阿里云渠道商:服务器操作系统怎么选?
阿里云提供丰富操作系统镜像,涵盖Windows与主流Linux发行版。选型需综合技术兼容性、运维成本、安全稳定等因素。推荐Alibaba Cloud Linux、Ubuntu等用于Web与容器场景,Windows Server支撑.NET应用。建议优先选用LTS版本并进行测试验证,通过标准化镜像管理提升部署效率与一致性。
|
2月前
|
弹性计算 ice
阿里云4核8g服务器多少钱一年?1个月和1小时价格,省钱购买方法分享
阿里云4核8G服务器价格因实例类型而异,经济型e实例约159元/月,计算型c9i约371元/月,按小时计费最低0.45元。实际购买享折扣,1年最高可省至1578元,附主流ECS实例及CPU型号参考。
421 8
|
2月前
|
存储 监控 安全
阿里云渠道商:云服务器价格有什么变动?
阿里云带宽与存储费用呈基础资源降价、增值服务差异化趋势。企业应结合业务特点,通过阶梯计价、智能分层、弹性带宽等策略优化成本,借助云监控与预算预警机制,实现高效、可控的云资源管理。
|
2月前
|
弹性计算 运维 安全
区别及选择指南:阿里云轻量应用服务器与ECS云服务器有什么区别?
阿里云轻量应用服务器适合个人开发者、学生搭建博客、测试环境,易用且性价比高;ECS功能更强大,适合企业级应用如大数据、高流量网站。根据需求选择:轻量入门首选,ECS专业之选。
268 2
|
2月前
|
弹性计算 ice
阿里云4核8G云服务器配置价格:热门ECS实例及CPU处理器型号说明
阿里云2025年4核8G服务器配置价格汇总,涵盖经济型e实例、计算型c9i等热门ECS实例,CPU含Intel Xeon及AMD EPYC系列,月费159元起,年付低至1578元,按小时计费0.45元起,实际购买享折扣优惠。
560 1

热门文章

最新文章