HTTP协议是一个典型的应用层协议,是我们学习使用应用层协议的途径,接下来阿鲤就会详细的对HTTP协议做一个介绍
1:序列化和反序列化
2:网络数据传输格式
3:认识URL
4:urlencode编码&urldecode解码
5:HTTP协议格式
6:Cookie/Set-Cookie
7:一个简单的http模型搭建
一:序列化和反序列化
序列化:将数据对象按照指定的协议格式进行持久化存储/数据传输的过程
反序列化:将持久化存储/数据传输的二进制数据串按照指定协议解析出哥哥数据对象的过程
1:json序列化介绍:https://blog.csdn.net/pupilxiaoming/article/details/80988013
二:网络数据传输格式
网络中数据传输格式分为
字符组织格式:以特殊字符进行间隔,比较节省空间。
结构化数据:使用结构体将数据结构化,这样的传输方式,反序列化块,使得更加敏捷的获取到数据。
三:认识URL
URL 即Uniform Resource Locator,统一资源定位符,也就是我们常说的网址,接下来给大家解析以下HTTP的格式;
http://username:password@baike.baidu.com/item/HTML/97406?fr=aladdin#ch
http:协议方案名称
username:password:用户名和密码
baike.baidu.com:服务器地址信息(ip地址(域名解析)和port号(默认80))
/item/HTML/97406:资源路径(实体资源(加载网页文件)or某个功能(登陆))
fr=aladdin:查询字符串(提供给服务器的信息,格式为key=val&key=val的型式)
ch:片段标识符,指向html标签
四:urlencode编码&urldecode解码
在UDL中所提交代请求字符串往往会带有特殊字符,这样就会导致数据的二义性,就会导致服务器解析失败,所以这就需要对这些特殊字符进行处理。所以就有了特殊字符的数据转换-urlencode
urlencode编码:将特殊字符转换成16进制的字符串,并使用%加以标注:
eg:对+的urlendoding编码
+的十进制十43转换成十六进制十2B 所一位 %2b
urldecode解码:将编码后的转换成为原有字符
五:HTTP协议格式
在这里首先给大家介绍一个抓包工具:fiddler(大家可以自行百度下载)
使用这个工具作为网络传输中的代理,你就可以得到他们通信之间的每次的请求和响应的数据
HTTP协议格式如下
首行
头部
空行
正文
一:首行:
1:请求首行:请求首航中包含三条信息,\r\n作为结尾,格式如下:
请求方法 URL 协议版本\r\n
请求方法:
1:GET:主要用于获取实体资源,没有正文,所提交的数据放在URL的查询字符串中:但是URL的长度不能超过8k;
2:HEAD:相较于get,在响应信息中它只需要头部不需要正文
3:POST:主要向服务器提交表单数据,并且数据存放在正文中,安全性高于GET;
4:DELETE:请求删除指定资源
5:PUT:创建新的资源或替换请求负载目标资源表示
URL:见上文
协议版本:
HTTP/0.9:短链接,http在传输层使用的十tcp协议,tcp是面向链接的,发出请求,得到相应则通信结束关闭链接,之支持GET
HTTP/1.0:支持长链接,并且增加了请求方法及头部信息:POST/HEAD
HTTP/1.1:在支持长连接的基础上支持管线化传输;并且增加了更多的请求方法以及头部信息
HTTP/2:增加了更多的特性,增加了服务器主动向客户端推送信息。。。
2:相应首行:相应首行中包含三条信息,以\r\n结尾,格式如下;
协议版本 相应状态码 状态码描述\r\n:
协议版本:HTTP/1.1....
相应状态码:告诉客户端针对这次请求,是一个什么样的处理结果 分别有以下五种类型
1xx:描述信息
2xx:正确处理完毕;200
3xx:重定向;301/302/303;比如我们在登陆时,验证没问题,然后告诉你让你去请求另一个资源
4xx:客户端错误;400/404
5xx:服务端错误;500/502/504
eg:
200:表示服务器已经成功接受请求,并将返回客户端所请求的最终结果
206:成功状态响应代码指示请求已成功并且主体包含所请求的数据范围(断点续传)
301:客户端请求的网页已经永久移动到新的位置,当链接发生变化时,返回301代码告诉客户端链接的变化,客户端保存新的链接,并向新的链接发出请求,已返回请求结果
302:Found临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI;
303:See Other查看其它地址。与301类似。使用GET和POST请求查看
304:客户端已经执行了GET,但文件变化。
400:Bad Request客户端请求的语法错误,服务器无法理解
404:请求求失败,客户端请求的资源没有找到或者是不存在。
500:服务器遇到未知的错误,导致无法完成客户端当前的请求。
502:服务器由于临时的服务器过载或者是维护,无法解决当前的请求。
状态码描述:对于状态码的解释;比如200--正确处理,302-Found,404-Page Not Found
二:头部
头部由一个个key:val形式的键值对组成,每个键值对以\r\n结尾,键值对之间以\r\n作为间隔,描述本次的请求,是相应的关键信息eg:
Connection-本次请求是否是长连接 keep-alive(长连接)/close(短链接)
Connten-Length:用于描述正文长度,让对方接收数据时知道数据段长度
Cache-Control:max=age=0 (每次都更新);用于描述缓存的时长
Conntent-Type:用于描述正文的数据类型,让对方知道数据该如何处理
User-Agent:客户端描述信息,比如浏览器版本,等等。。
Accept:告诉对方我能接收怎样的数据;比如压缩格式,编码格式,语言等等
Referer:客户端告诉服务端我是从哪跳转过来的;比如使用百度进其他的网站。
Transfer-Encoding:传输方式,eg:chunked--分块传输--正文不会一次性全部传输,而是分多次传输,每次传输一块正文前先告诉对方这块正文有多长
Location:搭配3xx状态码使用,告诉对方资源重定向的新位置(让客户端请求新的位置)
Range:HTTP请求报头指示该服务器应返回的文档的一部分
Contrnt-Range:响应的 HTTP 报头指示其中一个完整的身体信息的部分消息所属(Range和Content-Range常用于断点续传)
三:正文
传输的信息
六:Cookie/Set-Cookie
因为早期的HTTP协议建立的链接都是短链接,即在一次请求和相应结束后就会断开链接,所以就会出现以下问题;
比如我们在网购,买东西时,我们需要将我们购买的东西请求发给服务器,让服务器帮我们处理,在处理完之后因为短连接的问题请求就会退出,这就使得我们想要继续买东西时得重新发起请求。而Cookie/set-Cookie就是用来解决这个问题的。
每次客户端登陆,服务器就会为其创建一个session(会话),会话中保存着当前会话信息,客户端认证信息等等,然后将这些信息存储到数据库中,在登陆成功响应时使用Set-Cookie告诉对方会话id是多少,客户端在收到这相应之后,将id保存在Cookie中;
在下次客户端发起请求的时候,就会自动将Cookie发送给服务器,服务器在收到这个Cookie之后去除信息session_id,通过这个id在数据库中找到相应的会话信息,并打开。
七:一个简单的http服务器模型搭建
HTTP服务器其实就是一个tcp服务器,其在传输层使用的是tcp协议,只不过在应用层的数据沟通是采用HTTP协议格式的。直接上代码了,会有详细的注释
HTTP.cpp:
/*********************************************************
1:搭建一个tcp服务器,等待客户创建套接字
2:客户端连接来,服务端创建新的套接字
3:通过这个新的套接字接收数据,传输数据
**********************************************************/
#include<stdlib.h>
#include"tcpsocket.hpp"//自己封装的tcp通信
#include<sstream>
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cout << "plase scanf ./http.cpp ip port\n";
return false;
}
TcpSocket lst_sock;
lst_sock.Socket();//创建tcp套接字
std::string ip = argv[1];
uint16_t port = atoi(argv[2]);
lst_sock.Bind(ip, port);//绑定
lst_sock.Listen();//开始监听
TcpSocket newsock;
while(1)
{
bool ret = lst_sock.Accept(newsock);//获取新连接
if(ret == false)
{
continue;
}
std::string buf;//接收缓冲区
ret = newsock.Recv(buf);//接收数据
if(ret == false)
{
newsock.Close();
continue;
}
std::cout << "http req:[" << buf << "]\n";//打印接受的数据
std::string body = "<html><body><h1>hello world</h1></body></html>";//正文
std::string blank = "\r\n";//空行
std::stringstream header;//头部信息
header << "Content-Length: "<< body.size() << "\r\n";//正文长度,将数据转换成字符串
header << "Content-Type: text/html\r\n";//显示的数据类型
std::string first = "HTTP/1.1 200 OK\r\n";//响应首行
newsock.Send(first);//发送首行信息
std::string tmp(header.str());
newsock.Send(tmp);//发送头部
newsock.Send(blank);//发送空行
newsock.Send(body);//发送正文
newsock.Close();//关闭,短链接
}
lst_sock.Close();//关闭套接字
return 0;
}
运行情况:
1:
2:
3:
tcpsocket.cpp
/******************************************************************************
* 描述:封装一个tcpsocket类,向外提供接口,能够实现客户端服务端编程流程
*流程:1:创建套接字
* 2:绑定地址信息
* 3:开始监听/发起连接请求
* 4:获取已完成连接
* 5:发送数据
* 6:接收数据
* 7:关闭套接字
******************************************************************************/
#include<unistd.h>
#include<iostream>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#define CHECK_RET(q) if(q == false){return -1;}
class TcpSocket
{
int m_socket;
public:
TcpSocket():
m_socket(-1)
{}
~TcpSocket()
{
Close();
}
bool Socket()//创建套接字
{
m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(m_socket < 0)
{
std::cerr << "socket creat error" << std::endl;
return false;
}
return true;
}
bool Bind(std::string &ip, uint16_t port)//套接字绑定
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
socklen_t len = sizeof(sockaddr_in);
int ret = bind(m_socket, (struct sockaddr*)&addr, len);
if(ret < 0)
{
std::cerr << "bind error \n";
return false;
}
return true;
}
bool Listen(int backlog = 5)//开始监听//backlog:最大并发连接数
{
int ret = listen(m_socket, backlog);
if(ret < 0)
{
std::cerr << "listen error\n";
return false;
}
return true;
}
bool Connect(std::string &ip, uint16_t port)//客户端发起连接请求
{
sockaddr_in addr;//服务端地址
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(&ip[0]);
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(m_socket, (sockaddr*)&addr, len);
if(ret < 0)
{
std::cerr << "connect erroe\n";
return false;
}
return true;
}
void SetFd(int fd)//获取新连接,因为m_socket私有;
{
m_socket = fd;
}
bool Accept(TcpSocket &newsock)//服务端获取已完成的新连接
{
sockaddr_in addr;//并不需要得到客户端的地址,但是也可以获得
socklen_t len = sizeof(sockaddr_in);
int fd = accept(m_socket, (sockaddr*)&addr, &len);
if(fd < 0)
{
std::cerr << "accept error\n";
return false;
}
newsock.SetFd(fd);//将新连接的套接字操作句柄给newsock
return true;
}
bool Send(std::string &buf)//发送,若发送到过大,先发送一部分;所以要判断是否发送完
{
int ret = send(m_socket, &buf[0], buf.size(), 0);
if(ret < 0)
{
std::cerr << "send error\n";
return false;
}
return true;
}
bool Recv(std::string &buf)//接收数据,没有数据会阻塞
{
char tmp[4096] = {0};
int ret = recv(m_socket, tmp, 4096, 0);
if(ret < 0)
{
std::cerr << "recv error\n";
}
else if(ret == 0)//若返回值等于0则表示连接已断开
{
std::cerr << "peer shutdown\n";
return false;
}
buf.assign(tmp,ret);//拷贝
return true;
}
bool Close()
{
if(m_socket >= 0)//0:标准输入(在标准输入被关闭时,会等于0;(最小位使用))
{
close(m_socket);
m_socket = -1;
return true;
}
return false;
}
};