HTTP协议

  1、在万维网中,资源(网页,图片,音频等)都是使用“统一资源定位符”(URL)来进行标识的。这些资源是通过超文本传输协议(HTTP协议)传送到客户端的。而用户只需要点击链接来获取该资源。   

 2、 HTTP协议是应用层的一种协议(超文本传输协议),表示了web客户端与服务器端双方之间传送数据的格式和解析数据的格式。HTTP请求定义了客户端如何向服务器端请求万维网文档,以及服务器端如何将万维网文档传输给客户端。

3、HTTP协议是面向事务的应用层协议,它规定了在浏览器和服务器之间请求和响应的格式和规则,是基于客户端/服务端(C/S)的架构模型,通过可靠的连接来交换信息,是一个无状态的请求/响应协议。

4、

约定的内容包括:

  • 客户端与服务器之间有哪些信息需要进行交互
  • 如何将交互的信息组织成为字符串(进行序列化的过程)
  • 如何将字符串信息解析成结构化的信息

1、特点:

  • HTTP协议是无连接的:是指使用HTTP协议进行数据和信息的传输前,不需要建立HTTP连接,而是建立TCP连接,使用TCP连接来保证可靠性的传输。无连接指的是不建立HTTP连接。

       针对HTTP无连接,人们设计出了非持久连接持久连接

       (1) 当客户端/服务器的交互运行于TCP协议之上时,应用程序的每个 请求/响应对是由一个单独的TCP连接时,即客户端每请求 一次服务器端,服务器端对该请求进行响应完,就立即断开连接。该连接是非持久连接

      (2)  而当应用程序的每个请求/响应对是由相同的TCP连接时,即客户端和服务器端完成一次响应和请求后,仍然保持连接,等待后续的客户端对服务器的响应,所有的请求和响应都是基于该条连接进行的,该连接是持久连接。

持久连接(HTTP/1.1版本)支持。 

  • HTTP协议是无状态的,无状态是指协议对于事物处理没有记忆能力,没有办法区分每次请求的不同之处。可能会导致每次传送的数据量增大。但这种情况也保证了服务器端支持高并发的响应请求。
  • 支持客户/服务器模式
  • 简单/快速
  • 灵活

2、HTTP报文的结构

HTTP是面向文本的,报文中的每个字段都是ASCII码串,其报文又分为请求报文和响应报文。

  • 请求报文:由客户端发往服务器端,发送的请求报文
  • 响应报文:由服务器端发往客户端,发送的响应报文
  • 两种报文的区别:开始行是不同的。请求报文的开始行叫做请求行,响应报文的开始行叫做状态行

 

   HTTP请求报文:方法、资源的URL、HTTP版本(常用的方法包括:POST方法、GET方法、HEAD方法、CONNECT方法)

  •  GET方法:请求读取URL标识的信息
  • HEAD方法:请求读取URL标志信息的首部
  • POST方法:给服务器添加信息
  • CONNECT方法:用于代理服务器

下面是请求报文的实例:

POST http://128.304.0.25/login HTTP/1.1
Host: 128.304.0.25
Connection: keep-alive
Content-Length: 27
Origin: http://123.207.58.25
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Referer: http://123.207.58.25/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=b52hc1l3dddvm61i1cvkkmif93

name=775&passwd=18700798311

(1)首行为:方法+url+版本

  • POST方法(请求资源),GET方法(获取资源)、DELETE方法(请求服务器删除指定的资源)
  • url:网址是128.304.0.25
  • 版本:HTTP/1.1

(2)Header(请求的属性)

  是以冒号加空格分隔的键值对,而且每组属性各占一行,结束的标志为一个空行

  • Host属性表示主机名
  • Connection属性表示连接的属性,keep-alive(持久连接)
  • Content-Length属性:Body部分的长度
  • Content-Type:指定body部分的格式
  • Refer:当前页面是从哪个页面跳转过来的
  • Cookie:用于保存用户提交的身份信息等。当我们登录网站时,第一次登陆之后,本地将我们的信息提交给服务器,本地的cookie就会将服务器返回的信息存储下来,下次我们再请求连接时,就不需要再重复上述的操作了。cookie属性的大小上限为4k,当我们登录淘宝网时,cookie会保存我们的身份信息。

(3)Body部分

Header后面紧接着Body部分,多数情况下为HTML格式的信息。

3、HTTP响应报文

HTTP/1.1 200 OK
Server: nginx
Date: Sat, 30 Jun 2018 10:45:42 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Powered-By: PHP/5.4.45
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: PHPSESSID=b52hc1l3dddvm61i1cvkkmif93; expires=Sat, 30-Jun-2018 16:45:42 GMT; path=/
Set-Cookie: loginStatus=yes; expires=Sat, 30-Jun-2018 16:45:42 GMT; path=/
Content-Length: 27

(1)首行:版本号+状态码+状态码解释

状态码的分类:https://mp.csdn.net/postedit/87565202(见之前写的一篇博客)

