网络http协议

Posted 华丞臧.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络http协议相关的知识,希望对你有一定的参考价值。

🥁作者 华丞臧.
📕​​​​专栏【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站


文章目录


前言

在上一篇文章中我们见识了序列化和反序列化,其是处于应用层的,是进行网络通信双方进行一种约定;在网络发展至今,已经有了一些非常成熟的场景,并且有大佬自定义的一些协议,写的很好因此成为了应用层特定协议的标准,

一、http协议

虽然我们说,应用层协议是我们程序猿自己定的。但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用,http(超文本传输协议)就是其中之一,底层主流采用的是tcp协议,http是无连接的。

1.1 认识URL

平时我们所说的网址就是URL。

服务器地址是域名,域名就是字符串类型的字段,域名在我们访问网网站的时候必须被转换称为IP地址,访问网络服务必须具有网络号即端口号,网络通信的本质就是socket,IP+port。
一般在使用确定协议的时候,再url上面会缺省端口号,在进行网络通信时端口号会被(浏览器)自动添加上,所以浏览器访问指定url时,浏览器必须给我们自动添加port,浏览器就必须知道这些端口,所以特定的众所周知的服务端口号都是并且必须是确定的。因此,在前面学习端口号时,我们说用户自己使用的套接字不能在0~1023。

http --> 80
https --> 443
sshd --> 22

http是做什么的

http是以网页的形式呈现的,比如说查阅文档、查看视频;所以http是获取网页资源的,视频、音频等也都是文件,而网页也是一个.html文件。

http是向特定服务器申请特定资源的,获取到本地进行展示或者某种使用的。

网页上的资源在该网页的网络服务器(软件)所在的服务器(硬件)上,而服务器都是Linux,资源文件存放在Linux服务器上;用户在网页上申请资源时,服务器需要根据路径找到该文件,所以URL中需要添加资源文件的路径。/是Linux下的路径分隔符,但是需要注意这个/不一定为根目录,因为软件层面可以做一些处理,比如在这个目录前添加一个路径。

urlencode和urldecode

像 / ? :等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。
比如, 某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。在进行网络传输进行编码时,尤其是http中有些字段也会进行编码,服务端进行解码。编码是浏览器做的,解码是服务器做的。
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。

“+” 被转义成了 “%2B”
urldecode就是urlencode的逆过程:
urlencode工具

网页的内容在开发者工具中可以看到,如下图:

1.2 http格式

http是基于行的协议。

http请求

  • 首行:[方法] + [url] + [版本]
  • Header:请求的属性, 冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束 。
  • Body:空行后面的内容都是Body,Body允许为空字符串。如果Body存在,则在Header中会有一个 Content-Length属性来标识Body的长度。

http响应

  • 首行: [版本号] + [状态码] + [状态码解释]
  • Header:请求的属性,冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束。
  • Body:空行后面的内容都是Body,Body允许为空字符串。如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度;如果服务器返回了一个html页面,那么html页面内容就是在 body中。

1.3 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
UNLINE断开连接关系1.0

其中最常用的就是GET方法和POST方法.

1.4 http的状态码

类别原因短语
1XXInformational(信息状态码)接收的请求正在处理
2XXSuccess(成功状态码)请求正常处理完毕
3XXRedirection(重定向状态码)需要进行附加操作以完成请求
4XXClient Error(客户端错误状态码)服务器无法处理请求
5XXServer Error(服务器错误状态码)服务器处理请求出错

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

1.5 http常见Header

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

二、http服务器

2.1 最简单的http服务器

使用tcp协议的服务器端,在浏览器中可以连接该服务器,浏览器上默认使用http协议进行连接。在前几篇文章中已经编写了tcp套接字,这里只需要编写提供服务的代码即可。

#include <iostream>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<sys/types.h>
#include<sys/wait.h>
#include "Log.hpp"

using namespace std;
volatile bool quitSer = false;

void Usage(void *vgs)

    std::cout << "Usage:./tcpserver port ip" << std::endl;

//提供服务
void Serverhttp(int sock)

    char buffer[10240];
    ssize_t s = read(sock, buffer, sizeof buffer);
    cout << s << endl;
    if(s > 0)
    
        logMessage(DEBUG, "read success..");
        std::cout << buffer;
    


