Linux网络:HTTP协议 | URL | 协议格式 | HTTP方法 | HTTP状态码 | Cookie与Session


全文约 11617 字,预计阅读时长: 34分钟


网络计算器

  • 从代码层感知协议的含义:客户端、服务端按照规定的协议进行数据的发送和处理。

服务端

  • 数据协议ptl.hpp
#pragma once 

#include <iostream>
#include <string>
#include <unistd.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

namespace ns_protocol
{
    struct request//客户按照这样的约定发送数据
    {
        int x;
        int y;
        char op;

        request():x(0),y(0),op('+'){}
    };
    struct respone//处理数据的返回结果
    {
        int code;
        int result;
        respone():code(0),result(0){}
    };
    
}

  • sv.hpp
#pragma once 

#include "ptl.hpp"
#include <pthread.h>

namespace ns_server
{
    class Server
    {
        private:
            uint16_t port;//端口
            int listen_sock;
        public:
            Server(uint16_t _po):port(_po),listen_sock(-1){}
            
            void IninSe()
            {
                listen_sock = socket(AF_INET,SOCK_STREAM,0);//创建
                if(listen_sock<0)
                {
                    cerr<<"socket error " <<endl;
                    exit(2);
                }

                struct sockaddr_in local;
                bzero(&local,sizeof(local));
                local.sin_family = AF_INET;
                local.sin_port = htons(port);
                local.sin_addr.s_addr = INADDR_ANY;

                if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0)//绑定
                {
                    cerr<<"bind error"<<endl;
                    exit(3);
                }

                if(listen(listen_sock,5)<0)//监听
                {
                    cerr<<"listen error"<<endl;
                    exit(4);
                }
            }
            static void* calc(void* args)//线程处理函数
            {
                int sock = *(int*)args;
                delete (int*)args;
                while(true)
                {
                    ns_protocol::request req;
                    ssize_t s = recv(sock,&req,sizeof(req),0);//接受数据,存储 处理
                    if(s>0)
                    {
                        ns_protocol::respone rsp;
                        switch(req.op)
                        {
                            case '+':
                                rsp.result = req.x + req.y;
                                break;
                            case '-':
                                rsp.result = req.x - req.y;
                                break;
                            case '*':
                                rsp.result = req.x * req.y;
                                break;
                            case '/':
                                if(req.y!=0)
                                {
                                    rsp.result = req.x / req.y;
                                }
                                else{
                                    rsp.code = 1;
                                }
                                break;
                            case '%':
                                if(req.y!=0)
                                {
                                    rsp.result = req.x % req.y;
                                }
                                else
                                {
                                    rsp.code = 2;
                                }
                                break;
                            default:
                                rsp.code = 3;
                                break;
                        }
                        send(sock,&rsp,sizeof(rsp),0);//发送数据
                    }
                    else if (s==0)
                    {
                        cout<< "cl quit---"<<endl;
                        break;
                    }
                    else
                    {
                        cerr<<"cl quit---,me to0"<<endl;
                        break;
                    }
                }
                close(sock);
                return nullptr;
            }

            void Loop()//循环获取连接,处理数据
            {
                while(true)
                {
                    struct sockaddr_in peer;
                    socklen_t len = sizeof(peer);
                    int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);//获取客户端的连接
                    if(sock<0)
                    {
                        cerr << "accept error" << endl;
                        exit(5);
                    }
                    pthread_t tid;
                    int* p = new int(sock);
                    pthread_create(&tid,nullptr,calc,p);//创建线程
                }
            }
            ~Server()
            {
                if(listen_sock>=0)
                {
                    close(listen_sock);
                }
            }
    };
}

---//sv.cc
#include "sv.hpp"

int main()
{
    uint16_t port = 8081;
    ns_server::Server svr(port);

    svr.IninSe();
    cout<<"初始化完成"<<endl;
    svr.Loop();

    return 0;
}


客户端

  • cl.hpp
#pragma once 

#include "ptl.hpp"
#include <string>

namespace ns_client
{
    class client
    {
        private:
            string svr_ip;
            uint16_t svr_port;
            int sock;
        public:
            client(const string& _ip,uint16_t _port)
                :svr_ip(_ip)
                 ,svr_port(_port)
             {}

            void InitClient()
            {
                sock = socket(AF_INET,SOCK_STREAM,0);//创建
                if(sock<0)
                {
                    cerr<<"socket error"<<endl;
                    exit(2);
                }
            }

