HTTP协议
HTTP协议简介
HTTP协议又叫超文本传输协议,应用层核心协议,基于C/S 请求-响应模型的一个无连接、无状态的协议。
在实现网络版本计算器代码中我们在应用层是自己自定义协议,把结构化数据序列化后如何进行封装,反序列化后如何进行解包,都是我们自己自定义的,但是HTTP协议直接是一种现成的应用层协议,它定义了客户端与服务器如何进行通信,如何进行传输数据。
认识"URL"
**URL:**统一资源定位符,其实就是我们平常说的"网址"。
一个完整的url如下:
**协议方案名http:// :**表示请求时使用的协议,通常情况下位http/https(后边会讲https)。
**登录信息usr:pass :**之前的网站登录时需要提供账号和密码,目前大多数url已经省略。
**服务器地址 www.example.jp:**表示访问的服务器地址,这里也叫域名。
这里说明一下:url采用域名的形式代替ip地址,其实最主要原因就是,方便了用户,用户果用点分16进制去访问目标服务器那用户很有可能并不知道网站是干什的,而向这种使用域名用户很明确的知道自己访问的网站。
**服务器端口号 80:**表示的时服务器绑定的端口号,因为这些协议本质也是在应用层,启动这些协议也就是启动进程,需要给进程绑定一个端口号。
因为有些成熟的协议(http/https/ssh/ftp)已经比较成熟,所以很多协议都以内置了不同的端口号,所以我们在使用某些成熟协议时实际上是不需要指明对应的端口号的,所以在url中也经常省略。
**带层次的文件路径:**表示要访问部署在服务器上特定文件夹下的一个资源,这里会存在一个默认的目录。
后边两个暂时先不介绍
urlencode & urldecode
如果在搜索关键字当中出现了像/?: 这样的字符,由于这些字符已经被URL当作特殊意义理解了,因此URL在呈现时会对这些特殊字符进行转义。
转义的规则如下:
将需要转码的字符转为十六进制,然后从右到左,取4位(不足4位直接处理),每两位做一位,前面加上%,编码成%XY格式。
如下所示:(++ 被编码位%2B,有%2B->++称为解码)

在线编码解码网站:https://tool.chinaz.com/tools/urlencode.aspx
HTTP协议格式
引入:我们为什么要学习HTTP协议格式。
对于TCP/IP四层协议结构,其中传输层、网路层、数据链路层,这三层功能是由操作系统或者驱动帮我们完成的,它们主要负责如何封装数据,怎么把数据安全的传输出去。而对于应用层是直接使用对方传来的数据。其实我们可以抽象的理解,在应用层视角进行通信的双方只需要把数据交给对方应用层即可。而对于应用层得到的数据我们该如何去使用,这就需要定制一些协议规定,让我们更明确的利用数据。这个协议就是HTTP协议。
我们又知道HTTP是基于请求和响应的,一搬来讲就是客户端请求,服务端响应,而在这个过程中作为服务端需要对客户端的请求做分析知道客户端请求什么,而作为客户端也需要对服务端的响应做分析,知道响应数据是什么。所以我们如果要想对这些请求响应分析,就必须了解HTTP的请求/响应格式。
HTTP请求格式
如上图所示:HTTP请求格式主要由上边结构组成。
- 请求行:[请求方法]+[请求url]+[HTTP版本]
- 请求报头:请求的一些属性(key:value)形式存储
- 空行:请求报头和请求正文分割,表示请求报头结束
- 请求正文:用户的请求数据,请求数据的长度记录在请求报文的一个属性位Content-Length中
HTTP响应格式

