计算机网络HTTP/HTTPS

Posted 进击的小白*

tags:

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

HTTP网络协议

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

理解网络协议

协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?

首先我们可以将这种结构化数据存储在一个结构体中,通信双方都知道这种结构体,接收发送请求或者响应就使用结构体进行接收,从此达到传输结构化数据的目的。而结构体如何定义,结构体内部成员又是如何?这是客户端和服务端在通信前就已经约定好了的,通信的前提是双方都知道,并且愿意遵守这个约定

网络版简易计算器

全部代码详见码云

使用这两个结构体是我们自己定义的约定(协议)我们创建的client 和 server 都必须遵守!这就叫做自定义协议

我们所写的cs模式的在线版本计算器,本质是一个应用层网络服务,基本的通信代码是我们自己写的,序列化和反序列化(后文会讲)是通过组件完成的,请求,结果格式等约定时我们自己做的,业务逻辑也是我们自己写的

通过这个简易版计算器我们可以对OSI七层模型的上三层建立初步理解

//请求结构体
typedef struct request //请求结构体
    int x; 				//数字1	
    int y;				//数字2
    char op;			//运算符
request_t;

//响应结构体
// response format 相应格式
typedef struct response
    int sign;			//标志位,反应结果是否可靠
    int result; 		//结果
response_t;

部分服务器通信代码

// 服务器 
// 1.Read request
  request_t req;							  //创建请求结构体接收请求
  memset(&req, 0, sizeof(req));		
  ssize_t s = read(sock, &req, sizeof(req));  //将网络数据传输给请求结构体对象
  std::cout << "get a request, request size = " << sizeof(req) << std::endl;
  std::cout << s << " " << req.x << req.op << req.y << std::endl; 
  if (s == sizeof(req))			
    // Read full request success 			 //若获取的请求完整
    // 2.prase request 						 //解析请求信息,构建响应结构体
    std::cout << "prase request" << std::endl;
    response_t resp0 , 0;					 
    switch (req.op)						 //通过请求信息来构建响应
      case '+':
        resp.result = req.x + req.y;
        break;
      case '/':
        if (req.y == 0) resp.sign = 1;
        else resp.result = req.x / req.y;
        break;
      default:
        resp.sign = 3; // request method errno
        break;
    
    // send response
    std::cout << "return response" << std::endl;
    write(sock, &resp, sizeof(resp));	 	 //将构建的响应发送给客户端
  

部分客户端通信代码

  // 客户端
  request_t req;						//从标准输入(客户)得到数据保存到结构体中
  cout << "Please Enter Date One# ";
  cin >> req.x;
  cout << "Please Enter Date Two# ";
  cin >> req.y;
  cout << "Please Enter Operator";
  cin >> req.op;

  write(sock, &req, sizeof(req));	 	//将结构体发送给服务器
  response_t resp;						//创建响应结构体接收服务器响应
  ssize_t s = read(sock, &resp, sizeof(resp)); //读取响应内容打印结果
  if (s == sizeof(resp))
    if (resp.sign == 1)
      std::cout << "除零错误" << std::endl;
    
    else if (resp.sign == 3)
      std::cout << "非法运算符" << std::endl;
    
    else 
      std::cout << "result = " << resp.result << std::endl;
    
  

通过上述代码我们确实通过自己定义的协议使用结构体完成了网络结构化数据的传输,但是这种方式却有着非常明显的弊端。首先,我们必须保证客户端和服务器的内存对齐方式必须相同,其次一旦服务器进行更新,对传输的结构体进行了修改,那么以前的所有客户端就用不了,因为两个结构体的格式不同,那么也就无法寄希望于传输后的数据可以原模原样的拿出来了。再者,有很多场景的某些数据大小是并不固定的,比如微信聊天,你怎么知道一个人发的信息内容有多大呢,对于信息我们结构体应该开多大的空间才合适??开大了浪费网络资源,开小了可能长一点的话发了就会出现截断或者乱码等问题

为了解决上述问题,以前的程序员们提出了序列化和反序列化

序列化和反序列化

当一台主机想要将数据上传到网络之前,将数据进行序列化然后再传入网络。而一台主机想要从网络读取数据后,需要将网络中的数据进行反序列化

JSON 是我们日常开发中常用的一种序列化和反序列化工具

sudo yum install -y jsoncpp-devel //安装json

JSON传输数据

JSON序列化
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

typedef struct request
    int x;
    int y;
    char op;
request_t;

int main()
    request_t req10, 20, '*';
    //序列化过程
    Json::Value root; //可以承装任何对象,json是一种kv式的序列化方案
    root["datax"] = req.x;
    root["datay"] = req.y;
    root["operator"] = req.op;

    // Json::StyledWriter writer;
    Json::FastWriter writer;
    writer.write(root);
    std::string json_string = writer.write(root);
    std::cout << json_string << std::cout;
    return 0;