            void Run()
            {
                struct sockaddr_in peer;
                bzero(&peer,sizeof(peer));

                peer.sin_family = AF_INET;
                peer.sin_port = htons(svr_port);
                peer.sin_addr.s_addr = inet_addr(svr_ip.c_str());

                int ret = connect(sock,(struct sockaddr*)&peer,sizeof(peer));//发起连接
                if(ret<0)
                {
                    cerr<<"connect error"<<endl;
                    exit(3);
                }
                while(true)
                {
                    ns_protocol::request req;
                    cout<<"cin one number# "<<endl;
                    cin>>req.x;
                    cout<<"cin onr char# "<<endl;
                    cin>>req.op;
                    cout<<"cin one number# "<<endl;
                    cin>>req.y;

                    send(sock,&req,sizeof(req),0);//发送

                    ns_protocol::respone rp;
                    ssize_t s = recv(sock,&rp,sizeof(rp),0);//接收
                    if(s>0)
                    {
                        cout<< "code : " <<rp.code<<endl;
                        cout<<"answer:  "<<rp.result<<endl;
                    }
                }

            }

            ~client()
            {
                if(sock>=0)
                {
                    close(sock);
                }
            }

    };
}

---//cl.cc
#include "cl.hpp"

int main()
{
    string ip ="127.0.0.1";
    uint16_t port = 8081;

    ns_client::client cl(ip,port);

    cl.InitClient();

    cl.Run();
    return 0;
}

---//makefile
.PHONY:all
all:cl sv 

cl:cl.cc
		g++ -o $@ $^ -std=c++11 
sv:sv.cc
		g++ -o $@ $^ -std=c++11 -lpthread 

.PHONY:clean
clean:
		rm -f cl sv


HTTP协议

HTTP(超文本传输协议)是应用层最重要的协议。有些场景需要自己定义协议。目前所有的上网行为,本质无非两种情况:一,把服务器上的数据拉取到本地;二,把自己的数据提交到服务器上(注册/登录)。程序员角度,IP=域名;用户角度,域名为了用户方便使用。
在这里插入图片描述

序列化、反序列化:将结构化的数据,序列化成可以在网络上传输的字符串流。序列化和反序列化的算法:json、XML。


认识URL

  • URL(Uniform Resoure Locator)中文译为统一资源定位符,是 Internet 上资源的地址,比如一个文本文件,一张图片,一个视频。
  • 通过 URL 我们可以知道网络资源的位置以及访问它的协议。URL 协议特定部分以双斜杠 “//” 开头,表明它符合通用 Internet 协议语法。
  • 是一个字符串序列,由数字、字母和特殊字符组成。
    在这里插入图片描述

协议方案名

  • 协议名称,又叫Scheme,不区分大小写,以“:”作为结束符号,表示获取资源需要使用的协议。比如,http,https(即http+ssh),ftp。
    Internet 有很多应用层的协议可以获取网络资源,其 URL 的格式语法会有所不同。以下是常见的协议。
ftp                     File Transfer protocol
http                    Hypertext Transfer Protocol
gopher                  The Gopher protocol
mailto                  Electronic mail address
news                    USENET news
nntp                    USENET news using NNTP access
telnet                  Reference to interactive sessions
wais                    Wide Area Information Servers
file                    Host-specific file names
prospero                Prospero Directory Service

登录信息

  • user: 一个可选的用户名。有些协议(例如 ftp)允许指定用户名。
  • password: 一个可选的密码。如果存在,则在用户名后面用冒号分隔。用户名和密码如果存在,后面跟着一个 @ 字符。在用户名和密码字段中,必须对字符 : @ / 进行编码。

服务器地址、端口号

  • host:域名。网络主机名由合法的 域名 、或者 IP 表示,其中合法的域名中一系列域标签由点号分隔,每个域标签以字母、数字、字符、开头和结尾,中间可能还包含 - 字符,最右边的域标签永远不会以、数字、开头。

  • port: 可选的端口号。大多数协议具有默认端口号,所以有时可以省略。端口号以十进制为单位,与主机用冒号分隔,如果省略了端口,冒号也是可以省略的。


带有层次的文件路径

  • 也叫:path,要获取(访问)的应用资源的路径,即资源的存储位置,一般会使用/来分级描述。路径也是可选的,缺省访问默认资源。