如上图所示:HTTP响应格式主要由上边结构组成。
- 状态行:[HTTP版本]+[状态码]+[状态码描述]
- 响应报头:HTTP响应报头对应属性,key:value存储。
- 空行:报头和正文进行分离。
- 响应正文:对应有效载荷,大小存在响应报头的一个属性中Content-Length。
协议"报头/正文"分离
对于协议的学习我们必须要知道对应报头和正文如何进行分离,因为接收方需要提取有效数据。
对于HTTP协议结构我们可以清楚的知道,该协议分离就是根据请求空行来进行分离的,空行之前是报头,空行之后是有效载荷。对于有效载荷的大小可以通过报头里边的Content-length属性进行提取。
查看HTTP请求
方法:编写Socket作为服务端接收客户端传来的消息,浏览器作为客户端进行访问服务器。服务器对传来的请求不做任何处理,直接打印。
编写代码
cpp
int main()
{
//创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0){
cerr << "socket error!" << endl;
return 1;
}
//绑定
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(8080);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0){
cerr << "bind error!" << endl;
return 2;
}
//监听
if (listen(listen_sock, 5) < 0){
cerr << "listen error!" << endl;
return 3;
}
//启动服务器
struct sockaddr peer;
memset(&peer, 0, sizeof(peer));
socklen_t len = sizeof(peer);
for (;;){
int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (sock < 0){
cerr << "accept error!" << endl;
continue;
}
if (fork() == 0){
close(listen_sock);
if (fork() > 0){
exit(0);
}
//孙子进程
char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0);
cout << "--------------------------http request begin--------------------------" << endl;
cout << buffer << endl;
cout << "---------------------------http request end---------------------------" << endl;
close(sock);
exit(0);
}
close(sock);
waitpid(-1, nullptr, 0);
}
return 0;
}
浏览器直接发起请求
对于浏览器发起请求只需要输入ip+port即可,因为在浏览器输入框中的url默认就是http协议

服务端显示
收到请求报文,请求行:GET /HTTP/1.1只有请求方法和/协议版本,这是因为我们请求的就是该网站下的默认目录,并没有请求其他目录。如果我们请求指令为:120.55.43.253:8080/a/b/c/html请求行就会出现请求目录了。
这里注意:我们发现请求行中拼接目录都是从"/"开始的,这里我们并不能简单理解为根目录,它可以是在你服务器上指定的任意一个目录。

