Http协议

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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值