Json::StyledWriter类型对象构建的json_string 
    
"datax":10,"datay":20,"operator":42

Json::FastWriter 类型对象构建的json_string
    

   "datax" : 10,
   "datay" : 20,
   "operator" : 42

[clx@VM-20-6-centos JsonTest]$ ldd a.out
        linux-vdso.so.1 =>  (0x00007fffddfee000)
        /$LIB/libonion.so => /lib64/libonion.so (0x00007f80236f2000)
        libjsoncpp.so.0 => /lib64/libjsoncpp.so.0 (0x00007f80233a2000) // 这就是第三方组件,也就是一个动态库
        libstdc++.so.6 => /home/clx/.VimForCpp/vim/bundle/YCM.so/el7.x86_64/libstdc++.so.6 (0x00007f8023021000)
        libm.so.6 => /lib64/libm.so.6 (0x00007f8022d1f000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f8022b09000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f802273b000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f8022537000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f80235d9000)
JSON反序列化
int main()
    // 反序列化
    std::string json_string = R"("datax":10, "datay":20, "operator":42)";//R()可以防止内部字符部分字符被转义
    Json::Reader reader; //使用Json::Reader类型对象反序列化 序列化数据,放入万能对象root中
    
    Json::Value root;
    reader.parse(json_string, root);
    request_t req;
    req.x = root["datax"].asInt();//root使用key来查找value数据,并使用asInt()函数转化成对应类型
    req.y = root["datay"].asInt();
    req.op = root["operator"].asUInt();
    std::cout << req.x << " " << req.op << " "<< req.y << std::endl;
    return 0; 

使用JSON优化计算器

1.使用JSON对请求和响应结构体分别进行序列化和反序列化

std::string SerializeRequest(const request_t &req)
    Json::Value root;
    root["datax"] = req.x;
    root["datay"] = req.y;
    root["operator"] = req.op;

    Json::FastWriter writer;
    std::string json_string = writer.write(root);
    return json_string;


void DeserializeRequest(const std::string &json_string, request_t &out)
    Json::Reader reader;
    
    Json::Value root;
    reader.parse(json_string, root);
    out.x = root["datax"].asInt();
    out.y = root["datay"].asInt();
    out.op = root["operator"].asUInt();


std::string SerializeResponse(const response_t &resp)
    Json::Value root;
    root["sign"] = resp.sign;
    root["result"] = resp.result;

    Json::FastWriter writer;
    std::string json_string = writer.write(root);
    return json_string;


void DeserializeResponse(const std::string &json_string, response_t &out)
    Json::Reader reader;
    
    Json::Value root;
    reader.parse(json_string, root);
    out.sign = root["sign"].asInt();
    out.result = root["result"].asInt();

2.使用JSON字符串在网络内传输数据

  //1.Method2 ReadRequest 从网络中读取Json字符串
  char buffer[1024] = 0;
  ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
  buffer[s] = 0;
  if (s > 0)
    request_t req;
    DeserializeRequest(buffer, req);
      
  // send response 将结构化数据进行序列化后再发送
    std::string json_string = SerializeResponse(resp);
    write(sock, json_string.c_str(), json_string.size());
    std::cout << "return response successs" << std::endl;

正式认识HTTP协议

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

HTTP协议,本质上和我们刚刚写的网络计算器没有区别,都是应用层协议,其内部也是实现我们网络计算器内的三个步骤1.网络通信 2.序列化和反序列化 3.协议细节

URL基本认识

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

我们请求的图片,视频等都称之为资源,这些资源都是存在网络中一台Linux机器上。IP + Port唯一确定一个进程,却不能唯一标识一个资源。而传统的操作系统保存资源的方式都是以文件保存的,单Linux系统,标识唯一资源的方式就是通过路径。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cJ4B5LL-1674359312024)(C:\\Users\\Lenovo\\AppData\\Roaming\\Typora\\typora-user-images\\image-20230117095522126.png)]

IP + Port唯一确定一个进程(IP通常是以域名呈现)

IP+Linux路径可以唯一确定一个网络资源(路径可以通过目录名 + /确认)

常见的网络协议如HTTP它们都有自己规定好的服务器端口号,这就像报警电话是110一样,所有人中国人都知道110是报警电话。而所有学过网络程序员,也都知道HTTP对应端口号80,所以这个端口号在很多情况下都可以省略

urlencode和urldecode

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

转义的规则如下: 将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY 格式

例如我们分别在百度中搜索 翻译和C++

观察上面的网址可以发现其中有个wd字段是来传输我们的搜索关键词的,翻译在URL中并没有变,而C++中的两个加号受到了转义处理,这是因为 + 这样的字符被url当作特殊意义理解了,传输前会对其进行转义

