什么是http协议?
http协议,HyperText Transfer Protocol,超文本传输协议。是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。HTTP是一个基于TCP/IP通信协议来传递数据的客户端服务器(C/S)模型的协议。HTTP协议 是无连接、无状态的协议。
概念名称 | 释义 |
---|---|
应用层功能 | 负责应用程序之间的数据沟通 |
网络通信协议 | 网络数据传输中数据格式的约定 |
自定制协议 | 我们程序员自己设计的数据传输格式 |
应用层的知名协议 | HTTP协议(默认端口80) |
关于以上概念我的理解:http请求是通过浏览器上体现的,属于应用层协议,在进行数据通信时候,我们需要让数据按照一定的格式去传输,包括不能改变的http协议格式和我们自制定的数据传输格式。
今天这篇博客就主要总结http协议格式,了解http协议通信原理。
序列化和反序列化
发送数据是将一个结构体按照一个规则转换成字符串,接受到数据再按照相同的规则把字符串转化回结构体的过程叫“序列化”和“反序列化”。
经过序列化的数据如果被抓包,截获者不一定能知道数据的转换规则,就无法很快知道数据内容。
//演示序列化和反序列化
//以什么样的格式存储就以什么样的格式读取
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<fcntl.h>
typedef struct
{
int id;
int sex;
int age;
char name[32];
}stu;
int main()
{
stu s;
s.id=10;
s.age=23;
s.sex=1;
strcpy(s.name,"xiaoming");
int fd=open("./tmp.txt",O_CREAT|O_RDWR,0664);
if(fd<0){
printf("open error");
return -1;
}
write(fd,(void*)&s,sizeof(s));
lseek(fd,0,SEEK_SET);//使文件指针指向文件开头
char str[1024]={0};
stu n;
int ret=read(fd,(void*)&n,sizeof(s));
if(ret<0){
printf("read error");
return -2;
}
printf("ID:%d\nAge:%d\nSex:%d\nName:%s\n",n.id,n.age,n.sex,n.name);
close(fd);
}
http请求方法
http请求行总共有三项:请求访问、URI、http版本。
请求方法
- GET 请求指定的页面信息,并返回实体主体。
- HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头。
- POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中POST请求可能会导致新的资源的建立和/或已有资源的修改。
- PUT 从客户端向服务器传送的数据取代指定的文档的内容。
- DELETE 请求服务器删除指定的页面。
- CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
- OPTIONS 允许客户端查看服务器的性能。
- TRACE 回显服务器收到的请求,主要用于测试或诊断。
认识URL
URL全称是统一资源定位符,俗称的“网址”就是不完整的URL。它可以唯一确定服务器上某一资源的位置,其中包括资源路径等信息。下面是URI的一般格式
scheme://userInfo@host:port/path/filename?query_string#fragment
字段 | 中文含义 | 释义 |
---|---|---|
scheme | 协议方案 | 常见的有http、https、ftp、telnet等 |
userinfo | 用户认证信息 | 现在大多数URI已经没有该字段,原因很简单,存在信息泄露风险。 |
host | 服务器地址 | 就是服务器的域名。 |
port | 服务器端口 | 这里所说的端口是传输层所用的端口,http默认端口是80端口。 |
/path/filename | 资源路径 | 请求访问的资源在服务的路径。 |
query_string | 查询字符串 | 比如百度搜索C++,该字段内容就是C%2B%2B,表示C++。 |
fragment | 片段标识符 | 表示资源内的某一位置。 |
URI编码(urlencode)和解码(urldecode)
https://www.baidu.com/s?ie=utf-8&f=8&rsv_bp=0&rsv_idx=1&tn=baidu&wd=C%2B%2B&rsv_pq=fe363a2200027b94&rsv_t=bc57iAA0kdbhJ8QId17ywUFgFLFeh8mpEPAIK6XUDoSzwlw%2FFUD09mst%2FdQ&rqlang=cn&rsv_enter=1&rsv_sug3=4&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&inputT=2260&rsv_sug4=3461
这是百度搜索C++的URI,在第一行后面,我找到了问号,我们知道问号之后就是查询字符串,我们可以看到这些查询字符串都是Key=Value&
的格式。
我们可以看到URI中除了这些字段,还有@ ? # :
这些符号用来将上述这些字段分隔开来,那么如果我们的查询字符串(查询字符串可以是任意字符串,比如你要搜索“我为什么这么帅?”就含有?)中含有这些字符,就会出现二义性,因此查询字符串中出现这些特殊字符就需要转义,汉字也需要转移。
对这些特殊字符进行转义,对转义之后的表单进行排列的过程叫URL编码(urlencode),在对端对转义字符进行释义,对数据重新组织的过程称为URL解码(urldecode)。
编码规则:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。比如+的二进制是0010 1011,转换成十六进制是2B,C++转移之后就是C%2B%2B。
请求报头
请求报头字段都是以key:空格Value
的形式存在,一个键值对占一行。以下是我通过fiddler抓取的百度首页的http请求包
GET https://www.baidu.com/ HTTP/1.1
Host: www.baidu.com
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3679.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie:字段过长,后面会详细说
字段 | 释义 |
---|---|
Referer | 记录的当前页面是从哪一个页面跳转过来的 |
Connection | keep-alive持久连接 |
Content-Length | 正文的长度 |
Content-Type | 数据类型(text/html等) |
Host: | 请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从HTTP URL中提取出来的 |
User-Agent | 声明用户的操作系统和浏览器版本信息; |
location | 搭配3xx状态码使用, 重定向地址 |
Transfer_Encoding | chunked分块传输 |
chunked分块传输
当返回的数据比较大时,如果等待生成完数据再传输,这样效率比较低下。相比而言,服务器更希望边生成数据边传输。可以在响应头加上以下字段标识分块传输:
1)当选择分块传输时,响应头中可以不包含Content-Length。
2)服务器会先回复一个不带数据的报文(只有响应行和响应头和\r\n),然后开始传输若干个数据块。
3)当传输完若干个数据块后,需要再传输一个空的数据块;当客户端收到空的数据块时,则客户端知道数据接收完毕。
http响应-----状态码
状态码是用来显示http请求的响应状态的标识。HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型。
分类 | 分类描述 | 最常见的栗子 |
---|---|---|
1** | 信息,服务器收到请求,需要请求者继续执行操作 | 100 Continue 继续。客户端应继续其请求 |
2** | 成功,操作被成功接收并处理 | 200 - 请求成功 |
3** | 重定向,需要进一步的操作以完成请求 | 301 - 资源(网页等)被永久转移到其它URL |
4** | 客户端错误,请求包含语法错误或无法完成请求 | 404 - 请求的资源(网页等)不存在 |
5** | 服务器错误,服务器在处理请求的过程中发生了错误 | 500 - 内部服务器错误 |
Cookie和Session
为什么需要cookie和session?我们知道TCP通信需要建立连接,应用层数据需要等待1.5RTT才能发出,对于HTTP短连接来说,这个等待时间意味着响应缓慢。所以客户端第一次访问服务器时候,在建立连接过程中,服务器在第二次连接报文中给客户端颁发cookie,可以使得客户端在一段时间内访问服务器,无需等待就可以发送应用数据。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
Cookie和Session的对比
1)cookie数据存放在客户的浏览器上,session数据放在服务器上;
2)cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,考虑到安全应当使用session;
3)session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE;
4)单个cookie在客户端的限制是3K,就是说一个站点在客户端存放的COOKIE不能超过3K
5)expires过期或者max-age最大生存周期,都是用来标记Cookie字段的有效期的。
最简单的http服务器代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
int main(int argc,char*argv[])
{
if(argc!=3){
printf("USage:./httpser ip port\n");
return -1;
}
int sockfd=-1;
sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sockfd<0){
perror("socket error");
return -1;
}
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=htons(atoi(argv[2]));
addr.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t len=sizeof(addr);
int ret=-1;
ret=bind(sockfd,(struct sockaddr*)&addr,len);
if(ret<0){
perror("bind error");
close(sockfd);
return -1;
}
ret=listen(sockfd,5);
if(ret<0){
perror("listen error");
close(sockfd);
return -1;
}
while(1){
struct sockaddr_in cli;
int newfd=accept(sockfd,(struct sockaddr*)&cli,&len);
if(newfd<0){
perror("accept error");
continue;
}
char header[1024]={0};
ret=recv(newfd,header,1023,0);
if(ret<=0){
perror("recv error");
close(newfd);
continue;
}
printf("header;[%s]\n",header);
//响应
char* first ="HTTP/1.1 302 GO\r\n";
char head[1024]={0};
char *body="<html><body><h1>Hello,World!</h1></body></html>";
sprintf(head,"Content-Length: %d\r\n",strlen(body));
strcat(head,"Location: http://www.baidu.com/\r\n\r\n");
send(newfd,first,strlen(first),0);
send(newfd,head,strlen(head),0);
send(newfd,body,strlen(body),0);
close(newfd);
}
return 0;
}