网络协议栈--应用层--HTTP协议

目录

  • 本节重点
    • 理解应用层的作用, 初识HTTP协议
  • 一、应用层
  • 二、HTTP协议
    • 2.1 认识URL
    • 2.2 urlencode和urldecode
    • 2.3 HTTP协议格式
    • 2.4 HTTP的方法
    • 2.4 HTTP的状态码
    • 2.5 HTTP常见的Header属性
  • 三、最简单的HTTP服务器
    • 3.1 HttpServer.hpp
    • 3.2 HttpServer.cc
    • 3.3 HttpClient.cc
    • 3.4 log.hpp
    • 3.5
    • 3.6 makefile
    • 3.7 wwwroot目录下的资源
  • 四、HTTP协议内容一览图

本节重点

理解应用层的作用, 初识HTTP协议

一、应用层

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

二、HTTP协议

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

2.1 认识URL

平时我们俗称的 “网址” 其实就是说的 URL。
在这里插入图片描述

2.2 urlencode和urldecode

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

2.3 HTTP协议格式

HTTP请求:
在这里插入图片描述
(1)请求行: [请求方法] + [url] + [HTTP版本]。

(2)请求报头(Header): 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;

(3)空行:遇到单单一个\n空行表示Header部分结束.

(4)请求正文: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

HTTP响应:
在这里插入图片描述
(1)状态行: [HTTP版本] + [状态码] + [状态码描述]
(2)响应报头(Header): 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;
(3)空行:遇到单单一个\n空行表示Header部分结束。
(4)响应正文:空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中。

2.4 HTTP的方法

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

2.4 HTTP的状态码

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

2.5 HTTP常见的Header属性

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

三、最简单的HTTP服务器

实现一个最简单的HTTP服务器, 只在网页上输出 “hello world”; 只要我们按照HTTP协议的要求构造数据, 就很容易能做到;
我们以下的服务器是添加了一点前端的代码的:

3.1 HttpServer.hpp