查询字符串

  • 也叫query:以 ? 开头,查询参数。格式为 key=value,多个参数使用 & 分隔;参数也是可选的。

片段标识符

  • 也叫:frag,一个网页上的某个资源在不调整大网页的情况下,一直在刷新,只需要调整这一小部分。
  • #:表示本网页;参数:表示在网页哪个部分。通常以#开始到最后,表示定位到页面某个位置,

urlencode和urldecode

  • / ? : 等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。
  • 比如,某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。
  • 转义的规则如下:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
    在这里插入图片描述
  • urldecode就是urlencode的逆过程,有 urlencode工具。

HTTP协议格式

  • 请求报文和响应报文都是由以下4部分组成:1.请求行,2.请求头,3.空行,4.消息主体。
  • 请求报文格式:
    在这里插入图片描述
  • 响应报文格式:
    在这里插入图片描述

请求格式

  • 通过代码 和 浏览器 验证。 核心代码:成功获取连接后,线程的执行通信函数。
	static void *HandlerRequest(void *args)
            {
                pthread_detach(pthread_self());
                int sock = *(int*)args;
                delete (int*)args;
                //debug
                //get request
                std::cout << "######################################## begin: " << sock << "   #####" << std::endl;
                char buffer[4096];
                ssize_t s = recv(sock, buffer, sizeof(buffer), 0);
                if(s > 0){
                    std::cout << buffer;
                }
                std::cout << "######################################## end: " << sock << "   #####" << std::endl;
                close(sock);    //基于短链接
                return nullptr;
            }
  • 格式如下:
######################################## begin: 5   #####
GET / HTTP/1.1
Host: 106.55.60.193:8081
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/104.0.5112.102 Safari/537.36 Edg/104.0.1293.63
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/\*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
dnt: 1
sec-gpc: 1

######################################## end: 5   #####

格式组成解析解析:

在这里插入图片描述

  • 请求行:
    • 首行经常使用的请求方法:
      • GET:请求指定的页面信息,并返回实体主体。
      • POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST 请求可能会导致新的资源的建立和/或已有资源的修改。
    • HTTP 协议版本:0.9,1.0,1.1,2.0;客户端请求用的哪个版本,服务器响应时用对应的版本回应。
  • 一些请求报头的含义:通知服务器关于客户端请求的信息。
    • Host:指定要请求的资源所在的主机和端口
    • User-Agent 作用:告诉服务器,客户端使用的操作系统、浏览器版本和名称。
    • Accept::请求什么资源。
    • Accept-Encoding:可以接受的数据编码格式;
    • Accept-Language:表示浏览器所支持的语言类型
    • Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接。只进行一次通信的叫短连接。
    • Content-Length:有效载荷的内容长度。
    • Cache-Control:对缓存进行控制,一个请求希望响应的内容是否在在客户端缓存。
    • Referer:表示这是请求是从哪个URL进来的。比如想在网上购物,但是不知道选择哪家电商平台。你就去问度娘,说哪家电商的东西便宜啊?然后一堆东西弹出在你面前,第一给就是某宝,当你从这里进入某宝的时候,这个请求报文的Referer就是www.baidu.com。
    • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)功能。

HTTP的解包和分用

  • 请求行和请求报头是HTTP的报头信息,请求正文实际就是HTTP的有效载荷,而请求当中的空行起到分离报头和有效载荷的作用。
  • 读取一个请求时,通过报头中的Content-Length(正文的长度)来精准控制读取该请求正文的长度,从而将连续的几个请求进行分开。
  • 理论上 HTTP 不需要向上交付,HTTP已经是最上一层的协议,但是上一层还有用户,需要将正文、请求方法和属性等交给用户。

响应格式

响应报文组成:

在这里插入图片描述

  • 常见的响应报头:
    • Content-Type:发送给接收者的实体正文的媒体类型。
    • Content-Lenght:实体正文的长度
    • Content-Language:描述资源所用的自然语言,没有设置则该选项则认为实体内容将提供给所有的语言阅读。
    • Date:表示消息产生的日期和时间
    • Server:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的。
    • Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。

