【Linux】网络基础知识(二)—— 应用层 —— HTTP协议详解

本文详细介绍了应用层的HTTP协议,包括URL的组成部分、urlencode和urldecode的作用、HTTP请求和响应的格式,以及常见的HTTP状态码。还讨论了GET和POST方法的区别,并提供了实现简单HTTP服务器的思路。
摘要由CSDN通过智能技术生成

应用层

我们程序员写的一个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层。

1. 再谈 “协议”

协议是一种 “约定”,socket api的接口,在读写数据时,都是按 “字符串” 的方式来发送接收的。

1.1 自定制协议

  1. 工作在应用层,自己定制的协议;
  2. 双方严格遵守协议内用;
  3. 增加定长的报头;
  4. 对一条数据增加分隔符,通常是/r/n
  5. 序列化与反序列化。

2. HTTP协议

只要保证, 一端发送时构造的数据,在另一端能够正确的进行解析,这种约定,就是应用层协议。HTTP(超文本传输协议)就是其中之一。

2.1 认识URL

平时我们俗称的 “网址” 其实就是说的 URL。
在这里插入图片描述
URL组成

  • 协议方案名:例如http或https;
  • 登录信息(认证);
  • 服务器地址:域名或ip;
  • 服务器端口号:http中不显示就默认80端口;
  • 带层次的文件路径:服务器提供的服务;
  • 查询字符串:?为分界点,以键值对形式表现。键1=值1&键2=值2…
  • 片段标识符:前端用来做页面定位使用的。

2.2 urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。"+" 被转义成了 “%2B”。
在这里插入图片描述

urldecode就是urlencode的逆过程

2.3 HTTP协议格式

2.3.1 HTTP请求

GET /xxgk/about1.htm HTTP/1.1
Host: www.xiyou.edu.cn
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://www.xiyou.edu.cn/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=EE5774BE918212AACACF850083AFAF54
If-None-Match: "6edd-5b6ef7e4b1bdc-gzip"
If-Modified-Since: Mon, 21 Dec 2020 01:36:55 GMT
  • 首行: [方法] + [url] + [版本];
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束;
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度。

2.3.2 HTTP响应

HTTP/1.1 200 OK
Server: VWebServer/6.0.0
Date: Tue, 22 Dec 2020 02:25:01 GMT
Expires: Tue, 22 Dec 2020 02:33:36 GMT
Cache-Control: max-age=600
Content-Type: text/html
Content-Length: 9857
X-Frame-Options: SAMEORIGIN
Last-Modified: Mon, 21 Dec 2020 01:36:55 GMT
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Encoding: gzip
ETag: "6edd-5b6ef7e4b1bdc-gzip"
Content-Language: zh-CN
  • 首行: [版本号] + [状态码] + [状态码解释];
  • Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束;
  • Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中。

2.4 HTTP常见Header

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

2.5 HTTP的方法

方法说明支持的HTTP协议版本
GET获取资源1.0、1.1
POST传输实体主体1.0、1.1
PUT传输文件1.0、1.1
HEAD获取报文首部1.0、1.1
DELETE删除文件1.0、1.1
OPTIONS询问支持的方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0
UNLINK断开连接关系1.0

最常用的就是GET方法和POST方法

GET方法和POST方法的区别:

  • 数据长度限制:GET方法发送数据是向URL添加数据,但URL的长度是受限制的(URL最大长度是2048个字符)。POST方法没有限制。
  • 安全性:GET方法发送的数据是URL的一部分。POST方法传输数据的参数不会被保存在浏览器历史或web服务器日志中,所以POST更安全。
  • 可见性:GET方法数据都在URL中,URL对所有人都可见。POST方法数据不会显示在URL中。

2.6 HTTP的状态码

在这里插入图片描述
最常见的状态码:

  • 200:服务器正确处理完请求,并返回响应。
  • 301/302/307:重定向(服务端响应3XX重定向状态码,location报头,然后浏览器会自行跳转到location)。
  • 400:客户端错误(客户端请求数据错误–数据格式/数据类型错误)。
  • 404:找不到资源(服务器没有提供该URL地址请求的服务)。
  • 405:Method Not Allow 不支持的客户端请求方法(服务器的这个服务不支持请求报头的请求方法)。
  • 500:服务器错误(一般是服务器后端错误–抛出异常)。
  • 502:Bad Gateway 网关错误。