#pragma once#include <iostream>
#include "Socket.hpp"
#include <functional>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#include <unordered_map>// HTTP协议可以以"\r\n"作为行分隔符,也可以是"\n"
const std::string sep = "\r\n";// 传说中的web根目录
const std::string wwwroot = "./wwwroot";// 服务器主页
const std::string homepage = "index.html";const uint16_t DEFAULT_PORT = 8080;class HttpServer;class ThreadData
{
public:ThreadData(int sockfd, HttpServer *svr): _sockfd(sockfd), _svr(svr){}public:int _sockfd;HttpServer *_svr;
};class Request
{
public:// 反序列化void Deserialize(std::string req){while (true){// 用行分隔符"\r\n",分割请求报头中的属性信息auto pos = req.find(sep);if (pos == string::npos){break;}std::string tmp = req.substr(0, pos);if (tmp.empty()){// 说明读取到了空行,即报头已经读完了req.erase(pos, sep.size());break;}_req_head.push_back(tmp);// 没获取一条属性信息记得在原报头中删除req.erase(0, pos + sep.size());}// 取出报头之后得到的剩余的就是请求正文_body = req;}// 解析http协议void Parse(){std::string req_line = _req_head[0];//stringstream默认是以空格作为分隔符的std::stringstream ss(req_line);//必须按顺序ss >> _method >> _url >> _http_version;// 拼接资源的路径_filepath = wwwroot;if (_url == "/" || _url == "/index.html"){_filepath += "/";_filepath += homepage;}else{_filepath += _url;}//从后往前找到'.',从而找出后缀auto pos = _filepath.rfind('.');if (pos == string::npos){_suffix = ".html";}else{_suffix = _filepath.substr(pos);}}//用来调试的void DebugPrint(){for (auto &line : _req_head){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: " << _filepath << std::endl;std::cout << _body << std::endl;}std::string GetFilePath(){return _filepath;}std::string GetFileSuffix(){return _suffix;}private:std::string _method;                     //请求方法std::string _url;                        //请求的资源的urlstd::string _http_version;               //http协议版本号std::vector<std::string> _req_head;      //请求报头:包括请求行、报头属性信息std::string _body;                       //请求正文std::string _filepath;                   //文件的路径,从web根目录开始,即wwwroot/urlstd::string _suffix;                     //url的后缀
};class HttpServer
{
public:HttpServer(const uint16_t &port = DEFAULT_PORT): _port(port){//.html对应的网页是文本类型_content_type[".html"] = "text/html";//  _content_type[".html"] = "application/json";//.png对应的网页是图片_content_type[".png"] = "image/png";}~HttpServer(){}void InitHttpServer(){// 1、创建套接字_listen_sock.Socket();// 2、绑定_listen_sock.Bind(_port);// 3、监听_listen_sock.Listen();}static string ReadIndexHtml(const std::string &path){// 有坑?std::string str;//注意,这里一定要用二进制的方式去读取,否则类似于图片的文件就有可能会读取出//错从而在访问的时候看不到图片ifstream in(path, std::ios::binary);if (!in.is_open()){return "";}//对于seekg函数,0表示偏移量,std::ios_base::end是基准,//意思是:设置当前位置相对于最后一个位置的偏移量是0,//说明当前指针的位置就指向文件内容的最后一个位置in.seekg(0, std::ios_base::end);//对于tellg函数,是获取当前读取位置的,因为上面已经设置了//当前指针指向的位置是文件内容的最后一个位置,所以当前位置//的数值就等于文件内容的大小,即一共有len个自己的内容int len = in.tellg();//同上,即设置指针指向文件的开始位置in.seekg(0, std::ios_base::beg);std::string content;content.resize(len);//把内容读取到content中in.read((char *)content.c_str(), len);in.close();return content;}//url的后缀转换成对应的后缀描述,用于构建响应std::string SuffixToDesc(const std::string &suffix){auto ret = _content_type.find(suffix);if (ret == _content_type.end()){//如果从类型中没有找到,那就统一当成是.html后缀//返回对应的文本类型的描述"text/html"return _content_type[".html"];}else{return _content_type[suffix];}}static void *Handler(void *args){//线程分离pthread_detach(pthread_self());ThreadData *ptd = static_cast<ThreadData *>(args);int sockfd = ptd->_sockfd;while (true){char buffer[10240] = {0};ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = '\0';std::cout << buffer << std::endl;//解析http协议请求Request req;req.Deserialize(buffer);req.Parse();req.DebugPrint();// 构建http响应std::string response;bool ok = true;//根据请求的url,读取对应的文件内容作为响应的正文std::string response_body = ReadIndexHtml(req.GetFilePath());if (response_body.empty()){ok = false;std::string err_html = wwwroot;err_html += '/';err_html += "err.html";response_body = ReadIndexHtml(err_html);}// 状态行std::string state_line;if (ok){state_line = "HTTP/1.0 200 OK\r\n";}else{state_line = "HTTP/1.0 404 Not Found\r\n";}response += state_line;//做重定向的状态行//response = "HTTP/1.0 302 Found\r\n";// 响应报头std::string response_head;//属性1:content-lenthstd::string content_lenth = "Content-Lenth: ";content_lenth + to_string(response_body.size());//属性2:content-typestd::string content_type = "Content-Type: ";content_type += ptd->_svr->SuffixToDesc(req.GetFileSuffix());response_head += content_lenth;response_head += "\r\n";response_head += content_type;response_head += "\r\n";//属性3:set cookieresponse_head += "Set-Cookie: name=kobe&&passwd=123456";response_head += "\r\n";//属性4:Location,设置重定向时需要访问的网址// response_head += "Location: http://www.qq.com/";// response_head += "\r\n";response += response_head;// 空行response += "\r\n";// 正文response += response_body;ssize_t n = send(sockfd, response.c_str(), response.size(), 0);}else if (n == 0){log(Info, "client quit...,关闭连接:%d", sockfd);close(sockfd);return nullptr;}else if (n < 0){std::cout << "n=" << n << std::endl;log(Error, "recv error,关闭连接:%d", sockfd);close(sockfd);return nullptr;}}log(Info, "服务器退出,关闭连接:%d", sockfd);close(sockfd);return nullptr;}void Start(){while (true){std::string client_ip;uint16_t client_port;int sockfd = _listen_sock.Accept(client_ip, client_port);if (sockfd < 0){continue;}pthread_t tid;ThreadData td(sockfd, this);// 创建线程pthread_create(&tid, nullptr, Handler, (void *)(&td));}}private:Sock _listen_sock;uint16_t _port;std::unordered_map<std::string, std::string> _content_type;
};

3.2 HttpServer.cc

#include "HttpServer.hpp"
#include <memory>void Usage(const std::string& proc)
{std::cout<<"\t\n"<<std::endl;std::cout<<"Usage: "<<proc<<" server_port[>=1024]"<<std::endl<<std::endl;
}int main(int argc, char* argv[])
{if(argc!=2){Usage(argv[0]);exit(1);}uint16_t server_port=(uint16_t)stoi(argv[1]);std::unique_ptr<HttpServer> svr(new HttpServer(server_port));svr->InitHttpServer();svr->Start();return 0;
}

3.3 HttpClient.cc

提示一下:如果直接用HttpClient.cc访问服务器,那么需要按照HTTP协议的标准格式构建报文才能正确地返回,否则服务器会发生段错误的。

#include "HttpServer.hpp"
#include "Socket.hpp"int main()
{Sock sock;sock.Socket();sock.Connect("43.138.156.240",8081);string buffer;while(true){std::cout<<"Please Enter:";std::getline(std::cin,buffer);send(sock.Sockfd(),buffer.c_str(),buffer.size(),0);sleep(1);char tmp[10240]={0};recv(sock.Sockfd(),tmp,sizeof(tmp),0);std::cout<<tmp<<std::endl;}return 0;
}

3.4 log.hpp

#pragma once#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include <time.h>
#include <stdarg.h>// 日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define OneFile 2
//向多个文件打印
#define Classfile 3
#define SIZE 1024#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int mothod){printMethod = mothod;}string LevelToString(int level){switch (level){case Info:{return "Info";}case Debug:{return "Debug";}case Warning:{return "Warning";}case Error:{return "Error";}case Fatal:{return "Fatal";}default:{return "None";}}}void printlog(int level,const string& logtxt){switch(printMethod){case Screen:{cout<<logtxt<<endl;break;}case OneFile:{PrintOneFile(LogFile,logtxt);break;}case Classfile:{PrintClassfile(level,logtxt);break;}default:{break;}}}void PrintOneFile(const string& logname,const string& logtxt){string _logname=path+logname;int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);if(fd<0){perror("open fail");return;}write(fd,logtxt.c_str(),logtxt.size());close(fd);}void PrintClassfile(int level,const string& logtxt){string filename=LogFile;filename+='.';filename+=LevelToString(level);PrintOneFile(filename,logtxt);}void operator()(int level,const char* format,...){time_t t=time(nullptr);struct tm* ctime=localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer,SIZE,"[%s][%d-%d-%d %d:%d:%d]",LevelToString(level).c_str(),ctime->tm_year+1900,ctime->tm_mon+1,ctime->tm_mday,ctime->tm_hour,ctime->tm_min,ctime->tm_sec);va_list s;va_start(s,format);char rightbuffer[SIZE]={0};vsnprintf(rightbuffer,SIZE,format,s);va_end(s);char logtxt[SIZE*2];snprintf(logtxt,sizeof(logtxt),"%s %s",leftbuffer,rightbuffer);printlog(level,logtxt);}~Log(){}private:// 打印方法int printMethod;string path;
};//定义一个全局的log
Log log;