服务端核心代码简易的模拟构建响应:

		static void *HandlerRequest(void *args)
            {
                pthread_detach(pthread_self());
                int sock = *(int*)args;
                delete (int*)args;
                
                //make response
                std::string body = "<html><head><meta charset=\"UTF-8\"></head><body><h1>hello world</h1><p>欢迎来的我的测试服务器</p></body></html>";

                 std::string response = HTTP_VERSION;
                 response += " 200 OK\n";
                 response += "Content-Type: text/html\n";
                 response += ("Content-Length: " + std::to_string(body.size()) + "\n");

                 response += "\n"; //传说中的空行,用来区分我们的报头和有效载荷
                 response += body;

                send(sock, response.c_str(), response.size(), 0);
            
                close(sock);    //基于短链接
                return nullptr;
            }
  • 动态响应:
#define HTTP_VERSION "HTTP/1.0"
#define HOME_PAGE "wwwroot/index.html"

		static void *HandlerRequest(void *args)
            {
                pthread_detach(pthread_self());
                int sock = *(int*)args;
                delete (int*)args;

                std::string response;
                std::string body;
                std::ifstream in(HOME_PAGE, std::ios::in | std::ios::binary);
                if(!in.is_open()){

                }
                else{//成功
                    std::string line;
                    while(std::getline(in, line)){
                        body+=line;
                    }

                    response = HTTP_VERSION;
                    response += " 200 OK\n";
                    response += "Content-Type: text/html\n";
                    response += ("Content-Length: " + std::to_string(body.size()) + "\n");

                    response += "\n"; //传说中的空行,用来区分我们的报头和有效载荷

                    response += body;
                }
                in.close();
                send(sock, response.c_str(), response.size(), 0);
               
                close(sock);    //基于短链接

                return nullptr;
            }
---//简易的前端html框架文件:/wwwroot/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>你好,这里是云服务器!</h1>
    <p>此时所看到的页面,是在进程所在路径的同级某个目录,该目录作为根目录。</p>
    <p>根目录下的一个html文件作为默认主页,底层通过tcp协议传输,再通过浏览器解析成所看到的网页样式</p>
    <p>test for http  response</p>
    <p>方法: 写一个网页的html文件</p>
    <p>将文件读到一个缓冲区,作为有效数据。按照响应格式,字符串+="....\n" </p>
    <p>最后 send 发送 </p>
    <p>通过html实现动态响应,将前端和后端解耦。</p>
    <p>差不多就是这个意思。。。帖子们</p>
</body>
</html>

获取响应报文的方法:postman、浏览器、telnet

  • 使用 postman 进行GET方法获取响应:
    在这里插入图片描述

  • 可以通过浏览器 ip 和 端口的方式获取响应。
    在这里插入图片描述

  • 通过 telnet 语法操作获取响应。
    在这里插入图片描述


HTTP的方法

主要介绍 GET 和 POST 方法。

GET方法

  • 一般用于向服务器获取某种资源信息。
  • 上传数据时也有可能使用GET方法,比如提交搜索数据时。百度搜索:框框里输入的搜索内容。
  • 通过url传参,url的长度是有限,所以传参有限。
  • 在浏览器中:参数被拼接到 域名 + 路径的后面,回显出来。
  • 在http请求中,参数会拼接到 请求行 中 URL 的后面,提交到服务器。
  • 缺点:对私密信息不够友好,数据不够私密。

POST方法

  • 一般用于将数据上传给服务器。

  • 是通过正文传参的,通过正文传参能传递更多的参数。

  • 提交更具有私密性。

  • 两者比较:两种方法都不安全,POST方法传参可以被截取,要做到安全只能通过加密来完成

  • HTML表单:网页可以实现账户密码登录的功能

GET 与 POST 现象验证:

  • GET:
    在这里插入图片描述

  • POST:
    在这里插入图片描述


HTPP的状态码

  每个网站的根目录都会有一个 index.html作为网站的主页面。这里的根目录可以是服务器后端任意一个路径的一个目录。

  • 状态码图片:

4xx系列:404为例:

  • 每个状态码,对应响应的描述符。404指明一个我们资源请求不到的页面描述;和其他网页没有太大差别。404报错是客户端的错误。正如一个酒吧没有卖鸡蛋炒饼一样,虽然你要什么酒都有,当你点名需要炒饼时,整个酒吧便 “ 炸 ”了。
---//wwwroot/404.html
   <title>test for 404</title>
</head>
<body>
    <h1>404</h1>
    <p>Not Found ,您请求的网页飞走了.....</p>