3. 实现简单的HTTP服务器

实现一个最简单的HTTP服务器, 只在网页上输出 “Hello World”。
只要我们按照HTTP协议的要求构造数据, 就很容易能做到。

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

int main()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;                       
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");

    int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -1;                        
    }

    ret = listen(sockfd, 5);
    if(ret < 0)
    {
        perror("listen");
        return -1;                        
    }

    int newsockfd = -1;
    while(1)
    {
        newsockfd = accept(sockfd, NULL, NULL);
        if(newsockfd < 0)
        {
            perror("accept");
            return -1;
        }

        // <html>Hello World</html> text/html
        // 首行
        // 响应报头
        // 空行
        // 响应体
        std::stringstream ss;
        ss << "http/1.1 200 OK\r\n";
        ss << "Content-Type: text/html\r\n";
        ss << "Content-Length: "<< body.size() << "\r\n";
        ss << "\r\n"; //空行
        
        std::string body = "<html>Hello World</html>";

        ret = send(newsockfd, ss.str().c_str(), ss.str().size(), 0); // 发头部
        if(ret < 0)
        {
            perror("send");
            return -1;
        }

        ret = send(newsockfd, body.data(), body.size(), 0); // 发正文
        if(ret < 0)
        {
            perror("send");
            return -1;
        }
    }

    close(newsockfd); 
    close(sockfd);
    return 0;
}

编译, 启动服务. 在浏览器中输入 http://[ip]:[port], 就能看到显示的结果 “Hello World”。

在这里插入图片描述
在这里插入图片描述
备注:
此处我们使用 8080 端口号启动了HTTP服务器。
虽然HTTP服务器一般使用80端口,但这只是一个通用的习惯。
并不是说HTTP服务器就不能使用其他的端口号。

4. 测试不同的状态码

把返回的状态码改成302, 404, 403, 504等, 看浏览器上分别会出现什么样的效果。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <sstream>

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        //./http_test [ip] [port] 或者 ./http_test 0.0.0.0 9090
        printf("using \"./http_test [ip] [port]\"\n");
        return -1;
    }

    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip.c_str());

    int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    ret = listen(sockfd, 5);
    if(ret < 0)
    {
        perror("listen");
        return -1;
    }

    int newfd = accept(sockfd, NULL, NULL);
    if(newfd < 0)
    {
        perror("accept");
        return -1;
    }

    while(1)
    {
        char buf[1024] = { 0 };
        ret = recv(newfd, buf, sizeof(buf) - 1, 0);
        if(ret < 0)
        {
            perror("recv");
            continue;
        }
        else if(ret == 0)
        {
            printf("peer shutdown\n");
            close(newfd);
            close(sockfd);
            return -1;
        }
        printf("chrome say: %s\n", buf);
        memset(buf, '\0', sizeof(buf));

        //模拟http协议:
        //buf==>响应报头,body==>响应正文
        std::string body = "<html>Hello World</html>";
        std::stringstream header;
        
        header << "HTTP/1.1 200 OK\r\n";
        header << "Content-Type: text/html\r\n";
        header << "Content-Length: " << body.size() << "\r\n";
        header << "\r\n";

        //header << "HTTP/1.1 302 Redirect\r\n";
        //header << "Content-Type: text/html\r\n";
        //header << "Content-Length: " << body.size() << "\r\n";
        //header << "Location: https://www.baidu.com\r\n"; // 重定向
        //header << "\r\n";

        //header << "HTTP/1.1 404 page not found\r\n";
        //header << "Content-Type: text/html\r\n";
        //header << "Content-Length: " << body.size() << "\r\n";
        //header << "\r\n";

        send(newfd, header.str().c_str(), header.str().size(), 0);
        send(newfd, body.c_str(), body.size(), 0);
    }

    close(newfd);
    close(sockfd);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值