Http协议存在于应用层,虽然说,应用层的协议都是由程序员们自己定义的。
但是,不妨有一些牛逼的人定义了一些现成的,可以供我们直接使用的,而且还非常好用的应用层协议。其中就有http协议------也叫作
超文本传输协议。
(Http 是基于TCP协议的,就是明文传输-------传输的就是字符串,打印后人眼可以识别出来)
在认识Http前,还是得先认识一个玩意儿---------------URL
URL其实就是我们日常口中所说的网址,网址就是URL
先举个例子,比如我们度娘(百度)的URL:
http://user:pass@www.baidu.com:80/dir/index.htm?uid=#ch1
这一串玩意就是百度的URL,也就是网址,大多数人可能除了www.baidu.com 其他的可能都不知道是干啥的吧,我们现在来一一解释一下:
上图:
上图中
协议方案名:就是Http,就是我们用的就是Http协议,也叫超文本传输协议
登录信息:有些时候,我们在需要输入自己的账户密码(比如CSDN这种网站),我们的密码就体现在了这个地方
服务器地址:就是我们知道的网站名(后边会说明为啥是www.baidu.com)
服务其端口号:就是我们Http协议所在的端口号默认是80,这里要多说一点的是,我们在前面说TCP/UDP协议的时候,我们需要绑定端口号,我们写的代码所绑定的端口号都是1024以上的端口号,因为1024以下的端口号已经被像Http这种知名协议默认了,所以我们使用不了
查询字符串:就是我们想通过百度搜索的东西
片段标识符:这个不太好理解,其实它就是一个书签一样的东,我们在使用百度百科的时候(比如:我们要了解C++)进入词条后,最上面有一些类似与书签的选项(比如历史背景什么的),我们点击它,不会出现一个新的页面,而是当前页面跳转至你点击的内容处。这个就是片段标识符(解释的不好,如果不理解,大家可以去百度百科,一看就知道了)
上面所展示的,就是URL,到这我们就会有疑问,那些奇奇怪怪的字符都是什么
其实这里要说到一个urlencode和urlecode
就是URL编码和URL解码。像 /?: 等这样的字符,已经被url当作特殊意义理解了,因此这些字符不能随意出现
比如,某个参数中需要带有这样的特殊字符,就必须先对特殊字符进行转义。
转义的规则是:将需要转码的字符转为16进制,然后从左到右,取4位(不足4位直接处理),每2位左一位,前面加上%,编码成%XY的格式。
举个例子:
我们在百度中搜索关键字C++,此时我们看上面地址栏的中间部分:有一串字符串为:C%2B%2B,其实这就是我们搜索的C++
‘+’号被转义成了‘%2B’。
到这,我们就要开始对主题进行研究了,Http协议到底是个什么鬼
Http是一种协议,既然是协议,就有它一定的格式:
在我们对某网站进行访问的时候,其实就是对该网站进行请求访问,该网站也会对你的请求作出回复
这就是Http请求与Http应答
请求:
首行: 请求方法【空格】url【空格】协议版本\r\n
头部: key: val\r\nkey: val\r\n(key冒号空格val换行key冒号空格val换行)
正文:
注意中间的空行(必须空一行,来表示头部与正文的间隔)
应答:
首行:协议版本【空格】状态码【空格】状态描述
头部: key: val\r\nkey: val\r\n(key冒号空格val换行key冒号空格val换行)
正文:
同样,头部与正文之间的空行不可省略
总结一下:
Http请求:
首行:[方法]+[url]+[版本]
头部:请求的属性,冒号分割的键值对;每组属性之间用\n分割,遇到空行表示头部部分结束
正文:空行后面的内容都是正文,正文允许为空字符串,如果正文存在,那么头部中会有一个Content—Length属性来来标识正文的长度
Http响应:
首行:[版本号]+[状态码]+[状态码解释]
头部:请求的属性,冒号分割符的键值对;每组属性之间使用\n分割,遇到空行表示头部部分结束
正文:空行后面的内容都是正文,正文允许为空字符串,如果正文存在,那么在头部中会有一个Content—Length来标志正文的长度,如果服务器返回了一个html页面,那么这个html页面就在正文中。
下来我们使用fiddler抓包工具来演示一下:
这是在访问百度的时候抓取的一个Http请求
我们来看:
首行:就是我们所说的格式(GET为方法)
头部:就是key—val这种键值对格式
但是我们发现这个请求好像没有正文
原因是这样的,我们要知道Get就是表明的方法,方法除了Get还有很多
其中GET方法,和POST方法是我们最常用到的,这二者的区别就是:Get没有正文,POST有正文
所以这就解释了我们抓取的包为什么没有正文
再来看看Http响应的包:
首行:版本号+状态码+状态码解释
我们来看,Http/1.1就是版本号,302就是状态码,后面Found就是对这个状态码的解释
那么都有哪些状态码呢?
状态码2开头的表示:正确响应并回复 3开头的表示:重定向(301 永久重定向 302 临时重定向) 状态码4开头的表示:客户端错误
状态码5开头的表示:服务端错误
头部:也是我们上面所说的键值对的格式
正文:由于请求时的方法是GET,所以没有正文
再仔细看,其实在请求的头部中有user-Agent:这个表示将操作系统以及硬件的一些版本告诉服务器
响应的头部中有set—cookie这一项,表示保存的有哪些信息,保存多久
除此之外,还有洗衣额Http中常见的头部信息:
Content-Type:数据类型(text/html等)
Content-Length:正文的长度
Host:客户端告知服务器,所请求的资源是在哪个主机记得哪个端口上
User-Agent:声明用户的操作系统和浏览器版本
referer:当前页面是从哪个页面跳转过来的
location:搭配3开头的状态码使用,告诉客户端接下来要去哪里访问
Cookie:用于在客户端存储少量信息,通常用于实现会话的功能
说到这里,我们就可以实现一个简单的Http服务器,我们知道,Http是基于TCP协议的,所以代码就和实现TCP协议的大同小异。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
char *header_1(int statu,char *context)
{
static char buff[1024] = {0};
if(statu > 600 || context == NULL){
return NULL;
}
sprintf(buff,"HTTP/1.1 %d %s\r\n",statu,context);
return buff;
}
char *header_2(char *body)
{
static char buff[1024] = {0};
sprintf(buff,"Content_Length: %ld\r\n",strlen(body));
strcat(buff,"\r\n");
return buff;
}
int main(int argc,char *argv[])
{
if(argc != 3){
printf("Usage: ./http_server ip port\n");
return -1;
}
int lst_fd = -1;
lst_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(lst_fd < 0){
perror("socket error!");
return -1;
}
struct sockaddr_in lst_addr;
lst_addr.sin_family = AF_INET;
lst_addr.sin_port = htons(atoi(argv[2]));
lst_addr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(lst_fd,(struct sockaddr*)&lst_addr,len);
if(ret < 0){
perror("bind error!");
return -1;
}
if(listen(lst_fd,5) < 0){
perror("listen error!");
return -1;
}
while(1){
int newfd = -1;
struct sockaddr_in cli_addr;
newfd = accept(lst_fd,(struct sockaddr*)&cli_addr,&len);
if(newfd < 0){
perror("accept error!");
continue;
}
char req_http[1024] = {0};
ret = recv(newfd,req_http,1023,0);
if(ret <= 0){
close(newfd);
continue;
}
printf("http req:[%s]\n",req_http);
char html[1024] = "<h1>hello world</h1>";
char *ptr = header_1(200,"OK");
send(newfd,ptr,strlen(ptr),0);
ptr = html;
send(newfd,ptr,strlen(ptr),0);
close(newfd);
}
close(lst_fd);
return 0;
}