自定义HTTP应答
对于客户端传来的请求,作为服务器端要对请求进行分析,然后给出对应的回应,这里我们简单模拟一下,自定义给客户端发送一个应答报文。
服务器代码增加
大致就是自定义一些应答属性,然后设置默认打开的文件PAGE(index.html)简单的一个回答。
cpp
//读取index.html文件
ifstream in(PAGE);
if (in.is_open()){
in.seekg(0, in.end);
int len = in.tellg();
in.seekg(0, in.beg);
char* file = new char[len];
in.read(file, len);
in.close();
//构建HTTP响应
string status_line = "http/1.1 200 OK\n"; //状态行
string response_header = "Content-Length: " + to_string(len) + "\n"; //响应报头
string blank = "\n"; //空行
string response_text = file; //响应正文
string response = status_line + response_header + blank + response_text; //响应报文
//响应HTTP请求
send(sock, response.c_str(), response.size(), 0);
delete[] file;
效果展示

HTTP版本问题
我们可以由HTTP请求和应答发现,在请求和应答格式中,都会带有各自的HTTP版本信息。客户端HTTP请求时会表明自己的客户端版本,服务器HTTP响应时也会带服务器的版本信息。
各自带有各自的HTTP版本给对方主要时为了解决兼容性问题,服务器和客户端使用的可能是不同的版本,服务器会不定时更新版本,为了让不同版本的客户端享受对应服务,在回应时需要带上自己的版本信息,便于版本协商。客户端表明自己的版本是为了让服务器根据不同版本的请求给出不同版本的回应。而不至于因双方使用不同的版本信息而造成无法正常通信。因此保证了良好的兼容性。
HTTP的方法
HTTP方法有很多这里只介绍几个常见的。

常见的HTTP方法
GET/POST方法
GET方法一般用于请求URl的指定资源信息,POST方法一般用于将数据上传给服务器,平常提交账号密码操作。但实际我们上传数据时也有可能使用GET方法,比如百度提交数据时实际使用的就是GET方法。
GET/POST传参示例:
**GET:**GET方法是通过url传参的。
GET /index.html HTTP/1.1
**POST:**POST方法是通过正文传参的。
POST /submit.cgi HTTP/1.1
从GET方法和POST方法的传参形式可以看出,POST方法能传递更多的参数,因为url的长度是有限制的,POST方法通过正文传参就可以携带更多的数据。
此外,使用POST方法传参更加私密,因为POST方法不会将你的参数回显到url当中,此时也就不会被别人轻易看到。不能说POST方法比GET方法更安全,因为POST方法虽然url没有回显但是通过抓包工具还是能够看到正文内容,所以都不安全。要做到安全只能通过加密来完成。
HTTP状态码

最常见的状态码,比如:200(OK),404(Not Found),403(Forbidden请求权限不够),302(Redirect重定向),504(Bad Gateway)
理解重定向状态码
重定向就是通过各种方法将各种网络请求重新定个方向转到其它网站,此时这个服务器相当于提供了一个跳转载体。
重定向又可分为临时重定向和永久重定向,其中状态码301表示的就是永久重定向,而状态码302和307表示的是临时重定向。
临时重定向和永久重定向本质是影响客户端的标签,决定客户端是否需要更新目标地址。如果某个网站是永久重定向,那么第一次访问该网站时由浏览器帮你进行重定向,但后续再访问该网站时就不需要浏览器再进行重定向了,此时你访问的直接就是重定向后的网站。而如果某个网站是临时重定向,那么每次访问该网站时如果需要进行重定向,都需要浏览器来帮我们完成重定向跳转到目标网站。
简单理解:如果果我们用浏览器访问我们的服务器(实现在服务器上设置一个跳转网页),当浏览器收到这个HTTP响应后,还会对这个HTTP响应进行分析,当浏览器识别到状态码是302后就会提取出Location后面的网址,然后继续自动对该网站继续发起请求,此时就完成了页面跳转这样的功能,这样就完成了重定向功能。
HTTP常见Head
常见Head
- Content-Type:数据类型(text/html等)。
- Content-Length:正文的长度。
- Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上。
- User-Agent:声明用户的操作系统和浏览器的版本信息。
- Referer:当前页面是哪个页面跳转过来的。
- Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问。
- Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能
Host字段
Host字段表明了客户端要访问的服务的IP和端口,比如当浏览器访问我们的服务器时,浏览器发来的HTTP请求当中的Host字段填的就是我们的IP和端口。但客户端不就是要访问服务器吗?为什么客户端还要告诉服务器它要访问的服务对应的IP和端口?
因为有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端。在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的IP和端口,此时Host提供的信息就有效了。
User-Agent
User-Agent代表的是客户端对应的操作系统和浏览器的版本信息。
比如当我们用电脑下载某些软件时,它会自动向我们展示与我们操作系统相匹配的版本,这实际就是因为我们在向目标网站发起请求的时候,User-Agent字段当中包含了我们的主机信息,此时该网站就会向你推送相匹配的软件版本。

Referer
Referer代表的是你当前是从哪一个页面跳转过来的。Referer记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性。
Keep-Alive(长连接)
HTTP/1.0是通过request&response的方式来进行请求和响应的,HTTP/1.0常见的工作方式就是客户端和服务器先建立链接,然后客户端发起请求给服务器,服务器再对该请求进行响应,然后立马端口连接。
但如果一个连接建立后客户端和服务器只进行一次交互,就将连接关闭,就太浪费资源了,因此现在主流的HTTP/1.1是支持长连接的。所谓的长连接就是建立连接后,客户端可以不断的向服务器一次写入多个HTTP请求,而服务器在上层依次读取这些请求就行了,此时一条连接就可以传送大量的请求和响应,这就是长连接。
如果HTTP请求或响应报头当中的Connect字段对应的值是Keep-Alive,就代表支持长连接。
(未完待续............)
