新万网站建设,网站开发 技术优势,手机之家中关村,服务专业网页制作目录
编辑
一#xff0c;HTTP协议基本认识
二#xff0c;认识URL
三#xff0c;http协议的格式 1#xff0c;发送格式
2#xff0c;回应格式
四#xff0c;服务端代码
五#xff0c;http报文细节
1#xff0c;Post与Get方法
2#xff0c;Content_lenth 3HTTP协议基本认识
二认识URL
三http协议的格式 1发送格式
2回应格式
四服务端代码
五http报文细节
1Post与Get方法
2Content_lenth 3状态码
4Location
5content_type
6cookie
编辑 一HTTP协议基本认识 在我之前写的文章中我实现过自定义协议。但是在实际的网络编程中我们是不太需要定制协议的。因为前辈早就定制好了。而HTTP协议就是其中的一种。 http协议又被叫做超文本传输协议这是因为http的本质其实就是按照http协议从服务端拿文件资源。而http协议能够拿走所有的文件资源所以http协议又被叫做超文本传输协议。
二认识URL URLUniform Resource Locator中文名叫统一资源定位符。 URL的样子如下
http://www.example.com/path/to/resource?querystring#fragment http:// 是协议部分表示这是一个使用 HTTP 协议的 URL。www.example.com 是域名部分表示资源所在的服务器的地址。/path/to/resource 是路径部分表示服务器上资源的具体位置。?querystring 是查询字符串部分用于传递参数给服务器。#fragment 是片段标识符部分用于指定资源的某一部分通常用于网页的导航。 域名其实就是ip地址那为什么只要有ip地址就可以访问到主机上的唯一资源呢 因为http的端口号是默认绑定的是统一的不需要我们再来绑定。 urlencode与urldecode urlencode:在url中有些符号是被url默认使用了的。比如//。当我们的用户输入的url中带有这些字符时这些字符就会被encode。encode的规则是将对应的字符的ASSCALL码转化为16进制然后在前面加上%。urldecode就是反过来。当然这个过程并不需要我们来做有相应的工具可以帮我们完成urlencodeurldecode工具 三http协议的格式 1发送格式 当我们以http为协议向服务端发送请求时我们发送的数据会包含如下数据 1请求行 2请求报文 3一个空行 4请求正文 在请求行包含如下数据 1method 2url 3http version 4\r\n 图示如下 2回应格式 当服务器在将数据发送回客户端时会依照如下何时将数据返回 1状态行 2响应报文 3空行 4响应正文 在状态行内包含如下数据 1http version 2状态码 3状态码描述 4“\r\n 图示如下 四服务端代码
在了解了http发送消息和响应消息的数据发送格式以后我们便可以来动手写上一个能够按照http协议的格式进行响应的服务端。
实现如下
1为了能够更方便的使用创建套接字的接口我对创建套接字的接口做如下封装
#pragma once
#includeiostream
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#includecstring
#includearpa/inet.h
#includeunistd.h//定义一些变量
#define blog 10
#define defaultport 8080class Socket
{public://构造函数Socket(): sockfd_(0){}public://创建套接字bool Sock(){sockfd_ socket(AF_INET, SOCK_STREAM, 0); // 创建套接字if (sockfd_ 0){std::cerr 创建套接字失败 std::endl;return false;}int opt 1;setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt));return true; // 将创建好的套接字返回}//bind服务端只要绑定端口号bool Bind(int port defaultport){sockaddr_in server_addr;memset(server_addr, 0, sizeof (server_addr));//清空数据server_addr.sin_family AF_INET;server_addr.sin_port htons(port);server_addr.sin_addr.s_addr INADDR_ANY;int r1 bind(sockfd_,(sockaddr*)server_addr,sizeof server_addr);if(r10){std::cerr bind err std::endl;return false;}return true;}//监听bool Listen(){int r2 listen(sockfd_, blog);if(r20){std::cerr listen err std::endl;return 0;}return true;}//接收int Accept(std::string* ip,int* port){sockaddr_in cli_addr;socklen_t len sizeof(cli_addr);int sockfd accept(sockfd_, (sockaddr *)cli_addr, len);if(sockfd0){std::cerr accept err std::endl;return -1;}char buff[64]{0};inet_ntop(AF_INET, cli_addr, buff, sizeof(buff));*ip buff;*port ntohs(cli_addr.sin_port);return sockfd;}//连接bool Connect(std::string ip,int16_t port){sockaddr_in addr_;addr_.sin_family AF_INET;addr_.sin_port htons(port);inet_pton(AF_INET, ip.c_str(), (addr_.sin_addr));int r connect(sockfd_, (sockaddr *)addr_, sizeof (addr_));if(r0){std::cerr connect err std::endl;return false;}return true;}//关闭void Close(){close(sockfd_);}public://成员int sockfd_;
};
2服务端代码 在做好上述封装工作以后便可以来构建服务端了服务端的创建步骤如下
1类成员 1listensockfd_: Sock类成员。主要是为了调用创建套接字的接口。 2port_:端口号主要是为了绑定时使用。 2构造函数 初始化port_ 3,Init函数 创建套接字 bind套接字 监听套接字 4Start函数 1循环接收服务端发来的连接请求然后创建线程执行相应的任务。 2读出客户端发来的请求。 3然后根据http协议将请求处理出来到req中。 4根据url判断客户端想要请求的资源是服务器那个资源。 5读出资源发送回客户端。 在理清上面创建服务端的过程以后便可以写出如下代码 struct ThreadData
{int sockfd;
};class HttpServer
{public:HttpServer(int port):port_(port){} void Init(){listensocket_.Sock();//创建套接字listensocket_.Bind(port_);//绑定套接字端口listensocket_.Listen();//监听套接字}bool Start(){while (true){// 建立连接std::string ip;int port;int sockfd listensocket_.Accept(ip, port_);lg(Debug, link-%s:%d sucess,sockfd:%d, ip.c_str(), port_,sockfd);// 创建线程执行任务pthread_t td;ThreadData ts;ts.sockfd sockfd;pthread_create(td, nullptr, ThreadRun, ts);}return true;}static void* ThreadRun(void*args){pthread_detach(pthread_self());//与主线程分离ThreadData* ts static_castThreadData*(args);Helper(ts-sockfd);//处理客户端发来的消息delete ts;return nullptr;}private:Socket listensocket_;int port_;};如何处理客户端发来的消息
代码如下
static void Helper(int sockfd){// 接收消息并打印char buff[1024] {0};int n recv(sockfd, buff, sizeof(buff), 0);if (n 0){ //显示读出来的消息buff[n] 0;std::cout buff std::endl;//读出来的消息进行解析处理得到客户端想要访问什么资源Request req;req.Deserialize(buff);req.prase(buff);std::string path req.Select_path();path rootpath path;std::cout debug: std::endl;req.Debugprint();//根据http协议的方式将资源发送会给客户端std::string text Readrequest(path);std::string response;std::string sep \r\n;std::string line HTTP 1.1 1 OK;line sep;std::string head contentlenth:;std::string len std::to_string(text.size());head len;head sep;head sep;responseline;//状态行response head;//报头response text;//正文send(sockfd, response.c_str(), response.size(), 0);}}如何对客户端的发来的消息进行处理呢 创建一个Request类这个类里面含有如下成员 1 std::vectorstd::string arr 对内容进行分割 2 std::string text 将正文提取出来 ###################################### 3std::string method 接收方法 4std::string url 显示网址 5std::string http_version 显示http的版本 6 std::string path 表示路径 类方法如下 1Deseralize: 对从客户端接收到的消息里的请求行和报文进行打散打散到我的arr数组里面。并将正文提取出来。 2prase:打散后arr[0]便代表着请求行包含着三个信息method url http version。通过prase函数将这些消息获取出来。 3DebugPrint:显示相应的消息。用于debug。 4select_path:选择路径。 实现如下
class Request
{public:void Deserialize(std::string message){while (true)//循环读取{int pos message.find(sep);if(pos std::string ::npos){break;}std::string str message.substr(0, pos);if(str.empty())break;arr.push_back(str);message.erase(0, pos 1);//读一行消一行}text message;}void prase(std::string message)//将状态行的信息散开{std::stringstream s (message);s method url http_version;}void Debugprint(){for(auto e:arr){std::cout e std::endl ;}std::cout method: method std::endl;std::cout url: url std::endl;std::cout http_version: http_version std::endl;}std::string Select_path(){if(url /||url /html.index){path rootpath/;}else {path url;}return path;}private:std::vectorstd::string arr;std::string text;std::string method;std::string url;std::string http_version;std::string path;
};
返回消息 当我们处理了客户端发来的消息以后便可以得到客户端想要的资源在那个路径下。于是我们便可以将该路径对应的文件的内容读出来并返回给客户端显示。 读取文件的函数如下
std::string Readrequest(std::string path)//从文件内读取
{ std::ifstream in(path);if(!in.is_open())return 404;std::string line;std::string content;while(getline(in,line)){content line;}return content;//这个消息会拼接到正文也就是text
}五http报文细节
1Post与Get方法 在http的请求报头当中经常使用的请求方法有如下两个1Post 2Get
Post: Post方法在提取form表单的参数时通常会将参数放到正文部分来提交参数。 Get: Get方法在提取表单的参数时通常会将参数放到url的后面来提交参数。 相对于Get方法Post方法提交参数的方式比较隐蔽。但是两种提交参数的方式都是不安全的。因为通过抓包都可以将报文抓取然后获取报文的所有内容。如果想要安全就得加密加密协议就是https协议。
2Content_lenth
在相应报文的报头里面有一个Content_lenth的字段代表着正文的长度。报头与正文之间通过一个空行来分隔分割以后正文内容的大小由Content_lenth来指定进而进行读取。 3状态码
在http报文当中相应报文内的状态行中会有一串数字表示响应的状态。 比如200就代表正常状态码描述便是ok。
通常状态码对应的消息如下 1xx:信息型状态码。 2xx:代表请求ok如200。 3xx:代表重定向如301永久重定向 302临时重定向。 4xx:代表客户端请求错误如404。 5xx:代表服务端内部错误如501。 4Location
Location字段一般都是和重定向状态3xx搭配使用的。当我的客户端申请访问我们已经移动后的资源时服务端的Location字段便会指引客户端去访问移动后的资源。
5content_type
content_type标识的是正文的文件类型。这个content_type放在报头指明文件类型进而让客户端以正确的类型收取文件资源显示文件资源。
6cookie
cookie字段的作用是储存少量信息用于搭建临时会话。 在我们的浏览器上其实每次访问一个资源都是需要认证的。比如你在一个浏览器上第一次访问b站我们是不是要进行登录认证呢是的吧那我们看b站上的视频需不需要进行认证呢其实也是需要的但是cookie已经帮你吧认证信息存储起来了所以会帮你自动登录认证。 分类 cookie文件一般分为内存文件和硬盘文件。为了安全一般都会设置为内存文件会定时清理。比如在edge 浏览器上便是内存文件。 更完整细节 HTTP方法