目录
在学习HTTP协议时,我们需要掌握的内容有九个点。
1.HTTP概述
- HTTP协议,HyperText Transfer Protocol,超文本传输协议。
- HTTP协议是无连接,无状态,工作在应用层的协议。
在这里解释一下HTTP协议的两个特性,无连接,无状态。
- 无连接指的是HTTP协议本身在发送HTTP数据的时候并不需要与服务端建立连接,这是从HTTP协议本身所说的。但是HTTP协议作为应用层协议,它在传输层使用的是TCP协议,TCP在传输协议的过程中是要建立连接的。
- 无状态是指HTTP协议本身是对请求和响应之间的通信状态不进行保存。现在双方的状态是服务端在实现的机制,这个机制我们称之为会话机制。也就是说在HTTP这个级别,协议对于发送过的请求或响应都不做持久化处理。
2.HTTP协议的URL解释
- a. 协议方案名:总共分为两个http与https。两个都是超文本传输协议,只不过https在http上增加了ssl加密过程,ssl是一个非对称加密,会对http双方发送的数据进行加密。
- b.登录信息:早期时显示的我们的身份与认证,现在并没有,因为是不安全的行为。
- c.域名:他最终会被DNS协议解析成为IP地址。
- d.服务端的端口号:指定服务器连接的网络端口号。若用户省略则自动使用默认端口号。
- e.带层次的文件路径:这是向服务端后台请求的资源,而他之前的'/'指的是服务器当中的逻辑根目录,而不是Linux服务器的根目录。服务端可以指定一个路径为http服务端的根目录的其实路径。
- f.查询字符串:他是提交给服务端的数据。在这里要注意,1.他的格式为key=value,如果有多个,则格式为key=value&key1=value1。2.再提交上去的的内容有通俗意义的字符串要进行转码,将字符采用16进制进行表示。使用%这个字符+urlencode之后的字符来表示,其中%是告诉我们服务端后面的内容是经过urlencode的字符,需要服务端进行解码。
- g.片段标识符:现在已经不太常用,使用它通常可以标记出已经湖区资源的子资源(文档内的某个位置)。
拓展:
非对称加密分为了公钥与私钥,服务端持有私钥,http客户端持有公钥。在HTTP客户端使用公钥进行加密,加密完后传输到服务端,服务端用私钥进行解密就可以得到原生的内容,而在网路传输过程当中,如果有人进行网络数据的抓取由于他没有私钥,他拿到的数据是解析不了的,也就是说即使拿到了数据包也是一堆乱码,这就完成了对传输数据的保密过程。
3.HTTP协议的数据流
左边我们可以理解为是浏览器,右边可以理解为HTTP的服务端。浏览器在这里会先产生一个HTTP数据,HTTP数据就按照HTTP协议格式将它进行封装并且交给传输层;传输层得到后在这里打上TCP首部然后递交给网络层;网络成在这里打上IP协议的包头后递交给数据链路层;数据链路层打上以太网头部和以太网尾部传递给物理层;物理层将这个二进制处理成过电信号在网络当中进行传输,传输到对端,对端拿到数据之后再将其转化成二进制和格式,然后层层去掉原生数据的封装,最后拿到的数据还是按照HTTP协议组织得到的数据。在这里数据流还是牵扯到了两点,封装与分用。
4.HTTP协议的格式
HTTP协议规定,请求从客户端发出,最后服务端响应该请求并返回。话句话说肯定是先从客户端开始建立通信的,服务器端在没有接受到请求之前是不会发送相应。
4.1HTTP请求格式
HTTP的请求格式分为了四个部分:请求首行(方法URI协议版本),请求体(key:value的属性行),空行,请求正文 。
我们先给出框架,如下图:
4.2HTTP响应格式
HTTP响应格式有四部分,分别是响应首行(协议首行,状态码,状态码解释),响应体,空行和响应内容。
5.HTTP协议的版本
- HTTP/0.9:HTTP 于 1990 年问世。那时的 HTTP 并没有作为正式的标准被建⽴。 现在的 HTTP其实含有 HTTP1.0 之前版本的意思,因此被称为 HTTP/0.9。
- HTTP/1.0:HTTP 正式作为标准被公布是在1996 年的 5 ⽉,版本被命名为 HTTP/1.0,并记载于 RFC1945。虽说是初期标准,但该协议标准⾄今仍被⼴泛使⽤在服务器端。
- HTTP/1.1:这是目前主流的HTTP协议版本。
- HTTP/2.0:0 新 ⼀代HTTP/2.0 正在制订中,但要达到较⾼的使⽤覆盖率,仍需假以时⽇。
6.HTTP协议的请求方法
请求方法当中比较常见的有Get与Post。
6.1GET方法与POST方法
- Get:向服务端索要某些资源,也可以给服务端提供少量的数据在URL当中(少量的原因是URL的长度是有限制的,所以不能无限制给服务端提交数据在"查询字符串当中",且URL在不同浏览器当中是不同的)。
- Post:给服务器传输资源的方法,提交的数据实在请求正文当中传输给服务端。
- POST与GET对比:POST方法比GET方法更加私密。不能说POST方法比GET方法更加安全,因为无论GET方法是在URL当中提交数据,还是POST方法在请求正文当中提交数据,都是明文传输。
6.2其他的请求方法
- PUT:它用来传输文件。HTTP没有校验,一般情况下,后台的服务端是不支持PUT方法的。
- HEAD:获取响应头部,只获取响应首行和请求体,为了测试请求资源是否有效。
- DELETE:删除文件。HTTP没有校验,一般情况下,后台的服务端是不支持DELETE方法的。
- OPTIONS:询问服务端支持的方法。
常见的请求方法的说明,如下表:
方法 | 说明 | 支持HTTP协议版本 |
GET | 获取资源 | 1.0,1.1 |
POST | 传输实体主体 | 1.0,1.1 |
PUT | 传输文件 | 1.0,1.1 |
HEAD | 获得报文首部 | 1.0,1.1 |
OPTIONS | 询问支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 |
DELETE | 删除文件 | 1.0,1.1 |
CONNECT | 要求用隧道协议连接代理 | 1.1 |
LINK | 建立和资源之间的联系 | 1.0 |
UNLINE | 断开连接关系 | 1.0 |
7.HTTP协议的响应状态码
7.1状态码类别
类别 | 原因短语 | |
1XX | Informational(信息性状态码) | 接受的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误代码) | 服务器无法处理请求,访问资源不正确或者访问页面不存在 |
5XX | Sever Error(服务器错误状态码) | 服务器处理请求出错 |
7.2具体的响应状态码
- 200:OK,表示从客户端发来的请求在服务端被正常处理了。
- 302:Found,临时重定向行为,在服务器当中,资源的URI已经临时重定向d奥其他位置。
- 404:Not Found,服务器桑没有请求资源,访问的页面不存在。
- 502:Bad Getway,坏的网关
8.请求/相应的常见字段
- Content-Type:正文的类型(text/html:返回原生的html页面; application/json:json数据类型)
- Content-Length:正文的长度
- Host:保存服务端的IP和端口信息
- User-Agent:保存的是操作系统和浏览器版本的信息
- Location:保存重定向的网页地址
- Connection:keep-alive,保持长连接(HTTP底层使用到的TCP保持长连接)
- Cookie:他是服务端返回给浏览器的,由浏览器进行保存Cookie;在访问服务器其他界面的时候,由浏览器自动在请求体当中加上Cookie。(它的作用:服务端通过Cookie当中的value值,可以得到服务端生成的sessionid,通过会话id,可以在服务端查询出来是哪一个用户的session;浏览器通过请求当中的Cookie信息提交到服务端,服务端就可以通过Cookie保存的会话信息,进行会话校验)。
9.代码模拟实现HTTP协议与浏览器的交互
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sstream>
using namespace std;
int main(){
int listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(listen_sock < 0){
perror("socket");
return 0;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(28989);
//0.0.0.0 : 本地所有的网卡地址
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(listen_sock, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0){
perror("bind");
return 0;
}
ret = listen(listen_sock, 1);
if(ret < 0){
perror("listen");
return 0;
}
struct sockaddr_in cli_addr;
socklen_t cli_addrlen = sizeof(cli_addr);
int newsockfd = accept(listen_sock, (struct sockaddr*)&cli_addr, &cli_addrlen);
if(newsockfd < 0){
perror("accept");
return 0;
}
printf("accept new connect from client %s:%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
while(1){
//接收
char buf[1024] = {0};
ssize_t recv_size = recv(newsockfd, buf, sizeof(buf) - 1, 0);
if(recv_size < 0){
perror("recv");
continue;
}
else if(recv_size == 0){
printf("peer close connect\n");
close(newsockfd);
return 0;
}
printf("%s\n", buf);
memset(buf, '\0', sizeof(buf));
string body = "<html><h1>hello</h1></html>";
stringstream ss;
ss << "HTTP/1.1 302 Found\r\n";
//ss << "Content-Type: text/html\r\n";//相应首行
//ss << "Content-Length: " << body.size() << "\r\n";//响应体
ss << "Location: https://www.baidu.com/\r\n";
ss << "\r\n";
send(newsockfd, ss.str().c_str(), ss.str().size(), 0);
send(newsockfd, body.c_str(), body.size(), 0);
}
close(listen_sock);
return 0;
}
10.自定制协议
10.1TCP粘包的现象
我们在了解自定制协议之前先举个例子:
现在客户端使用send接口给服务端发送了一串数据给服务端12+12,此时客户端有发送一条数据24+24,两条数据依次被丢入网络当中,因为TCP是面向字节流的,此时两条数据到达服务端的传输层的TCP协议,而此时两条数据之间没有任何的分隔符。当应用层调用recv接口将数据拿到,但是服务端没有办法分析是12+12,24+24还是12+1224+24,此时服务端没有办法进行拆分,我们将这种现象称之为TCP粘包问题。
即就是TCP服务端没有办法针对TCP数据进行拆分,拆分成为不同的请求。因为TCP是面向字节流的,数据之间并没有明显的间隔,就导致服务端无法拆分数据。
10.2解决TCP粘包的现象
那么就在应用层自定制我们的协议,用来解决TCP粘包问题。
解决方案:
- 定义应用层自己的协议数据结构,来描述发送到数据
- 每一条数据都会友谊和分隔符(\r\n),来间隔前后两条数据。
10.3序列化和反序列化
序列化:将对象转换成二进制数据
反序列化:将二进制数据转化成对象
举个栗子:假设在这里有一个结构体
struct a{
string name;
string passwd;
};
我们知道这个结构体当中的两个string对象有可能在我们进程虚拟地址空间中并非连续存储,而如果不转化则会发送一些无效的数据,因此将当前的结构体中的对象转化成连续的二进制数据,这样就不会传输无效的数据,这种方式称之为序列化。而反序列化就与其相反。
josn也是一个好的使用方式。json是一种key/value的数据结构,可以支持嵌套定义,也可以支持多种基础类型(int,string,char)。同时也支持将josn对象序列化成二进制序列,也可以将二进制反序列化成josn对象。