class server

public:
    server(int port, std::string ip = "")
        : sockfd_(-1), ip_(ip), port_(port)
    
    
    ~server()
    
    

public:
    void init()
    
        // 1. 创建套接字
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        
            logMessage(FATAL, "socket:%s[%d]", strerror(errno), sockfd_);
            exit(SOCK_ERR);
        
        logMessage(DEBUG, "socket success..");

        // 2.填充域
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));

        // 3. 绑定网络信息
        if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
        
            logMessage(FATAL, "bind:%s[%d]", strerror(errno), sockfd_);
            exit(BIND_ERR);
        
        logMessage(DEBUG, "bind success...");

        // 4. 监听套接字
        if (listen(sockfd_, 5) < 0)
        
            logMessage(FATAL, "listen:%s[%d]", strerror(errno), sockfd_);
            exit(LISTEN_ERR);
        
        logMessage(DEBUG, "listen success...");

        // 完成
    

    void start()
    
        char inbuffer_[1024]; // 用来接收客户端发来的消息
        // 提供服务
        while (true)
        
            quitSer = false;
            
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 5. 获取连接, accept 的返回值是一个新的socket fd
            logMessage(DEBUG,"start1");
            int serviceSock = accept(sockfd_, (struct sockaddr *)&peer, &len);
            logMessage(DEBUG,"start2");
            if (serviceSock < 0)
            
                // 获取链接失败
                logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
                continue;
            
            logMessage(DEBUG, "accept success");

            pid_t id = fork();
            if(id == 0)
            
                pid_t tid = fork();
                if(tid == 0)
                
                    uint16_t peerPort = htons(peer.sin_port);
                    std::string peerIp = inet_ntoa(peer.sin_addr);
                    Serverhttp(serviceSock);
                    exit(0);
                

                exit(0);
            
            close(serviceSock);
            int ret = waitpid(id, nullptr, 0);
            if(ret < 0)
            
                logMessage(WARINING, "wait child error:%d", errno);
            
            logMessage(DEBUG, "wait success...");
        
    

private:
    int sockfd_;
    uint16_t port_;
    std::string ip_;
;

// ./tcpserver port ip
int main(int argc, char *argv[])

    if (argc < 2 || argc > 3)
    
        Usage(argv[0]);
        exit(USAGE_ERR);
    

    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
        ip = argv[2];
    std::cout << port << ":" << ip << std::endl; 

    server tcpSer(port, ip);
    tcpSer.init();
    tcpSer.start();
    return 0;

服务端并没有提供服务,所以访问服务器时网页显示如下图:

在Linux服务端可以看到请求的相关属性,如下图:

2.2 简单的http服务器

服务器收到请求,提供服务向对应的套接字文件中写入一个“hello world”,注意响应的格式为首行+header+body,其中header和body之间需要空一行。

void Serverhttp(int sock)

    char buffer[10240];
    ssize_t s = read(sock, buffer, sizeof buffer);
    cout << s << endl;
    if(s > 0)
    
        logMessage(DEBUG, "read success..");
        std::cout << buffer;
    
	
	//开始响应
    string response;
    response = "HTTP/1.0 200 OK\\r\\n"; //响应首行
    response += "\\r\\n";   //空行
    response += "hello world!!";  //正文
    send(sock, response.c_str(), response.size(), 0);

服务器收到的请求报文如下图:

浏览器收到的响应如下图:

使用telnet可以查看服务器给用户的响应。

//安装telnet
sudo yum install -y telnet

//使用格式
telnet ip port
//connected后
//出现escape character时使用ctrl+
//输入请求

2.3 html的http服务器

作为服务端,它并不关心html的信息也不需要管html的格式是什么,服务器只负责网络请求并且把网页信息给用户推过去就行了,所以实际上在进行网站开发时,网页的资源都是通过html文件推送给用户的,请求的文件在请求行中,其第二个字段就是用户需要访问的文件,如下图:

其中/并不是根目录,而是web根目录,可以设置成为根目录。

