HTTP协议(应用层协议)

1 协议

      我们都知道,协议是一种约定,我们规定好一种信息的格式,如果发送方按照这种请求格式发送信息,那么接收端就要按照这样的格式解析数据。这就是协议。

      应用层协议,一方面包含客户端和服务器端需要进行交互的信息,一方面包含如何组织(序列化)以及如何解析信息(反序列化)。

2 自定制协议

      我们可以通过一个简单的网络计算器的例子来自定制一个协议,体会其中的含义。

自定制协议一:

      首先,客户端规定发送的请求是一串字符串,形如“1+1”;

      其次,这串字符串由两个数字,中间一个加号构成,并且他们之间没有空格;

      最后,服务器在解析这串字符时,就可以按照它规定的方式解析解析出两个加数。这样,我们规定的字符串的格式就是一种协议。

自定制协议二:

      除了上面定义一个字符固定格式的字符串的方法,我们还可以选择定义一个结构体,这个结构提包含我们要传递的信息,比如定义一个结构体,保存着两个加数,在定义一个结构体,保证着两个加数相加之后的结果;

      在客户端将该结构体转化为字符串,再将该字符串(序列化)发送给服务器,服务器将该字符串再转化为结构体(反序列化),通过计算,将结果写入另一个结构体中,转化为字符串,在发送回客户端。

#include <stdio.h>                                                                                                                   
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include "proto.h"
/*
 * 这是一个服务器实现了简单的计算功能(加法)
 * 这里使用一个结构体来保存数据,解析数据时就按照结构体的定义来解析数据
 */

int main(int argc, char* argv[])
{
    if(argc != 3)
    {   
        printf("usage: ./server [IP] [port]\n");
        return 0;
    }
    //创建socket                                                                                                                     
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if(sock < 0)
    {
        perror("socket");
        return 1;
    }
    //绑定IP和port
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    int ret = bind(sock, (struct sockaddr*)&server, sizeof(server));
    if(ret < 0)
    {
        perror("bind");                                                                                                              
        return 2;
    }
    //进入循环,处理数据
    while(1)
    {
        Request req;
        //将数据读到req中(req是一个结构体,不需要进行强转)
        struct sockaddr_in client;
        socklen_t len;
        recvfrom(sock, &req, sizeof(req), 0, (struct sockaddr*)&client, &len);
        //进行计算
        ntohl(req.a);
        ntohl(req.b);
        Response res;
        res.sum = req.a + req.b;                                                                                                     
        htonl(res.sum);
        //将结果写回到客户端
        sendto(sock, &res, sizeof(res), 0, (struct sockaddr*)&client, len);
    }
}
#pragma once                                                                                                                         

typedef struct Request
{
    int a;
    int b;
}Request;

typedef struct Response
{
    int sum;
}Response;

上面是采用结构体实现的应用层协议,无论是方案一实现的协议,还是方案二实现的,只要能够正确的按照协议解析和传递数据,就是应用层协议。

3 HTTP协议

      HTTP协议是大佬定制的非常好用的应用层协议,我们可以直接使用该协议,而不是自己花时间精力定制一个不好用的协议,省去了许多事情。下面我们就来认识以下HTTP协议:

3.1 认识URL

      URL(统一资源定位符):平时我们说的网址,就是URL

URL的格式:

  • 协议方案名:使用http:或https:等协议方案名获取访问资源时要指定协议类型

  • 登录信息:用户名和密码作为从服务器端获取的必要登录信息,是可选项

  • 服务器地址:带访问服务器的地址(可以是方面的带解析的地址,也可以是IPv4地址)

  • 服务器端口号:指定服务器连接的网络端口号,同样是可选项(用户省略,则使用默认端口号)

  • 带层次的文件路径:指定服务器上的文件路径来定位特指的资源

  • 查询字符串:针对已指定文件路径内的资源,可以使用查询字符串传入任意参数

  • 片段标识符:通常可标记出以获取资源中的子资源

urlencode和urldecode:

      在url中,像/,?等都不能随意出现,因为它们都已经具有了特殊的含义。于是当我们需要这些字符可以原模原样的出现时,就需要进行转义,比如说C++中的‘+’:

urldecode就是urlencode的逆过程。

3.2 HTTP协议格式

3.2.1 HTTP请求

  • 首行:由方法+url+http版本构成
  • header:请求的属性,由冒号分隔的键值对构成
  • 空行:分隔了header和body部分
  • body:空行后面的内容都是body,允许body为空;当body存在时,header中要有一个Content-Length属性来标识body长度

3.2.2 HTTP响应

  • 首行:由版本号+状态码+状态码解释构成
  • header:响应的属性,有一个个以冒号为分隔的键值对组成
  • 空行:分隔header和body
  • body:body允许为空;当body存在,header中要有一个Content-Length属性来标识body长度

3.2.3 方法

以上是HTTP协议常见的方法。

3.2.4 状态码

3.2.5 header

HTTP协议常见header:

  • Content-Type:body部分的数据类型
  • Content-Length:body部分的长度
  • Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上‘
  • User-Agent:声明用户的操作系统和浏览器的版本信息
  • referer:当前页面是从哪个页面跳转过来的
  • location:搭配3xx状态码使用,告诉客户端接下来要去哪里访问
  • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能

3.3 实现一个简单的HTTP服务器

#include <stdio.h>
#include <string.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <unistd.h>

void* Entry(void* arg)
{
    int new_sock = (int)arg;
    char buf[1024*10] = {0};                                                                                                         
    //读取请求并响应(响应忽略)
    ssize_t s = read(new_sock, buf, sizeof(buf) - 1); 
    if(s > 0)
    {
        buf[s] = '\0';                                                                                                               
        printf("response:%s\n", buf);
    }
    const char* first_line = "HTTP/1.1 200 OK\n";
    const char* blank_line = "\n";
    const char* body = "<html>hello world!</html>";
    const char header[1024] = {0};
    //格式化构造字符串
    sprintf(header, "Content_Type: text/html;\nContent_Length: %lu\n", strlen(body));
    //注意写的顺序
    write(new_sock, first_line, strlen(first_line));
    write(new_sock, header, strlen(header));
    write(new_sock, blank_line, strlen(blank_line));
    write(new_sock, body, strlen(body));
    return NULL;
}                                                                                                                                    
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printf("usage: ./server [ip] [port]\n");
        return 1;
    }
    //创建socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0)
    {
        perror("socket");
        return 2;
    }
    //进行绑定                                                                                                                       
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);
    server.sin_port = htons(atoi(argv[2]));
    int ret = bind(sock, (struct sockaddr*)&server, sizeof(server));
    if(ret < 0)
    {
        perror("bind");
        return 2;
    }
    //listen
    if(listen(sock, 128) < 0)
    {
        perror("listen");
        return 3;                                                                                                                    
    }
    //进入循环
    while(1)
    {
        struct sockaddr_in client;
        socklen_t len;
        int new_sock = accept(sock, (struct sockaddr*)&client, &len);
        if(new_sock < 0)
        {
            close(sock);
            continue;
        }
        pthread_t td;
        pthread_create(&td, NULL, Entry, (void*)new_sock);
        pthread_detach(td);                                                                                                          
    }
}

我们使用我们的代码时,还可能出现GET /favicon.ico HTTP/1.1这样的请求,那么这是什么意思呢?

上图中用蓝色圈出来的小图标就是通过这个请求获得的。

另外,不同的状态码的含义不同,200代表成功发送响应,404代表找不到页面。在上面的代码中,如果我们将200改成404,那么页面就加载不出来了..

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值