3.5

#pragma once#include <iostream>
using namespace std;
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include <unistd.h>
#include <strings.h>
#include <cstring>
#include <string>int backlog = 10;enum
{SockErr = 2,BindErr,ListenErr,ConnectErr,
};class Sock
{
public:Sock(): _sockfd(-1){}~Sock(){if(_sockfd>0){close(_sockfd);}}// 创建套接字void Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0){log(Fatal, "socket failed,errno:%d,errstring:%s", errno, strerror(errno));exit(SockErr);}int opt=1;if(setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&opt,sizeof(opt))<0){log(Warning, "setsockopt failed, sockfd:%d", _sockfd);}log(Info, "setsockopt successed, sockfd:%d", _sockfd);log(Info, "socket successed, sockfd:%d", _sockfd);}// 绑定void Bind(const uint16_t &serverPort){struct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(serverPort);local.sin_addr.s_addr = INADDR_ANY;if (bind(_sockfd, (struct sockaddr *)(&local), sizeof(local)) < 0){log(Fatal, "bind failed,errno:%d,errstring:%s", errno, strerror(errno));exit(BindErr);}log(Info, "bind successed...");}// 监听void Listen(){if (listen(_sockfd, backlog) < 0){log(Fatal, "set listen state failed,errno:%d,errstring:%s", errno, strerror(errno));exit(ListenErr);}log(Info, "set listen state successed");}//获取连接int Accept(string& clientip,uint16_t& clientport){struct sockaddr_in client;socklen_t len=sizeof(client);bzero(&client,sizeof(client));int sockfd=accept(_sockfd,(struct sockaddr*)(&client),&len);if(sockfd<0){log(Warning, "accept new link failed,errno:%d,errstring:%s", errno, strerror(errno));return -1;}log(Info,"accept a new link...,sockfd:%d",sockfd);clientip=inet_ntoa(client.sin_addr);clientport=(uint16_t)(ntohs(client.sin_port));return sockfd;}// 连接void Connect(const string &serverIp, const uint16_t &serverPort){struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(serverIp.c_str());server.sin_port = htons(serverPort);if (connect(_sockfd, (struct sockaddr *)(&server), sizeof(server)) < 0){log(Fatal, "connect server failed,errno:%d,errstring:%s", errno, strerror(errno));exit(ConnectErr);}log(Info, "connect server succeeded...");}void Close(){if(_sockfd>0){close(_sockfd);}}int Sockfd(){return _sockfd;}private:int _sockfd;
};