我们可以通过这个转义工具来对自己的字符串进行转义,查看其在URL中的形态

HTTP协议格式

无论是请求还是响应,http都是按照行(\\n)为单位进行构建请求或者响应的!无论是请求还是响应,几乎都是由3或者4部分组成的

如何理解普通用户的上网行为 1.从目标服务器拿到你要的资源 2.向目标服务器上传你的数据

HTTP请求

第一部分 请求行(第一行) 由 请求方法 + url(去掉域名后的内容) + http协议版本 + \\n 构成

第二部分 请求报头(Header), 冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束

第三部分 空行 \\n

第四部分 请求正文(Body)(如果有的话) 用户提交的数据,请求正文允许为空字符串. 如果正文存在, 则在Header中会有一个Content-Length属性来标识Body的长度

前三部分称为HTTP的请求报头,第四部分成为就HTTP的有效载荷

HTTP响应

第一部分 状态行(第一行) 由 http协议版本 + 状态码 + 状态码描述

第二部分 请求报头(Header), 冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束

第三部分 空行 \\n

第四部分 响应正文(Body)(如果有的话) 用户提交的数据,响应正文允许为空字符串. 如果正文存在, 则在Header中会有一个Content-Length属性来标识响应正文的长度

前三部分称为HTTP的响应报头,第四部分成为就HTTP的有效载荷

思考:

1、HTTP响应或者请求如何解包,如何分用

2、HTTP请求或者响应,是如何被读取的?以字符串发送

3、HTTP请求时如何被发送的?以字符串读取

4、http request 和 http response被如何看待?字符串

如何解包:我们讲请求和响应看成一个大字符串,其中的空行是在HTTP协议中是一种特殊字符,使用空行将HTTP报头和有效载荷区分开来,当我们一行一行读取数据,当这一行没有数据只有\\n时,我们就知道HTTP报头读完了,接下来的部分是有效载荷

如何分用:这并不是http解决的,而是通过具体的应用代码解决的,http需要由接口来帮助上层获取参数

HTTP方法

协议支持并不代表服务器支持此方法,服务器会根据自身情况来对各种方法进行支持

HTTP常见状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden权限过低无法访问), 301(永久重定向),302or307(Redirect, 临时重定向), 504(Bad Gateway)。

永久重定向:例如一个网站更新了,并且网址也更新了,他在原来的网址上设置一个永久重定向,这样你访问旧网址就可以跳到新的网页

临时重定向:例如美团下单,会从美团跳入下单界面,当我们下完单后又会自动跳转回原界面

应用层是人要参与的,人水平参差不齐,http状态码很多人根本不清楚如何使用,又因为浏览器非常的多,导致大家对状态码的支持并不是特别好,有时你写一个错误的状态码照样能显示。所以现在404状态码对浏览器没有任何指导意义,浏览器就是正常显示你的网页

HTTP常见Header

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

Conection: keep-alive 长链接

我们之前做的所有实验都是请求响应断开链接,但是一个服务器上有很多资源,一个大型网页内部,是由非常多的资源组成的,每个资源都需要发起http请求。而http/1.1推出长链接版本,双方链接只建立一次,所有资源交互完毕后再关闭链接。通过减少频繁建立TCP丽娜姐,来达到提高效率的目的

搭建简单HTTP服务器

简单前端页面设计

这个网站包含简单的前端也米娜HTML编写教程w3cschool ,我通过这个网站的表单,制作了一个简单的首页HTML界面

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <h3>hello net</h3> 
        <h5>hello 我是表单</h5>
        <from action="/a" method = "POST">
            姓名:<input type="text" name="name"><br/>
            密码:<input type="password" name="passwd"><br/>
            <input type="submit" value="登录"> <br/>
        </from>
    </body>
</html>

HTTP服务器搭建

//这里使用了专门的网络写入读取接口
ssize_t recv(int sockfd, void *buf, size_t len, int flags);  
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

这组接口和我们的read和write接口用法一摸一样,最后的falgs参数设置为0就可以了,其他用法可以使用man recv指令查看文档

#include "Sock.hpp"
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fstream>

#define WWWROOT "./wwwroot/"    //根目录
#define HOME_PAGE "index.html"	//首页

void Usage(std::string proc)
    std::cout << "Usage " << proc << " port " << std::endl; 


void* HandlerHttpRequest(void* args)
    int sock = *(int*)args;
    delete (int*)args;
    
    pthread_detach(pthread_self());
#define SIZE 1024 * 10
    char buffer[SIZE];
    memset(buffer, 0, SIZE);
    ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
        buffer[s] = 0

以上是关于计算机网络HTTP/HTTPS的主要内容,如果未能解决你的问题,请参考以下文章

基于FTP的载荷投递

jwt认证

iPhone 在 App 中推送有效载荷信息

基本网络概述

基本网络概述

H264 NAL解析