//获取资源路径
string getPath(string in)

    size_t pos = in.find(CRLF);
    string request = in.substr(0, pos);
    cout << "request:" << request << endl;
    // get path http/1.0
    size_t first = request.find(SPACE); //第一个空格
    cout << "first:" << first << endl;
    if(first == string:: npos) return nullptr;
    size_t second = request.rfind(SPACE); //第二个空格
    cout << "second:" << second << endl;

    if(second == string::npos) return nullptr;
    string path = request.substr(first + SPACE_LEN, second- (first + SPACE_LEN));
    if(path.size() == 1 && path[0] == '/') path += HOME_PAGE; //用户访问根目录,就返回主页
    cout << "getPath:" << path << endl;

    return path;

//获取资源
string readFile(string& recource)

    ifstream in(recource);
    if(!in.is_open()) return "404";
    string content;
    string line;
    while(getline(in, line)) content += line;
    return content;


//提供服务
void Serverhttp(int sock)

    //std::cout << peerIp << ":" << peerPort << std::endl;
    char buffer[10240];
    ssize_t s = read(sock, buffer, sizeof buffer);
    cout << s << endl;
    if(s > 0)
    
        logMessage(DEBUG, "read success..");
        std::cout << buffer;
    
    // 1. 获取路径
    string path = getPath(buffer);

    string recource = ROOT_PATH;
    recource += path;
    cout << recource << endl;
    
    // 获取对应文件内容
    string html = readFile(recource);

    // 开始响应
    string response;
    response = "HTTP/1.0 200 OK\\r\\n";
    response += "Content-Type:text/html\\r\\n";
    response += ("Content-Length:" + std::to_string(html.size()) + "\\r\\n");
    response += "\\r\\n";
    //response += "<html><h1>hello world!!</h1><html>\\r\\n";
    response += html;

    send(sock, response.c_str(), response.size(), 0);


关于html的编写方法可以参考👉菜鸟教程

//index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> //
<title>华丞臧(runoob.com)</title> //网页名称
</head>
<body> //正文
    <h1>我的第一个标题</h1>   //标题
    <p>我的第一个段落。</p>
</body>
</html>

在浏览器中访问服务器可以看到网页显示如下图所示:


Liunx上收到的请求如下图,左为服务器收到的请求,右为客户端收到的响应。

2.4 表单

用户的网络行为无非有两种

  1. 用户将自己的远端资源拿到本地:get /index.html http/1.1;
  2. 用户想将自己的属性字段提交到远端。

在浏览器上,网页通常会让用户注册一个账号,用来标识该用户,用户的存放在远端的资源也可以使用该账号标识,在html中这种端口称为表单。

get

在http中get会以明文方式将用户对应的参数信息拼接到url中。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>华丞臧(runoob.com)</title>
</head>
<body>
    <h1>hello world</h1>
    <p>我的第一个段落。</p>
    <form action="html_form_action.php" method="get">
        Username: <input type="text" name="user"><br>
        Password: <input type="password" name="passwd"><br>
        <input type="submit" value="Submit">
        </form>
</body>
</html>

表单形式如下图所示:

post

在http中post会以明文方式将用户对应的参数信息拼接到正文中。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>华丞臧(runoob.com)</title>
</head>
<body>
    <h1>hello world</h1> 
    <p>我的第一个段落。</p>
    <form action="html_form_action.php" method="post">
        Username: <input type="text" name="user"><br>  //用户名,<br>标识换行
        Password: <input type="password" name="passwd"><br>  //密码
        <input type="submit" value="Submit">
        </form>
</body>
</html>

表单形式如下图所示:

在表单中填入用户名和密码后,可以看到url中并没有拼接用户参数,如下图:

使用progress telerik fiddler可以查看用户发送的请求报文(Raw),可以看到用户参数被拼接到正文当中,如下图:

在服务端也可以收到用户发来的请求,如下图:

以上是关于网络http协议的主要内容,如果未能解决你的问题,请参考以下文章

网络知识&http协议

[ 网络 ] 应用层协议 —— HTTP协议

iOS开发网络篇—HTTP协议

网络知识之二HTTP协议

Python网络爬虫——http和https协议

[网络]应用层协议:HTTP / HTTPS[转载]