(2)Header部分

  • Set-Cookie:从请求报文中所获得的一些属性来设置该属性,再给客户端发送响应时,就会将其发送回去

(3)Body部分:

       报文的内容,通常以HTML的形式展示

下面该程序,模拟HTTP服务器

基于TCP服务器端的整个流程:socket->bind->listen->accept->send/recv->closesocket

僵尸进程:子进程执行exit()后,代码执行部分其实已经结束执行了,系统的资源也已归还给系统了,但是其进程的进程控制块(PCB)仍驻留在内存中,因为PCB是进程存在的唯一标志,里面有PID等信息,并没有消亡,因此这样的进程称为僵尸进程

孤儿进程:父进程结束(异常结束),未能及时收回子进程,子进程仍在运行,这样的进程称为孤儿进程。在Linx系统中,孤儿进程一般会被init进程所收养,成为init进程的子进程,由init进程来做善后的处理,所以她不会像僵尸进程一样无人问津,但是大量存在会有危害。init进程是pid=1的进程,因此又叫做1号进程

#include<stdio.h>                                                                                                                     
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
//处理连接的请求响应,addr是存放IPv4型的结构体

int main(int argc,char * argv[])
{
  //判断命令行参数是否合法
  if(argc != 3)
  {
    printf("Usage: ./server [IP] [Port]\n");
    return 1;
  }

   
  struct sockaddr_in addr;//定义结构体addr存放IPv4类型的套接字的相关信息
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = inet_addr(argv[1]);//IP地址
  addr.sin_port = htons(atoi(argv[2]));//端口号

  socklen_t  addr_len = sizeof(addr);
  
  int fd = socket(AF_INET,SOCK_STREAM,0);//使用socket函数打开网卡文件,创建套接字

  //绑定IP和端口号
  int bind_ret = bind(fd,(struct sockaddr *)&addr,addr_len);
  if(bind_ret < 0)
  {
    perror("bind");
    return 2;
  }

  //使服务器处于监听状态,接收客户端的请求
  if(listen(fd,5) < 0 )
  {
    perror("listen");
    return 3;
  }

  //服务器开始进行循环,不断地接收来自客户端的请求
  while(1)
  {
    //服务器接收连接请求,使用accept函数接受连接,该函数返回客户端网卡文件的文件描述符
    int new_socket = accept(fd ,(struct sockaddr *)&addr,&addr_len);
    if(new_socket < 0)
    {
        perror("accept");
        continue;
    }

    pid_t pid = fork();//使用fork()函数创建进程
    if(pid < 0)//创建进程失败
    {
        perror("fork");
        return 4;
    }
    if(pid == 0)//fork成功,返回值为0的为子进程
    {
         //子进程不用监听socket ,所以可以直接将网卡文件关闭
         close(fd);
         pid_t id = fork();//子进程中使用fork函数创建进程
         if(id < 0)
        {
         perror("fork");
         return 5;
        }
         if(id > 0)
        {//二代子进程,即新fork()后的父进程,其下还有一个子进程
         //为一个已经建立好连接的用户提供服务
         
           char read_buf[1024 * 10] = {0};//开辟10k的缓冲区,存放从客户端读取到的请求
           char buf[1024 * 10] = {0};//开辟10k缓冲区,进行初始化
           while(1)
           {
            //读取客户端的请求,使用read函数(),new_socket是客户端网卡文件的文件描述符,读取失败时返回-1
           if((read(new_socket ,read_buf ,sizeof(read_buf)-1))<0)
           {
                perror("read");
            }
           else
          {
             printf("%s",read_buf);//读取成功时,输出读取到的内容(客户端发送的请求内容)
             printf("********************************\n");
           }
          //现在不关心客户端发送的请求的内容,因此只要按照http响应报文格式回复过去就可以
          const char * body = "<html>\n<h1>hello world</h1>\n</html>";//Body部分内容,显示到网页上的信息
          const char * first_line = "HTTP/1.1 200 OK";
          const char * header_content_type = "Content-Type: text.html;charset=UTF-8";
          //构造http响应报文,sprintf函数是打印到buf中,而printf函数则是打印到屏幕上,属性内容之间使用\n隔开
          sprintf(buf,"%s\n%s\nContent-Length: %lu\n\n%s",first_line,header_content_type,strlen(body),body);
          write(new_socket,buf,strlen(buf)); //将构造好的响应报文写到客户端的网卡文件中
          }

         close(new_socket);
         exit(0);    
        }
        else
        { //一代子进程,新fork()后的子进程
          //一代子进程直接退出,使二代子进程成为孤儿进程
          //被一号进程收养,避免成为僵尸进程,造成内存泄漏
          //并且直接退出,此处可以不用手动关闭文件
          exit(0);
        }
    }
 else
    {//父进程,pid>0,返回的是子进程的进程号
      //在父进程的执行逻辑中,不用new_socket 必须要将其关闭
      //因为父进程是一直在执行中,为了防止文件描述符泄漏
        close(new_socket);
        waitpid(pid,NULL,0);//等待子进程的终止
     }
  }
  close(fd);
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值