http协议概念
HTTP(HyperText Transfer Protocol,超⽂本传输协 )的协议。
- http是无连接的,无状态的,简单快速的工作在应用层的协议。
- 无连接:
http本身没有维护连接信息,它会把数据交给传输层的TCP,当TCP建立好连接后,http直接发送数据,不关心TCP的细节
- 无状态:
http本身无状态,不会记录用户信息
- 简单快速:http采用的是短连接进行文本传输的
URL
虽然我们说, 应用层协议是我们程序猿自己定的.。但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输协议)就是其中之一。
下面我们看个常见的url:
- http ://是URL的协议部分,这代表网页使用的是HTTP协议
- 服务器地址:也就是域名部分,还可以用IP地址作为域名使用
- 服务器端口号:http使用的是80,https使用的是443端口号,它可以省略。因为有的端口号已经被内定了。就像生活中的119,110这些电话,我们一看到就知道是火警和报警电话。有的协议和端口号已经是强绑定的,我们绑定不了。
- 域名/:这个斜杠是url资源所处的路径,在Linux中一切接文件,这个路径存放着图片啊、视频啊...
- 查询字符串:是这次http请求的参数。是客户端与服务器之间传递数据的一种方式,多个参数之间用&连接
- 片段标识符:表示要跳转到本页面的某一部分
urlencode和urldecode
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现. 比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。例如:输入C+++
被转移成了%2B
了,urldecode就是urlencode的逆过程。
urlencode工具( https://tool.chinaz.com/Tools/urlencode.aspx)
http协议格式
http请求
注意:
- 所以当服务器读客户端的请求时
是一行一行读的
,当读到空行
时,再往下读就是正文部分
了。 - 当服务器收到多个请求时怎么保证不会读到其他请求的正文呢?在请求报头中有个
content-Length
,它会标识每个正文有多少字节,保证不会读到多余的正文。 - 请求报头由多个key-value组成
抓包
下面来用fildder抓包工具来抓下包试试:比如我抓个nba的
看圈起来的raw
- 由于我是粘贴在画图板的,fildder中是一行一行的,这就是hppt请求的结构
- 请求报头中有key:value组成的一些属性
- 空行下面是正文,请求报头中有connet-Lenght是320.说明正文有320个字节,正文是fildder编码的问题不要在意
http响应
我们再来看看响应请求
再来看看对应的刚刚抓的包
- 响应行有
http的版本
,状态码
,状态码描述
- 响应报头: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
- 如果有正文则content-Length描述正文有多少字节
http方法
| 方法 | 说明 | 支持的 http协议版本 |
|--| -- |--|
| GET | 获取资源 | 1.0 、1.1 |
| POST | 传输实体主体 | 1.0、1.1 |
| PUT | 传输文件 | 1.0、1.1 |
| HEAD | 获得报文首部 | 1.0、1.1 |
| DELETE | 删除文件 | 1.0、1.1 |
| OPTIONS | 询问支持的方法 | 1.1 |
| TRCAE | 追踪路径 | 1.1 |
| CONNECT | 要求用隧道协议连接代理 | 1.1 |
| LINK | 建立和资源之间的联系 | 1.0 |
| UNLINE | 断开的连接关系 | 1.0 |
我们常用的2中方法是GET和POST
GET 通过url来传参,长度限制
POST是通过正文来传参
http的状态码
类别 | 原因短语 | |
---|---|---|
1XX | Informantion(信息性状态码) | 接收的请求正在处理 |
2XX | Sucess (成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作已完成请求 |
4XX | Clinet Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器状态码) | 服务器处理请求出错 |
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
- 程序员最想看到的:200-OK。
- 程序员不想看到的:500-Internal-Server-Error。
- 用户不想看到的:401-Unauthorized、403-Forbidden、408-Request-Time-out
http常见的Header
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
http是无状态的,不会记录用户的信息,则会导致用户需要一直登录,用户的体验很差。我们平时只要登录一次下次就用登录是怎么回事呢?是因为Cookie是有状态的。
客户端自一次发送请求将自己的用户名和密码发送给服务器,服务器会创建set_cookie文件,再发送回客户端的浏览器,cookie文件则保存在浏览器中。cookie的本质就是浏览器中的一个文件
如下面的例子:
有一天电脑中病毒了,cookie文件被盗了,我们和服务器的通信细节都会被别人知道了,那该怎么办呢?就要用session来处理了。
session是将敏感的信息放在服务器中,返回给客户端一个cookie sid,下次客户端返送请求,服务器通过sid找到之前的状态。注意
session是相对安全一些。
- cookie数据存放在客户的浏览器上,session数据放在服务器上。
- cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,如果主要考虑到安全应当使用session。
- session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,如果主要考虑到减轻服务器性能方面,应当使用cookie。将登陆信息等重要信息存放为SESSION;其他信息如果需要保留,可以放在cookie中.
https
https比http多了加密和解密,http是直接将数据交给下一层,https是先交给SSL/TLS进行加密。
对称加密
对称加密用一把秘钥进行加密和解密。
这样的通信不安全,秘钥丢了就吉吉了。
非对称加密
非对称加密通常
用公钥加密
,私钥解密
。
具体流程:
- 首先客户端和服务器协商用什么加密算法,服务器说用非对称,把公钥发给客户端
- 客户端得到公钥,来加密对称加密的私钥,服务器通过秘钥解密得到秘钥
- 双方可以使用对称加密进行通信
为什么不直接用非对称加密通信?
非对称加密效率低,对称加密效率高。
实现一个简单的http服务器
实现一个简单的http服务器,在网页上输出hello
httpServer.hpp
1 #include<iostream>
2 #include<sys/socket.h>
3 #include<sys/types.h>
4 #include<arpa/inet.h>
5 #include<netinet/in.h>
6 #include<unistd.h>
7 #include<signal.h>
8 #define BACKLOG 5
9 using namespace std;
10
11 class httpServer
12 {
13
14 private:
15 int port;
16 int lsock;
17 public:
18 httpServer(int _port=8080,int _lsock= -1)
19 :port(_port)
20 ,lsock(_lsock)
21 {}
22 void initServer()
23 {
24 signal(SIGCHLD,SIG_IGN);
25 lsock=socket(AF_INET,SOCK_STREAM,0);
26 if(lsock < 0)
27 {
28 cerr<<"socket error"<<endl;
29 exit(2);
30 }
31 struct sockaddr_in local;
32 local.sin_family = AF_INET;
33 local.sin_port = htons(port);
34 local.sin_addr.s_addr = INADDR_ANY;
35 if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)
36 {
37 cerr<<"bind error"<<endl;
38 exit(3);
39 }
40 if(listen(lsock,BACKLOG) < 0)
41 {
42 cerr<<"listen error"<<endl;
43 exit(4);
44 }
45
46 }
47 void EchoHttp(int sock)
48 {
49 char response[2048];
50 ssize_t s = recv(sock,response,sizeof(response),0);
51 if(s > 0)
52 {
53 response[s] = 0;
54 string response = "HTTP/1.0 200 OK\r\n";
55 response += "Content-type: text/html\r\n";
56
57 response += "\r\n";
58
59 response += "<html><h1>hello </h1></html>";
60 send(sock,response.c_str(),response.size(),0);
61
62 }
63 close(sock);
64 }
65 void start()
66 {
67 struct sockaddr_in peer;
68 for(;;){
69 socklen_t len = sizeof(peer);
70 int sock = accept(lsock,(struct sockaddr*)&peer,&len);
37 cerr<<"bind error"<<endl;
38 exit(3);
39 }
40 if(listen(lsock,BACKLOG) < 0)
41 {
42 cerr<<"listen error"<<endl;
43 exit(4);
44 }
45
46 }
47 void EchoHttp(int sock)
48 {
49 char response[2048];
50 ssize_t s = recv(sock,response,sizeof(response),0);
51 if(s > 0)
52 {
53 response[s] = 0;
54 string response = "HTTP/1.0 200 OK\r\n";
55 response += "Content-type: text/html\r\n";
56
57 response += "\r\n";
58
59 response += "<html><h1>hello </h1></html>";
60 send(sock,response.c_str(),response.size(),0);
61
62 }
63 close(sock);
64 }
65 void start()
66 {
67 struct sockaddr_in peer;
68 for(;;){
69 socklen_t len = sizeof(peer);
70 int sock = accept(lsock,(struct sockaddr*)&peer,&len);
71 if(sock < 0)
72 {
73 cerr<<"accept error"<<endl;
74 continue;
75 }
76 if(fork() == 0)
77 {
78 close(lsock);
79 EchoHttp(sock);
80 exit(0);
81 }
82 close(sock);
83 }
84 }
85 ~httpServer()
86 {
87 if(lsock != -1)
88 {
89 close(lsock);
90 }
91 }
92 };
httpServer.cc
1 #include "httpServer.hpp"
2
3 static void Usage(string proc)
4 {
5 cout << "Usage:\n\t";
6 cout << proc << " port" << endl;
7
8 }
9
10 int main(int argc ,char *argv[])
11 {
12 if(argc != 2){
13 Usage(argv[0]);
14 exit(1);
15
16 }
17
18 httpServer *hp = new httpServer(atoi(argv[1]));
19 hp->initServer();
20 hp->start();
21
22 return 0;
23
24 }
在浏览器输入ip地址:端口号
一个简单的http服务器就搞定了。