3.6 makefile

.PHONY:all
all:http_server http_clienthttp_server:HttpServer.ccg++ -o $@ $^ -std=c++11 -lpthread http_client:HttpClient.ccg++ -o $@ $^ -std=c++11 .PHONY:clean
clean:rm -f http_server http_client

3.7 wwwroot目录下的资源

自行创建wwwroot目录并把资源按要求放到目录下:
web根目录下的资源

在这里插入图片描述
备注:
此处我们使用 8081 端口号启动了HTTP服务器. 虽然HTTP服务器一般使用80端口,但这只是一个通用的习惯. 并不是说HTTP服务器就不能使用其他的端口号.使用Edge测试我们的服务器时, 可以看到服务器打出的请求中还有一个
GET /favicon.ico HTTP/1.1 这样的请求。favicon.ico是用来设置网页上的小图标的:
在这里插入图片描述

可以试试把返回的状态码改成404, 403, 504等, 看浏览器上分别会出现什么样的效果。

四、HTTP协议内容一览图

在这里插入图片描述

以上就是今天想要跟大家分享的关于HTTP协议的所有内容了,你学会了吗?如果感觉到有所收获的话,那就点点小心心,再点点关注呗,后期还会持续更新有关Linux网络编程的相关知识哦,我们下期见!!!!!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Allure小白下载安装

1、下载官网地址&#xff1a;https://github.com/allure-framework/allure2/releases 2、下载安装包后需要解压到一个非中文名称路径下 3、配置环境变量 D:\Allure\allure-2.27.0\bin 我的电脑右键选择属性&#xff0c;高级系统设置&#xff0c;环境变量 4、CMD查看安装all…

QGIS3.34官方版本已经不能支持Win7,如果需要在WIN7上使用,请用微云上我打包的