</body>

3xx重定向系列:301、307

  • 重定向:网站了新域名,还可以通过原来的域名访问到网站,中间做了一个中转工作,可以有多层中转,这就叫重定向。
  • 307 Permanent Redirect:永久重定向,表示资源已经永久移动到另一个位置。
    • 永久重定向第一次访问浏览器进行重定向,并且更新客户端的标签,后续再访问直接就是重定向后的网站.
  • 301:临时重定向,表示资源临时移动到了另一个位置。
    • 每次访问该网站时,都需要浏览器通过 状态码 和 Location 报头 信息 ,来帮我们完成重定向跳转到目标网站。

重定向演示:需要用到响应报文中的 Location 报头。

		---//重定向验证
                string status_line = HTTP_VERSION;//状态行
                status_line += (" " + to_string(301) + " Moved Permanently\n");

                string resp_header = "Content-Type: text/html;charset=uft8\n";
                resp_header += "location: https://cn.bing.com/?mkt=zh-CN\n";
                resp_header += "\n";
                send(sock,status_line.c_str(),status_line.size(),0);
                send(sock,resp_header.c_str(),resp_header.size(),0);

HTTP常见Header

  • Content-Type:数据类型(text/html等)
  • Content-Length:正文的长度
  • Host:客户端告知服务器,所请求的资源是在哪个主机的哪个端口上
    • 注:Host字段表明了客户端要访问的服务的IP和端口,有些服务器实际提供的是一种代理服务,也就是代替客户端向其他服务器发起请求,然后将请求得到的结果再返回给客户端,在这种情况下客户端就必须告诉代理服务器它要访问的服务对应的IP和端口
  • User-Agent:声明用户的操作系统和浏览器的版本信息
    • 注:User-Agent代表的是客户端对应的操作系统和浏览器的版本信息,访问一些网站是就会根据主机的版本和系统推送相匹配的服务
  • Referer:当前页面是哪个页面跳转过来的
    • 注:Referer记录上一个页面的好处一方面是方便回退,另一方面可以知道我们当前页面与上一个页面之间的相关性
  • Location:搭配3XX状态码使用,告诉客户端接下来要去哪里访问
  • Cookie:用于在客户端存储少量信息,通常用于实现会话(session)的功能
  • favicon,即Favorites Icon的缩写,是指显示在浏览器收藏夹、地址栏和标签标题前面的个性化图标。 以图标的方式区别不同的网站。

Cookie和Session

什么是Http无状态?Session、Cookie、Token三者之间的区别

  • HTTP的特点:
    • 无连接:双端在应用层发起http请求时,不用建立连接。
    • 无状态:打开一个网站,每次得重新登陆。
    • 有长连接,和短链接。
  • Cookie:
    • 把自己的账号密码等私密信息,保存子浏览器的默认文件中;浏览器每次发起请求的时候,http请求都会自动携带曾经写到临时文件中的内容。发送给服务端。这样的文件就叫做cookie。
    • cookie配置。有内存级别的:这次登陆上去,一个网站可能会请求多次服务器上的资源,浏览器或网页关了,下次需要重新登录。文件级别的就是保存在浏览器默认路径下的某个文件里。长期有效。
    • 有个人私密信息泄露的风险,他人借用户身份从事一些不好的事情。
  • session
    • 对登录信息进行认证,在服务端形成一个文件:session ,保存用户的信息。session 文件名 session id具有唯一性。
    • 服务端返回sid,也要在浏览器端用cookie文件保存。
    • 下次在用浏览器,携带的cookie信息的内容不再是账户密码,而是session sid。

  cookie 和 session 相互配合,依然存在信息被盗取的风险。区别在于,用户个人私密信息是保存在服务器端 的,客户端只保留了 session ID。服务器端有各位网络安全工程师通过各种复杂机制来确保安全。比如:cookie 和 session 有时间限制的。有黑就有白,安全永远是处于 相对安全的状态。

---//响应报文伪代码
	response += "HTTP/1.0 200 OK\n";
    response += "Content-Type: text/html\n";
    response += ("Content-Length: " + std::to_string(body.size()) + "\n");
    response += "Set-Cookie: sessionid=123456\n"; //添加Set-Cookie字段
    response += "\n";
    response += body;
    send(sock, response.c_str(), response.size(), 0);

参考

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值