医院网站建设医生需要做什么,网络运维工程师工资,做公司,科技项目申报文章目录 OSI七层模型http协议域名路径信息请求和响应 编写一个httpserver OSI七层模型
在结束了前面对于序列化反序列化等内容的学习后#xff0c;重新回到对于OSI模型的部分 如上所示的是对于OSI接口的示意图#xff0c;在这当中可以看到会话层的概念#xff0c;会话层的… 文章目录 OSI七层模型http协议域名路径信息请求和响应 编写一个httpserver OSI七层模型
在结束了前面对于序列化反序列化等内容的学习后重新回到对于OSI模型的部分 如上所示的是对于OSI接口的示意图在这当中可以看到会话层的概念会话层的作用主要是建立断开连接管理的相当于是在网络服务器中在TCP server中获取链接的过程如果不需要这个链接就在TCP server中把对应的链接关掉而下面的表示层中写的是固有的数据格式和网络数据的转换比如有接受不同形式的信息文字图像声音等等这个表示层其实就是在前面的部分中定义的协议
这个协议表示的就是固有的数据格式比如说是序列化和反序列化这些内容再比如有说网络序列转换为主机序列或者是在缓冲区中定义了各种视频的名称格式等等这些都是属于自定义协议的范畴因此这些内容都是属于是表示层当中的信息
最后是应用层应用层中有针对于特定应用的协议比如说有电子邮件远程传输的问题再比如有传输数据解析的问题这些其实都是在应用层需要解决的问题
所以说这个OSI的这些层次结构就是已经提前定制好了未来的发展方式把网络中需要的各个功能和网络中每一层的能力就都定制好了那在表示层和应用层层相对比较好理解一些无非就是定制协议的过程那对于会话层的理解该如何深入理解呢
对于会话的概念并不陌生在将Linux终端的时候就有提及过这个session的概念而当客户端链接到服务端后如果一直不给服务器发送数据那么对应的服务端就会一直阻塞在这里那未来可以设置对应的一些策略比如说超过5秒钟部进行发送消息就把对应的链接自动关闭
因此在定制OSI模型的时候实际上会话表示应用这三层是不能定义在内核当中的原因就是无论是这些当中的那一层最终都必须要根据使用场景的不同由程序员自己定义出对应的协议信息然后才能有对应的序列化和反序列化的方法如果用操作系统来全部实现自然会引起一定的问题所以就都交给用户自己来进行实现
http协议
域名
在前面的认知中对于访问网络服务的概念是可以使用一个ip地址和端口号来访问唯一一台主机上的唯一的一个进程所以我们才可以用ip加端口号的方式来进行访问但是现在的问题是平时使用访问的都是使用一个网址来进行访问没有使用端口号这该如何理解呢
事实上的确在技术上用ip地址和端口号就可以实现这样的功能但是在日常生活中作为用户会直接使用一个域名的内容来进行访问网站因为域名的名字相比起来更加的好记忆尤其是对于一些大型的网站都是直接用拼音来进行标注方便用户来进行访问信息
所以就诞生了域名的概念域名实际上在背后会被解析为对应的服务这个技术上叫做是域名解析的概念通过这个技术就能实现把域名转换为ip地址因此当浏览器访问了特定的域名的时候浏览器内部会内置一些地址就能访问对应的域名解析的服务这样就能转换为ip地址我们以下面的这个例子为例
当访问百度一下这个网址的时候都是直接访问这个www.baidu.com这个网址而当在浏览器中访问的时候其实在背后已经把这个域名转换成ip地址了那这个怎么看呢
在命令行中可以使用这样的命令来进行查看 由此我们就可以根据这个信息来进行访问百度了 https协议默认的端口号是80所以本质上访问的是39.156.66.18:80/关于这个问题后续再进行详谈
路径信息
对于每一个网址来说其实是有固定的规则的 未来会对带层次的文件路径进行解析其他的字段不进行额外的讲解
请求和响应
下一步要谈论的主题是关于http中的请求和响应的问题
http请求
http的请求是有固定格式的下图画出http请求的具体格式 下图展示的是http的一个请求 如图所示的是http的一个请求第一行表示的是POST方法后面带有对应的URL网址信息还有http协议版本还有换行分割符这些信息后续的内容都是对应的是请求报头其中包含有对应的Key和Value信息在结束之后是一个空行也是作为一个分割的作用最后一行的信息表示的是请求的正文在这里表示的是一个用户名和密码的信息后续会实现这个内容
这里对于这个\r\n进行一个理解这两个表示的是对于请求来说都是按行读取这样可以读取到完整的一行读取到完整的一行之后就能对于报头和有效载荷进行分离进而进行后续的一些操作那么现在的问题是在对于读取网络请求的时候如何保证读取到的是一个完整的http请求呢具体的做法是分析它的长度并进行提取这是我们之前的做法而我们之前的做法本质上是一个自定义协议而在对应的http协议如何保证答案是保证不了但是因为有这个\r和\n的存在所以可以保证的是可以完整的读取到一个http的报头之后就能进行解析而在解析内容中就包含有长度这样的字段所以说想要实现一个http的请求正确读取是一个非常繁琐的过程
http响应
那http响应该如何理解呢在http的响应和请求几乎是完全一样的 所以说此时对于请求和响应已经有了一个基本的视线了那么下一步就是要进行序列化和反序列化然后再进行对应的分割就可以这都是可以解决的问题所以在未来进行使用http实现网络通信的时候就是一个发送请求进行响应再发送请求再进行响应的一个过程
编写一个httpserver
那么下一步就直接进行编写一个httpserver来进行测试这里我就直接用一份已经完备的httpserver来进行测试主要想法是根据实验现象来理解http背后的细节原理
// socket.hpp
#pragma once#include iostream
#include string
#include unistd.h
#include cstring
#include sys/types.h
#include sys/stat.h
#include sys/socket.h
#include arpa/inet.h
#include netinet/in.h
#include Log.hppenum
{SocketErr 2,BindErr,ListenErr,
};// TODO
const int backlog 10;class Sock
{
public:Sock(){}~Sock(){}public:void Socket(){sockfd_ socket(AF_INET, SOCK_STREAM, 0);if (sockfd_ 0){lg(Fatal, socker error, %s: %d, strerror(errno), errno);exit(SocketErr);}int opt 1;setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));}void Bind(uint16_t port){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(sockfd_, (struct sockaddr *)local, sizeof(local)) 0){lg(Fatal, bind error, %s: %d, strerror(errno), errno);exit(BindErr);}}void Listen(){if (listen(sockfd_, backlog) 0){lg(Fatal, listen error, %s: %d, strerror(errno), errno);exit(ListenErr);}}int Accept(std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len sizeof(peer);int newfd accept(sockfd_, (struct sockaddr*)peer, len);if(newfd 0){lg(Warning, accept error, %s: %d, strerror(errno), errno);return -1;}char ipstr[64];inet_ntop(AF_INET, peer.sin_addr, ipstr, sizeof(ipstr));*clientip ipstr;*clientport ntohs(peer.sin_port);return newfd;}bool Connect(const std::string ip, const uint16_t port){struct sockaddr_in peer;memset(peer, 0, sizeof(peer));peer.sin_family AF_INET;peer.sin_port htons(port);inet_pton(AF_INET, ip.c_str(), (peer.sin_addr));int n connect(sockfd_, (struct sockaddr*)peer, sizeof(peer));if(n -1) {std::cerr connect to ip : port error std::endl;return false;}return true;}void Close(){close(sockfd_);}int Fd(){return sockfd_;}private:int sockfd_;
};
#pragma once
#include iostream
#include string
#include pthread.h
#include fstream
#include vector
#include sstream
#include sys/types.h
#include sys/socket.h
#include unordered_map
#include Socket.hpp
#include Log.hppconst std::string wwwroot ./wwwroot; // web 根目录
const std::string sep \r\n;
const std::string homepage index.html;static const int defaultport 8082;class HttpServer;class ThreadData
{
public:ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s){}public:int sockfd;HttpServer *svr;
};// 定义一个http的请求信息
class HttpRequest
{
public:// 对信息进行反序列化void Deserialize(std::string req){while (true){std::size_t pos req.find(sep);if (pos std::string::npos)break;std::string temp req.substr(0, pos);if (temp.empty())break;req_header.push_back(temp);req.erase(0, pos sep.size());}// 存储请求体的信息text req;}// .png:image/png// 对信息进行解析void Parse(){// 解析请求的方法URLhttp版本std::stringstream ss(req_header[0]);ss method url http_version;// 设置默认寻找的路径是wwwroot这个目录file_path wwwroot; // ./wwwroot// 对于URL文件的路径进行一定的拼接if (url / || url /index.html){file_path /;file_path homepage; // ./wwwroot/index.html}elsefile_path url; // /a/b/c/d.html-./wwwroot/a/b/c/d.html// 查找文件的后缀默认是html否则就进行合适的拼接auto pos file_path.rfind(.);if (pos std::string::npos)suffix .html;elsesuffix file_path.substr(pos);}// 打印调试信息void DebugPrint(){for (auto line : req_header){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: file_path std::endl;std::cout text std::endl;}public:// 请求信息中至少要包含下面的内容std::vectorstd::string req_header;std::string text;// 解析之后的结果std::string method;std::string url;std::string http_version;std::string file_path; // ./wwwroot/a/b/c.html 2.png// 文件后缀std::string suffix;
};// http服务端
class HttpServer
{
public:HttpServer(uint16_t port defaultport) : port_(port){content_type.insert({.html, text/html});content_type.insert({.png, image/png});}bool Start(){// 进行套接字的创建绑定设置监听listensock_.Socket();listensock_.Bind(port_);listensock_.Listen();for (;;){std::string clientip;uint16_t clientport;// 从监听信息中提起对应的ip和端口号信息当有用户连接的时候会进行接受对应的信息int sockfd listensock_.Accept(clientip, clientport);if (sockfd 0)continue;lg(Info, get a new connect, sockfd: %d, sockfd);pthread_t tid;ThreadData *td new ThreadData(sockfd, this);// 创建线程去执行对应的handle响应方法pthread_create(tid, nullptr, ThreadRun, td);}}// 读取html的内容static std::string ReadHtmlContent(const std::string htmlpath){std::ifstream in(htmlpath, std::ios::binary);if (!in.is_open())return ;in.seekg(0, std::ios_base::end);auto len in.tellg();in.seekg(0, std::ios_base::beg);std::string content;content.resize(len);in.read((char *)content.c_str(), content.size());in.close();return content;}// 将后缀名替换为描述信息std::string SuffixToDesc(const std::string suffix){auto iter content_type.find(suffix);if (iter content_type.end())return content_type[.html];elsereturn content_type[suffix];}// 处理http的请求信息void HandlerHttp(int sockfd){char buffer[10240];// 进行收消息的过程ssize_t n recv(sockfd, buffer, sizeof(buffer) - 1, 0); // bugif (n 0){buffer[n] 0;std::cout buffer std::endl;// 将接受到的请求信息进行反序列化HttpRequest req;req.Deserialize(buffer);// 对于反序列化后的内容进行解析解析为当前所需要的信息内容req.Parse();// 返回响应的过程std::string text;bool ok true;text ReadHtmlContent(req.file_path); // 失败if (text.empty()){ok false;std::string err_html wwwroot;err_html /;err_html err.html;text ReadHtmlContent(err_html);}// 设置响应状态行默认失败是404状态std::string response_line;if (ok)response_line HTTP/1.0 200 OK\r\n;elseresponse_line HTTP/1.0 404 Not Found\r\n;// 进行字符串的拼接// response_line HTTP/1.0 302 Found\r\n;std::string response_header Content-Length: ;response_header std::to_string(text.size()); // Content-Length: 11response_header \r\n;response_header Content-Type: ;response_header SuffixToDesc(req.suffix);response_header \r\n;response_header Set-Cookie: namehahapasswd12345;response_header \r\n;// response_header Location: https://www.qq.com\r\n;std::string blank_line \r\n; // \nstd::string response response_line;response response_header;response blank_line;response text;// 把拼接好的字符串响应再发送回去这样就完成了一次通信send(sockfd, response.c_str(), response.size(), 0);}close(sockfd);}static void *ThreadRun(void *args){// 对该线程进行设置使得主线程不必关心这个线程pthread_detach(pthread_self());ThreadData *td static_castThreadData *(args);// 去执行对应的handle方法td-svr-HandlerHttp(td-sockfd);delete td;return nullptr;}~HttpServer(){}private:Sock listensock_;uint16_t port_;std::unordered_mapstd::string, std::string content_type;
};