在网上看到有些网友在WIN7上安装官方发布的QGIS安装&#xff0c;会遇到上述问题&#xff0c;而不能正常运行&#xff01; 我打包的QGIS可以在WIN7上正常运行&#xff0c;这个我专门测试过。 详见&#xff1a; 打包了一个QGIS3.34分享给大家 下载地址&#xff1a;文件分享 软…

WordPress供求插件API文档:用户登录

该文档为WordPress供求插件文档&#xff0c;详情请查看 WordPress供求插件&#xff1a;一款专注于同城生活信息发布的插件-CSDN博客文章浏览阅读67次。WordPress供求插件&#xff1a;sliver-urban-life 是一款专注于提供同城生活信息发布与查看的插件&#xff0c;该插件可以实…

水牛社:专为创业者和网赚小白精心打造的助手

近几年来&#xff0c;经济发展快是快&#xff0c;物价也在蹭蹭往上涨&#xff0c;但工资却不见明显提升&#xff0c;随着生活成本的增加&#xff0c;单单靠工资&#xff0c;已经很难维持生活开支&#xff0c;加之疫情的影响&#xff0c;很多行业发展不景气&#xff0c;一些岗位…

liunx操作系统 环境变量

环境变量 main函数参数 命令行参数环境变量 环境变量的查看环境变量的获取 main函数参数 命令行参数 main函数是有参数的&#xff0c;只是我们一般不适用 这是main函数从bash中读取进程数据使用的一个基本入口。 下面进行简单演示。 o 好oo都是我们输入的命令行参数。其实&a…

【CSP试题回顾】201503-3-节日

CSP-201503-3-节日 关键点&#xff1a;格式化输出 在C中&#xff0c;格式化输出通常利用iostream库中的功能&#xff0c;特别是iomanip头文件提供的一系列操作符。这些操作符用于控制输出格式&#xff0c;如宽度、填充、对齐方式等。在你提供的代码中&#xff0c;用于格式化输…

换个角度看禅让制止于禹

前言 在历史课本上&#xff0c;对尧、舜、禹三代君王的描述是&#xff0c;他们在去世之前通过禅让制的方式把自己的王位禅让给了其他有贤明的能臣。 禅让制也是中国古代时期被无数的文人志士追捧的一个制度&#xff0c;因为他们觉得那个时代是公天下的时代。 可实际上自从人…

lanqiao:合根植物

题目描述&#xff1a; 代码实现&#xff1a;

江苏某机场多座超高端智慧公厕上线

作为行业信息化程度最高的智慧机场综合管理系统&#xff0c;能为旅客、航空公司以及机场自身的业务管理提供及时、准确、系统、完整的信息服务&#xff0c;达到信息高度统一、共享、调度严密、管理先进和服务优质的目的。而其中的智慧卫生间建设&#xff0c;更是提升机场旅客服…

opengl 学习(二)-----你好,三角形

你好&#xff0c;三角形 分类demo效果解析 分类 opengl c demo #include "glad/glad.h" #include "glfw3.h" #include <iostream> #include <cmath> #include <vector>using namespace std;/** * 在学习此节之前&#xff0c;建议将这…

堆和堆排序

堆排序是一种与插入排序和并归排序十分不同的算法。 优先级队列 Priority Queue 优先级队列是类似于常规队列或堆栈数据结构的抽象数据类型&#xff08;ADT&#xff09;。优先级队列中的每个元素都有一个相关联的优先级key。在优先级队列中&#xff0c;高优先级的元素优先于…

【文件增量备份系统】使用Mysql的流式查询优化数据清理性能(针对百万量级数据)

文章目录 功能介绍原始方案测试 流式处理测试 功能可用性测试 功能介绍 清理功能的作用是&#xff1a;扫描数据库中已经备份过的文件&#xff0c;查看数据源中是否还有相应的文件&#xff0c;如果没有&#xff0c;说明该文件被删除了&#xff0c;那相应的&#xff0c;也需要将…