HTTP/HTTPS协议认识
写在前面
这个博客我们要要讨论的是协议,主要是应用层.今天我们将正式认识HTTP和HTTPS,也要认识序列化和反序列化,内容比较多,但是不难
再谈协议
我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层,我们要完成下面三个步骤.
-
sock的使用
-
定制协议,也就是报头如何封报和解包
-
编写业务
协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?
例如 , 我们需要实现一个服务器版的加法器 . 客户端把要计算的两个加数发过去 , 然后由服务器进行计算 , 最后再把结果返回给客户端,此时我们就需要定制协议.我们有一下两种方案.
约定方案一
- 客户端发送一个形如 “1+1” 的字符串 ;
- 这个字符串中有两个操作数 , 都是整形 ;
- 两个数字之间会有一个字符是运算符 , 运算符只能是 + ;
- 数字和运算符之间没有空格
约定方案二
- 定义结构体来表示我们需要交互的信息 ;
- 发送数据时将这个结构体按照一个规则转换成字符串 , 接收到数据的时候再按照相同的规则把字符串转化回结构体
序列化 & 反序列化
其中我们把法案二的过程称之序列和反序列化,无论我们采用方案一 , 还是方案二 , 还是其他的方案 , 只要保证 , 一端发送时构造的数据 , 在另一端能够正确的进行解析, 就是 ok 的 . 这种约定 , 就是 应用层协议.下面我们要把方案二给实现一下.
下面我们说一下我们如何序列化和反序列化.很简单,所谓的序列化就是把我们结构体里面的内容变成一个字符串,反序列化就是把字符串按照某种格转换为结构体里面的属性.这里我们加上一个报头的封包和解包的动作,这里要确保我们一次行拿到的数据是一个结构体的数据,算是一个协议.
#pragma once#include <iostream>
#include <string>
#include <cassert>
#include <jsoncpp/json/json.h>
#include "util.hpp"// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算器#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)#define OPS "+-*/%"// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{assert(len);// 1. 确认是否是一个包含len的有效字符串*len = 0;std::size_t pos = in.find(CRLF);if (pos == std::string::npos)return ""; // 1234\r\nYYYYY for(int i = 3; i < 9 ;i++) [)// 2. 提取长度std::string inLen = in.substr(0, pos);int intLen = atoi(inLen.c_str());// 3. 确认有效载荷也是符合要求的int surplus = in.size() - 2 * CRLF_LEN - pos;if (surplus < intLen)return "";// 4. 确认有完整的报文结构std::string package = in.substr(pos + CRLF_LEN, intLen);*len = intLen;// 5. 将当前报文完整的从in中全部移除掉int removeLen = inLen.size() + package.size() + 2 * CRLF_LEN;in.erase(0, removeLen);// 6. 正常返回return package;
}// encode, 整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{// "exitCode_ result_"// "len\r\n""exitCode_ result_\r\n"std::string encodein = std::to_string(len);encodein += CRLF;encodein += in;encodein += CRLF;return encodein;
}// 定制的请求 x_ op y_
class Request
{
public:Request(){}~Request(){}// 序列化 -- 结构化的数据 -> 字符串// 认为结构化字段中的内容已经被填充void serialize(std::string *out){std::string xstr = std::to_string(x_);std::string ystr = std::to_string(y_);// std::string opstr = std::to_string(op_); // op_ -> char -> int -> 43 ->*out = xstr;*out += SPACE;*out += op_;*out += SPACE;*out += ystr;}// 反序列化 -- 字符串 -> 结构化的数据bool deserialize(std::string &in){// 100 + 200std::size_t spaceOne = in.find(SPACE);if (std::string::npos == spaceOne)return false;std::size_t spaceTwo = in.rfind(SPACE);if (std::string::npos == spaceTwo)return false;std::string dataOne = in.substr(0, spaceOne);std::string dataTwo = in.substr(spaceTwo + SPACE_LEN);std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));if (oper.size() != 1)return false;// 转成内部成员x_ = atoi(dataOne.c_str());y_ = atoi(dataTwo.c_str());op_ = oper[0];return true;}void debug(){std::cout << "#################################" << std::endl;std::cout << "x_: " << x_ << std::endl;std::cout << "op_: " << op_ << std::endl;std::cout << "y_: " << y_ << std::endl;std::cout << "#################################" << std::endl;}public:// 需要计算的数据int x_;int y_;// 需要进行的计算种类char op_; // + - * / %
};// 定制的响应
class Response
{
public:Response() : exitCode_(0), result_(0){}~Response(){}// 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的!void serialize(std::string *out){// "exitCode_ result_"std::string ec = std::to_string(exitCode_);std::string res = std::to_string(result_);*out = ec;*out += SPACE;*out += res;}// 反序列化bool deserialize(std::string &in){// "0 100"std::size_t pos = in.find(SPACE);if (std::string::npos == pos)return false;std::string codestr = in.substr(0, pos);std::string reststr = in.substr(pos + SPACE_LEN);// 将反序列化的结果写入到内部成员中,形成结构化数据exitCode_ = atoi(codestr.c_str());result_ = atoi(reststr.c_str());return true;}void debug(){std::cout << "#################################" << std::endl;std::cout << "exitCode_: " << exitCode_ << std::endl;std::cout << "result_: " << result_ << std::endl;std::cout << "#################################" << std::endl;}public:// 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了!int exitCode_;// 运算结果int result_;
};bool makeReuquest(const std::string &str, Request *req)
{// 123+1 1*1 1/1char strtmp[BUFFER_SIZE];snprintf(strtmp, sizeof strtmp, "%s", str.c_str());char *left = strtok(strtmp, OPS);if (!left)return false;char *right = strtok(nullptr, OPS);if (!right)return false;char mid = str[strlen(left)];req->x_ = atoi(left);req->y_ = atoi(right);req->op_ = mid;return true;
}
下面我们要做的非常简单,客户端拿到数据,根据数据构造一个请求的结构体,然后把这个结构体个序列化后进行封包,把最后的结构体发送过去就可以了.
//客户端
#include "util.hpp"
#include "Protocol.hpp"
#include <cstdio>
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!volatile bool quit = false;static void Usage(std::string proc)
{std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"<< std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);exit(USAGE_ERR);}std::string serverIp = argv[1];uint16_t serverPort = atoi(argv[2]);// 1. 创建socket SOCK_STREAMint sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket: " << strerror(errno) << std::endl;exit(SOCKET_ERR);}// 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽// 2.1 先填充需要连接的远端主机的基本信息struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverPort);inet_aton(serverIp.c_str(), &server.sin_addr);// 2.2 发起请求,connect 会自动帮我们进行bind!if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0){std::cerr << "connect: " << strerror(errno) << std::endl;exit(CONN_ERR);}std::cout << "info : connect success: " << sock << std::endl;std::string message;while (!quit){message.clear();std::cout << "请输入表达式>>> "; // 1 + 1std::getline(std::cin, message); // 结尾不会有\nif (strcasecmp(message.c_str(), "quit") == 0){quit = true;continue;}// message = trimStr(message); // 1+1 1 +1 1+ 1 1+ 1 1 +1 => 1+1 -- 不处理Request req;// 制作请求if (!makeReuquest(message, &req))continue;std::string package;req.serialize(&package); // 把请求序列化到这个package中std::cout << "debug->serialize-> " << package << std::endl;package = encode(package, package.size()); // 添加报头std::cout << "debug->encode-> \n"<< package << std::endl;// 把请求发送走ssize_t s = write(sock, package.c_str(), package.size());if (s > 0){char buff[1024];size_t s = read(sock, buff, sizeof(buff) - 1);if (s > 0)buff[s] = 0;std::string echoPackage = buff;Response resp;uint32_t len = 0;std::string tmp = decode(echoPackage, &len); // 去掉报头if (len > 0){echoPackage = tmp;// std::cout << "debug->decode-> " << echoPackage << std::endl;resp.deserialize(echoPackage); //反序列化printf("[exitcode: %d] %d\n", resp.exitCode_, resp.result_);}}else if (s <= 0){break;}}close(sock);return 0;
}
服务端得到字符串,首先要做的就是解包,然后进行反序列化,得到结构体,把这个任务处理好好之后,拿到数据把数依次经行序列化和分包后把结果返回过去就可以了.
// 服务端
static Response calculator(const Request &req)
{Response resp;switch (req.op_){case '+':resp.result_ = req.x_ + req.y_;break;case '-':resp.result_ = req.x_ - req.y_;break;case '*':resp.result_ = req.x_ * req.y_;break;case '/':{ // x_ / y_if (req.y_ == 0)resp.exitCode_ = -1; // -1. 除0elseresp.result_ = req.x_ / req.y_;}break;case '%':{ // x_ / y_if (req.y_ == 0)resp.exitCode_ = -2; // -2. 模0elseresp.result_ = req.x_ % req.y_;}break;default:resp.exitCode_ = -3; // -3: 非法操作符break;}return resp;
}// 1. 全部手写 -- done
// 2. 部分采用别人的方案--序列化和反序列化的问题 -- xml,json,protobuf
void netCal(int sock, const std::string &clientIp, uint16_t clientPort)
{assert(sock >= 0);assert(!clientIp.empty());assert(clientPort >= 1024);// 9\r\n100 + 200\r\n 9\r\n112 / 200\r\nstd::string inbuffer;while (true){Request req;char buff[128];ssize_t s = read(sock, buff, sizeof(buff) - 1);if (s == 0){logMessage(NOTICE, "client[%s:%d] close sock, service done", clientIp.c_str(), clientPort);break;}else if (s < 0){logMessage(WARINING, "read client[%s:%d] error, errorcode: %d, errormessage: %s",clientIp.c_str(), clientPort, errno, strerror(errno));break;}// read successbuff[s] = 0;inbuffer += buff;std::cout << "inbuffer: " << inbuffer << std::endl;// 1. 检查inbuffer是不是已经具有了一个strPackageuint32_t packageLen = 0;std::string package = decode(inbuffer, &packageLen); // 去掉报头if (packageLen == 0)continue; // 无法提取一个完整的报文,继续努力读取吧std::cout << "package: " << package << std::endl;// 2. 已经获得一个完整的packageif (req.deserialize(package)) // 反序列化{req.debug();// 3. 处理逻辑, 输入的是一个req,得到一个respResponse resp = calculator(req); // resp是一个结构化的数据 // 4. 对resp进行序列化std::string respPackage; resp.serialize(&respPackage);// 5. 对报文进行encode --respPackage = encode(respPackage, respPackage.size());// 6. 简单进行发送 -- 后续处理write(sock, respPackage.c_str(), respPackage.size()); // 把结果写回去}}
}
下面我们说一下我们不用这么麻烦,关于序列化和反序列化已经有人帮助我们完成好了,这里推荐使用jsoncpp,注意这里我们只需要知道有这个工具就可以了,具体的等到我们的项目和大家分析.
#include <iostream>
#include <string>
#include <cassert>
#include <jsoncpp/json/json.h>
#include "util.hpp"// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算器#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF) // 坑:sizeof(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)#define OPS "+-*/%"
//#define MY_SELF 1// decode,整个序列化之后的字符串进行提取长度
// 1. 必须具有完整的长度
// 2. 必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则,我们就是一个检测函数!
// 9\r\n100 + 200\r\n 9\r\n112 / 200\r\n
std::string decode(std::string &in, uint32_t *len)
{assert(len);// 1. 确认是否是一个包含len的有效字符串*len = 0;std::size_t pos = in.find(CRLF);if (pos == std::string::npos)return ""; // 1234\r\nYYYYY for(int i = 3; i < 9 ;i++) [)// 2. 提取长度std::string inLen = in.substr(0, pos);int intLen = atoi(inLen.c_str());// 3. 确认有效载荷也是符合要求的int surplus = in.size() - 2 * CRLF_LEN - pos;if (surplus < intLen)return "";// 4. 确认有完整的报文结构std::string package = in.substr(pos + CRLF_LEN, intLen);*len = intLen;// 5. 将当前报文完整的从in中全部移除掉int removeLen = inLen.size() + package.size() + 2 * CRLF_LEN;in.erase(0, removeLen);// 6. 正常返回return package;
}// encode, 整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{// "exitCode_ result_"// "len\r\n""exitCode_ result_\r\n"std::string encodein = std::to_string(len);encodein += CRLF;encodein += in;encodein += CRLF;return encodein;
}// 定制的请求 x_ op y_
class Request
{
public:Request(){}~Request(){}// 序列化 -- 结构化的数据 -> 字符串// 认为结构化字段中的内容已经被填充void serialize(std::string *out){
#ifdef MY_SELFstd::string xstr = std::to_string(x_);std::string ystr = std::to_string(y_);// std::string opstr = std::to_string(op_); // op_ -> char -> int -> 43 ->*out = xstr;*out += SPACE;*out += op_;*out += SPACE;*out += ystr;
#else// json// 1. Value对象,万能对象// 2. json是基于KV// 3. json有两套操作方法// 4. 序列化的时候,会将所有的数据内容,转换成为字符串Json::Value root;root["x"] = x_;root["y"] = y_;root["op"] = op_;Json::FastWriter fw;// Json::StyledWriter fw;*out = fw.write(root);
#endif}// 反序列化 -- 字符串 -> 结构化的数据bool deserialize(std::string &in){
#ifdef MY_SELF// 100 + 200std::size_t spaceOne = in.find(SPACE);if (std::string::npos == spaceOne)return false;std::size_t spaceTwo = in.rfind(SPACE);if (std::string::npos == spaceTwo)return false;std::string dataOne = in.substr(0, spaceOne);std::string dataTwo = in.substr(spaceTwo + SPACE_LEN);std::string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN));if (oper.size() != 1)return false;// 转成内部成员x_ = atoi(dataOne.c_str());y_ = atoi(dataTwo.c_str());op_ = oper[0];return true;
#else// jsonJson::Value root;Json::Reader rd;rd.parse(in, root);x_ = root["x"].asInt();y_ = root["y"].asInt();op_ = root["op"].asInt();return true;
#endif}void debug(){std::cout << "#################################" << std::endl;std::cout << "x_: " << x_ << std::endl;std::cout << "op_: " << op_ << std::endl;std::cout << "y_: " << y_ << std::endl;std::cout << "#################################" << std::endl;}public:// 需要计算的数据int x_;int y_;// 需要进行的计算种类char op_; // + - * / %
};// 定制的响应
class Response
{
public:Response() : exitCode_(0), result_(0){}~Response(){}// 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的!void serialize(std::string *out){
#ifdef MY_SELF// "exitCode_ result_"std::string ec = std::to_string(exitCode_);std::string res = std::to_string(result_);*out = ec;*out += SPACE;*out += res;
#else// jsonJson::Value root;root["exitcode"] = exitCode_;root["result"] = result_;Json::FastWriter fw;// Json::StyledWriter fw;*out = fw.write(root);
#endif}// 反序列化bool deserialize(std::string &in){
#ifdef MY_SELF// "0 100"std::size_t pos = in.find(SPACE);if (std::string::npos == pos)return false;std::string codestr = in.substr(0, pos);std::string reststr = in.substr(pos + SPACE_LEN);// 将反序列化的结果写入到内部成员中,形成结构化数据exitCode_ = atoi(codestr.c_str());result_ = atoi(reststr.c_str());return true;
#else// jsonJson::Value root;Json::Reader rd;rd.parse(in, root);exitCode_ = root["exitcode"].asInt();result_ = root["result"].asInt();return true;
#endif}void debug(){std::cout << "#################################" << std::endl;std::cout << "exitCode_: " << exitCode_ << std::endl;std::cout << "result_: " << result_ << std::endl;std::cout << "#################################" << std::endl;}public:// 退出状态,0标识运算结果合法,非0标识运行结果是非法的,!0是几就表示是什么原因错了!int exitCode_;// 运算结果int result_;
};
应用层
上面我们写了协议吗?写了,我们在有效数据上面加了长度,这就是我们我们协议.你会发现,我们自己写是很麻烦的,有没有非常成熟的场景,这让程序员自定义协议供所有人接受,也就是一套标准,也就是应用层的标准,特定协议的标准.是存在的.下面我们简绍两类协议,当然也存在其他的标准.
- HTTP 明文传输,默认端口是80端口
- HTTPS 加密传输,默认端口是443端口
HTTP协议
首先我们先说明一下HTTP协议我们现在不太常用了,主流的是HTTPS协议,不过之前HTTP使用太广泛了,直到现在仍旧存在HTTP协议的服务.,08年我国是属于草莽阶段,很多公司HTTP用的多,现在一般HTTPS用到多,HTTP(HyperText Transfer Protocol,超⽂本传输协议)它是⽆连接, ⽆状态, ⼯作在应⽤层的协议.
- 无连接理解为: HTTP协议本身是没有维护连接信息的, HTTP的数据会交给⽹络协议栈传输层的TCP协议, ⽽TCP是⾯向连接的.
- 无状态: HTTP 协议⾃身不对请求和响应之间的通信状态进⾏保存.也就是说在 HTTP 这个级别,协议对于发送过的请求或响应都不做持久化处理.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Qr2t2VG-1679454666162)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230320211742939.png)]
URL认识
平时我们俗称的 “网址” 其实就是说的 URL
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iheBrJri-1679454666164)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230320211131231.png)]
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RHqfwJto-1679454666164)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230307212503084.png)]
HTTP协议格式
HTTP 协议规定,请求从客户端发出,最后服务器端响应该请求并返回.换句话说,肯定是先从客户端开始建⽴通信的,服务器端在没有接收到请求之前不会发送响应.
下面我简单的说一下HTTP协议的格式,这里要说一下一般在书中,他们会说他们分为三部分,那里是不错的,只不过我们这里按照四部分来分析,主要是让大家更好的理解.
请求报文,
- 首行 : [ 方法 ] + [url] + [ 版本 ]
- Header: 请求的属性 , 冒号分割的键值对 ; 每组属性之间使用 \n 分隔 ; 遇到空行表示 Header 部分结束
- 这是一个换行
- Body: 空行后面的内容都是 Body. Body 允许为空字符串 . 如果 Body 存在 , 则在 Header 中会有一个Content-Length属性来标识 Body 的长度
响应报文
- 首行 : [ 版本号 ] + [ 状态码 ] + [ 状态码解释 ]
- Header: 请求的属性 , 冒号分割的键值对 ; 每组属性之间使用 \n 分隔 ; 遇到空行表示 Header 部分结束
- 这是一个换行
- Body: 空行后面的内容都是 Body. Body 允许为空字符串 . 如果 Body 存在 , 则在 Header 中会有一个Content-Length属性来标识 Body 的长度 ; 如果服务器返回了一个 html 页面 , 那么 html 页面内容就是在body中 .
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kPEFNVNK-1679454666165)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230320213807927.png)]
这里有两个问题,我们是如何找报头和有效载荷的,这是我们换行的作用,我们以他为分割线.那么我们如何知道有效载荷的长度的?这里如果我们存在有效载荷,那么Header中将会存在一个属性保存我们有效载荷的长度.
你说了那么多,我们是不是应该看看是不是呢?这里先来看我们的请求的格式究竟是不是?这里先来看别人的截图,后面我们自己写一个来让大家看一看.
这里我们实现一下,主要是让大家看一下
void handlerHTTPRequest(int sock)
{char buffer[10240];ssize_t s = read(sock, buffer, sizeof buffer);if(s > 0) cout << buffer;
}class ServerTcp
{
public:ServerTcp(uint16_t port, const std::string &ip = ""): port_(port),ip_(ip),listenSock_(-1){quit_ = false;}~ServerTcp(){if (listenSock_ >= 0)close(listenSock_);}public:void init(){// 1. 创建socketlistenSock_ = socket(PF_INET, SOCK_STREAM, 0);if (listenSock_ < 0){exit(1);}// 2. bind绑定// 2.1 填充服务器信息struct sockaddr_in local; // 用户栈memset(&local, 0, sizeof local);local.sin_family = PF_INET;local.sin_port = htons(port_);ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));// 2.2 本地socket信息,写入sock_对应的内核区域if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0){exit(2);}// 3. 监听socket,为何要监听呢?tcp是面向连接的!if (listen(listenSock_, 5 /*后面再说*/) < 0){exit(3);}// 运行别人来连接你了}void loop(){signal(SIGCHLD, SIG_IGN); // only Linuxwhile (!quit_){struct sockaddr_in peer;socklen_t len = sizeof(peer);int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);if (quit_)break;if (serviceSock < 0){// 获取链接失败cerr << "accept error ...." << endl;continue;}// 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的pid_t id = fork();assert(id != -1);if(id == 0){close(listenSock_); //建议if(fork() > 0) exit(0);//孙子进程handlerHTTPRequest(serviceSock);exit(0); // 进入僵尸}close(serviceSock);wait(nullptr);}}bool quitServer(){quit_ = true;return true;}private:// sockint listenSock_;// portuint16_t port_;// ipstd::string ip_;// 安全退出bool quit_;
};
HTTP常见Header
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能
HTTP请求方法
现在我们已经知道了我们HTTP协议的格式,这里我们要说一下请求方法.先说明一下,我们与计算机经行交互,那么我们要做的莫过于下面的两件事情.
- 向网络中发送数据
- 从网络中读取资源
下面是网络中为了支持不同的操作给我们提供的方法,我们重点看下面的两个.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rRLW1tKt-1679454666169)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/qkj/202303211046046.png)]
GET
GET方法会把我们的请求以明文方式将我们的参数信息拼接到我们url中,下面是我们的前端代码
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>104 期测试</title>
</head><body><h3>hello my server!</h3><p>我终于测试完了我的代码</p><form action="/a/b/c.html" 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
下面我们说一下POST传参,这个方法会把我们的参数以明文的方式放在正文中.
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>104 期测试</title>
</head><body><h3>hello my server!</h3><p>我终于测试完了我的代码</p><form action="/a/b/c.html" method="post">Username: <input type="text" name="user"><br>Password: <input type="password" name="passwd"><br><input type="submit" value="Submit"></form>
</body></html>
这里我们需要对比一下我们的两个方法,注意,我们这里谈的私密不是安全,他们都不安全,要知道我们的参数 以明文的方式在裸奔,如果有一个中间人拿到了我们请求报文,那么无论是POST还是GET方法,我们参数都可以被拿到.[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PnrvvU6l-1679454666171)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230209155448643.png)]
这里我还想问大家一个问题,为何我们在Window环境中下载软件会默认是适配Windows版本的,在手机上是手机版本的?这是我们的请求报文中会携带我们硬件的基本信息.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-38SmOwcE-1679454666172)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230209133604086.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HV2zRybb-1679454666173)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230209134811664.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jx4DSaL1-1679454666173)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230209134834052.png)]
HTTP状态码
首先我们要说的所谓的状态码就可以理解为一个哈希表,我们用特定的数字来代表我们不同相应的含义,仅此而已.可是各大厂商对他的支持标准支持的不是很好,我们了解就行了.
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
临时重定向
302是临时重定向,临时重定向就是如果你想要升级一下你的软件,暂时把你的服务迁移到另外的一个地方,可是你有很多的用户,你不想让这些用户记住你的新的额IP地址,这里我们就可以用一个临时重定向.
void handlerHTTPRequest(int sock)
{std::string response;response = "HTTP/1.0 302 Temporarily Moved\r\n";response += "Location: HTTPS://www.qq.com\r\n";response += "\r\n";send(sock, response.c_str(), response.size(), 0);
}
永久重定向
301是永久重定向,如果你想把你的功能完全迁移到另外一个IP地址,此时我们可以用永久重定向,注意我们的临时和永久这里不做区别,具体看要求吧.
void handlerHTTPRequest(int sock)
{std::string response;response = "HTTP/1.0 301 Temporarily Moved\r\n";response += "Location: HTTPS://www.qq.com\r\n";response += "\r\n";send(sock, response.c_str(), response.size(), 0);
}
无状态
上面我们说HTTP是没有状态的,我们要求啥资源,HTTP就会给我们提供什么资源,不做记录,
Cookie
可是我们看到的好像不对啊,一般我们登录网站只需要一次密码,后面就可以直接登录了,这不就是把我们的状态记录了吗?首先我们先说明HTTP确实是没有状态的,但是浏览器会帮助我们做记录,这就是涉及到cookie机制.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HKdj1kbu-1679454666175)(HTTPS://qkj0302.oss-cn-beijing.aliyuncs.com/image-20230209194308864.png)]
浏览器在我们第一次访问一个网站时会把我们信息保存下来形成一个文件,等待下一次我们访问这个网站的时候浏览器会自动拿着这个文件去访问.
session
首先我想说上面我们cookie策略非常好,但是由于我们的HTTP是明文传送的,也就是我们的cookie文件也是明文的,里面我们的密码和账户都有可能被中间人拿到,这样我们的账户就被盗取了,因此我们cookie不安全,所以我们主流的浏览器现在采用的是cookie+session.这个策略很好理解,第一次我们登录一个网站时,服务端会在在自己的本地形成一个session文件,该文件的文件名是唯一的,同时会给浏览器返回一个session_id,我们浏览器的cookie文件中保存的就是这个id等到下一次我们访问同一个网站的时候这个就去拿到id去和服务端进行查找.
注意,上面的策略也有问题,中间人是可以拿到我们的id,但是就算是中间人拿到我们id,他只能登录我们的账号,不能修该我们的密码.注意cookice文件里信息里面不仅仅id,例如登录地址,我们会遇到如果我们换一个地区进行登录,一般会要求我们进行信心认证.
无连接的
再说一次,我们的HTTP是无连接.这里不久矛盾了吗?下面你还说是支持长连接,可能我没有表述好.说一下如果你的亲戚有钱,是你有钱吗?不是,最多用一下人家的能钱.这里也是如此,我们认为HTTP和tcp毫无关系,tcp是有链接的,这里的HTTP只是用下tcp的能力,仅此而已.
短连接
注意HTTP1.0版本的协议是短连接的,也就是请求一下,回应一下.由于我们每一次建立和断开连接都需要三次握手和四次挥手,这里有很大的问题,在早期我们的计算机使用人数较少,资源也比较少,采用短连接是可以的,但是现在我们看懂的网页是非常大的,一个完整的网页可能背后是无数次的HTTP请求,所以短连接效率有点低.因此1.1版本更新了,如果双发都同意长连接方案,那么就是长链接,其中我们header中存在一个Connection: keep-alive,如果我们不支持长连接,那么就是Connection: close.
HTTPS协议
下面我们继续往下说,我们的HTTP协议下的数据是明文传送的,也就是我们的数据在裸奔.请问对于我们一些无关紧要的数据明文还可以接受,那么对于一些机密文件我们绝对不能以明文的方式传送,那么我们就需要加密.所谓的加密我想大家都可以很好的理解,其中HTTPS协议就是帮助我们加密的.
HTTPS 也是⼀个应⽤层协议. 是在 HTTP 协议的基础上引⼊了⼀个加密层.HTTP 协议内容都是按照⽂本的⽅式明⽂传输的. 这就导致在传输过程中出现⼀些被篡改的情况
加密
我们说一下加密和解密的概念
- 加密就是把 明⽂ (要传输的信息)进⾏⼀系列变换, ⽣成 密⽂
- 解密就是把 密⽂ 再进⾏⼀系列变换, 还原成 明⽂
在这个加密和解密的过程中, 往往需要⼀个或者多个中间的数据, 辅助进⾏这个过程, 这样的数据称为 密钥
中间人问题
上面我们我们一直说中间人问题?那么请问什么是中间人问题?因为HTTP的内容是明⽂传输的,明⽂数据会经过路由器、wifi热点、通信服务运营商、代理服务 器等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了.劫持者还可以篡改传 输的信息且不被双⽅察觉,这就是 中间⼈攻击 ,所以我们才需要对信息进⾏加密.
加密方式
虾米嗯我们说一下常见的加密方式
- 对称加密 速度快,效率高
- 非对称加密 慢 (相对的)
- 数字指纹 不是加密的方式
数据摘要
数字指纹(数据摘要),其基本原理是利⽤单向散列函数(Hash函数)对信息进⾏运算,⽣成⼀串固定⻓度的数字摘要.数字指纹并不是⼀种加密机制,但可以⽤来判断数据有没有被窜改.
- 摘要常见算法:有MD5、SHA1、SHA256、SHA512等,算法把无限的映射成有限,因此可能会有碰撞(两个不同的信息,算出的摘要相同,但是概率非常低)
- 摘要特征:和加密算法的区别是,摘要严格意义不是加密,因为没有解密,只不过从摘要很难反推原信息,通常用来进行数据对比
例如我们百度云盘在上传文件的时候,有可能出现妙传,这是由于我们百度网盘在传输文件的时候去先把我们文件进行数字摘要,看看生成的哈希值时候已经存在了,如果存在了就会把这个资源连接放在我们账户上面,这就是我们为何出现了秒传的情况.
对称加密
采⽤单钥密码系统的加密⽅法,同⼀个密钥可以同时⽤作信息的加密和解密,这种加密⽅法称为对称加密,也称为单密钥加密,特征:加密和解密所⽤的密钥是相同的.
- 常见对称加密算法(了解):DES、3DES、AES、TDEA、Blowfish、RC2等
- 特点:算法公开、计算量小、加密速度快、加密效率高
非对称加密
简单说就是有两把密钥,一把叫做公钥、一把叫私钥,用公钥加密的内容必须用私钥才能解开,同样,私钥加密的内容只有公钥能解开.
- 常见非对称加密算法(了解):RSA,DSA,ECDSA
- 特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,而使得加密解密速度没有对称加密解密的速度快
注意公钥和私钥是配对的,可以相互匹配使用
- 通过公钥对明文加密, 变成密文
- 通过私钥对密文解密, 变成明文
也可以反着用
- 通过私钥对明文加密, 变成密文
- 通过公钥对密文解密, 变成明文
HTTPS 的工作过程探究
现在我们已经知道了加密的方式,我们需要探究一下我们HTTPS采用的是什么加密的方式,这样有有助于我们理解HTTPS.
方案一 对称加密
如果通信双方都各自持有同一个密钥X,且没有别人知道,这两方的通信安全当然是可以被保证的(除非密钥被破解)
引入对称加密之后, 即使数据被截获, 由于黑客不知道密钥是啥, 因此就无法进行解密, 也就不知道请求的真实内容是啥了.但事情没这么简单. 服务器同一时刻其实是给很多客户端提供服务的. 这么多客户端, 每个人用的秘钥都必须是不同的(如果是相同那密钥就太容易扩散了, 黑客就也能拿到了). 因此服务器就需要维护每个客户端和每个密钥之间的关联关系, 这也是个很麻烦的事情.因此比较理想的做法, 就是能在客户端和服务器建立连接的时候, 双方协商确定这次的密钥是啥.那么这里就会出现问题,我们再协商密钥的时候是明文传送,此时中间人就会拿到我们密钥,我们数据就不安全了
方案二: 一方非对称加密
鉴于非对称加密的机制,如果服务器先把公钥以明文方式传输给浏览器,之后浏览器向服务器传数据前都先用这个公钥加密好再传,从客户端到服务器信道似乎是安全的(有安全问题),因为只有服务器有相应的私钥能解开公钥加密的数据.但是服务器到浏览器的这条路怎么保障安全?如果服务器用它的私钥加密数据传给浏览器,那么浏览器用公钥可以解密它,而这个公钥是一开始通过明文传输给浏览器的,若这个公钥被中间人劫持到了,那他也能用该公钥解密服务器传来的信息了.
方案三 双方非对称加密
注意我们加上一组非对称密钥就可以保证一条路的安全,例如如果我有两组公钥私钥,那我就可以保证双向数据传输的安全了呢?
某网站服务器拥有公钥A与对应的私钥A’;浏览器拥有公钥B与对应的私钥B’.浏览器把公钥B明文传输给服务器.
服务器把公钥A明文给传输浏览器.之后浏览器向服务器传输的内容都用公钥A加密,服务器收到后用私钥A’解密.由于只有服务器拥有私钥A’,所以能保证这条数据的安全.同理,服务器向浏览器传输的内容都用公钥B加密,浏览器收到后用私钥B’解密.同上也可以保证这条数据的安全.好像成功了!但是这个方法并没有被大范围推广,并且也不可能被大范围推广.很重要的原因是非对称加密算法非常耗时,而对称加密快很多.
方案四 对称+非对称加密
某网站拥有用于非对称加密的公钥A、私钥A’.浏览器向网站服务器请求,服务器把公钥A明文给传输浏览器.浏览器随机生成一个用于对称加密的密钥X,用公钥A加密后传给服务器.服务器拿到后用私钥A’解密得到密钥X.这样双方就都拥有密钥X了,且别人无法知道它.之后双方所有数据都通过密钥X加密解密即可.成功!HTTPS基本就是采用了这种方案.但是这并不是完美的,这仍然有漏洞喔!
中间人却完全不需要拿到私钥A’就能干坏事了,某网站有用于非对称加密的公钥A、私钥A’.浏览器向网站服务器请求,服务器把公钥A明文给传输浏览器.中间人劫持到公钥A,保存下来,把数据包中的公钥A替换成自己伪造的公钥B(它当然也拥有公钥B对应的私钥B’).浏览器生成一个用于对称加密的密钥X,用公钥B(浏览器以为是公钥A)加密后传给服务器.中间人劫持后用私钥B’解密得到密钥X,再用公钥A加密后传给服务器.服务器拿到后用私钥A’解密得到密钥X.这样在双方都不会发现异常的情况下,中间人通过一套“狸猫换太子”的操作,掉包了服务器传来的公钥,进而得到了密钥X.根本原因是浏览器无法确认收到的公钥是不是网站自己的,因为公钥本身是明文传输的.所以!我们只剩下最后一个问题,那就是怎么确保浏览器收到的公钥是网站的,而不是中间人的!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tOCu2iVJ-1679454666178)(https://qkj0302.oss-cn-beijing.aliyuncs.com/20230321_211421_3.gif)]
方案五 非对称加密 + 对称加密 + 证书认证
下面我们正式说一下HTTP采用的方式,这个是非常优秀的.
数字证书
下面我们需要引出数字证书,这个可以保证我们浏览器拿到的公钥是网站的,这里涉及到到数据摘要.
网站在使用HTTPS前,需要向CA机构申领一份数字证书,数字证书里含有证书持有者信息、公钥信息等.服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,证书就如身份证,证明“该公钥对应该网站”.只要我们的客户端拿到了证书,我们就可以判断公钥的合法性.
下面我说一下我们的流程服务端把公钥给CA机构,让后把私钥保存好,这样经过CA机构我们就得到了一个数字证书.
数字签名
下面我们继续,我们在浏览器访问服务端的时候我们把这个证书给浏览器就可以了,那么这里又有一个显而易见的问题,“证书本身的传输过程中,如何防止被篡改”?即如何证明证书本身的真实性?身份证运用了一些防伪技术,而数字证书怎么防伪呢?解决这个问题我们就真的接近胜利了!如何防止数字证书被篡改?
很简单,当服务端申请CA证书的时候,CA机构会对该服务端进行审核,并专门为该网站形成数字签名,过程如下:
- CA机构拥有非对称加密的私钥A和公钥A’
- CA机构对服务端申请的证书明文数据进行hash,形成数据摘要
- 然后对数据摘要用CA私钥A’加密,得到数字签名S,服务端申请的证书明文和数字签名S 共同组成了数字证书,这样一份数字证书就可以颁发给服务端了
在客户端和服务器刚一建立连接的时候, 服务器给客户端返回一个 证书,证书包含了之前服务端的公钥, 也包含了网站的身份信息
当客户端获取到这个证书之后, 会对证书进行校验(防止证书是伪造的).
- 判定证书的有效期是否过期
- 判定证书的发布机构是否受信任(操作系统中已内置的受信任的证书发布机构).
- 验证证书是否被篡改: 从系统中拿到该证书发布机构的公钥, 对签名解密, 得到一个 hash 值(称为数据摘要), 设为 hash1. 然后计算整个证书的 hash 值, 设为 hash2. 对比 hash1 和 hash2 是否相等. 如果相等, 则说明证书是没有被篡改过的.
下面我们说两个问题,补充我们理解.
中间人有没有可能篡改该证书?
由于他没有CA机构的私钥,所以⽆法hash之后⽤私钥加密形成签名,那么也就没法办法对篡改后的证书形成匹配的签名,如果强⾏篡改,客⼾端收到该证书后会发现明⽂和签名解密后的值不⼀致,则说明证书已被篡改,证书不可信,从⽽终⽌向服务器传输信息,防⽌信息泄露给中间⼈ .
那么中间人整个掉包证书呢?
因为中间⼈没有CA私钥,所以⽆法制作假的证书,所以中间⼈只能向CA申请真证书,然后⽤⾃⼰申请的证书进⾏掉包.这个确实能做到证书的整体掉包,但是别忘记,证书明⽂中包含了域名等服务端认证信息,如果整体掉包,客⼾端依旧能够识别出来.永远记住:中间⼈没有CA私钥,所以对任何证书都⽆法进⾏合法修改,包括⾃⼰的 .
如何成为中间人
- ARP欺骗:在局域网中,hacker经过收到ARP Request广播包,能够偷听到其它节点的 (IP, MAC)地址.例, 黑客收到两个主机A, B的地址,告诉B (受害者) ,自己是A,使得B在发送给A 的数据包都被黑客截取
- ICMP攻击:由于ICMP协议中有重定向的报文类型,那么我们就可以伪造一个ICMP信息然后发送给局域网中的客户端,并伪装自己是一个更好的路由通路.从而导致目标所有的上网流量都会发送到我们指定的接口上,达到和ARP欺骗同样的效果
- 假wifi && 假网站等
相关文章:

HTTP/HTTPS协议认识
写在前面 这个博客我们要要讨论的是协议,主要是应用层.今天我们将正式认识HTTP和HTTPS,也要认识序列化和反序列化,内容比较多,但是不难 再谈协议 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层,我们要完成下面三个步骤. sock的使用 定制…...

【VScode】远程连接Linux
目录标题1. 安装扩展插件2. 在Linux上操作3. 确定Linux的IP地址4. 远程连接到Linux5. 实现免密码登录使用 VScode 远程编程与调试的时有会用到插件 Remote Development,使用这个插件可以在很多情况下代替 vim 直接远程修改与调试服务器上的代码,同时具备…...

QT/C++调试技巧:内存泄漏检测
文章目录内存泄漏方案一方案二:CRT调试定位代码位置方法1方法2其它问题方案三:使用vs诊断工具方案四:使用工具VLD(Visio Leak Detector)方案五Cppcheck内存泄漏 内存泄漏:指的是在程序里动态申请的内存在使…...

【贪心算法】一文让你学会“贪心”(贪心算法详解及经典案例)
文章目录前言如何理解“贪心算法”?贪心算法实战分析1.分糖果2.钱币找零3.区间覆盖内容小结最后说一句🐱🐉作者简介:大家好,我是黑洞晓威,一名大二学生,希望和大家一起进步。 👿本…...

【字体图标iconfont】字体图标部署流程+项目源码分析
今日,心情甚是烦闷,原由… 公司项目需要将字体图标做一些细微的调整,我一人分析了许久,看不大懂源码的逻辑,产生了自我怀疑。深吸一口气,重新鼓起勇气,调整心境,一下子豁然开朗&…...

2023最全的Web自动化测试介绍(建议收藏)
做测试的同学们都了解,做Web自动化,我们主要用Selenium或者是QTP。 有的人可能就会说,我没这个Java基础,没有Selenium基础,能行吗?测试虽然属于计算机行业,但其实并不需要太深入的编程知识&…...

jvm_根节点枚举安全点安全区域
1、可达性分析可以分成两个阶段 根节点枚举 从根节点开始遍历对象图 前文我们在介绍垃圾收集算法的时候,简单提到过:标记-整理算法(Mark-Compact)中的移动存活对象操作是一种极为负重的操作,必须全程暂停用户应用程序才能进行,像这…...

fabric(token-erc-20链码部署)
确保自己已经安装了fabric。没有安装的可以参考我之前的教程fabric中bootstrap.sh到底帮助我们干了什么?(手动执行相关操作安装fabric2.4)_./bootstrap.sh_小小小小关同学的博客-CSDN博客小伙伴们在跟着官方示例来安装fabric的时候都是相当烦…...

C语言基础——流程控制语句
文章目录一、流程控制语句 -- 控制程序的运行过程 9条(一)、条件选择流程控制语句:if语句if……else……语句if……else if……语句switch语句(二)、循环流程控制语句:for语句while语句do while……语句co…...

WinForm | C# 界面弹出消息通知栏 (仿Win10系统通知栏)
ApeForms 弹出消息通知栏功能 文章目录ApeForms 弹出消息通知栏功能前言全局API通知栏起始方向通知排列方向通知栏之间的间隔距离无鼠标悬停时的不透明度消息通知窗体的默认大小示例代码文本消息提示栏文本消息提示栏(带选项)图文消息提示栏图文消息提示…...

刷题之最长公共/上升子序列问题
目录 一、最长公共子序列问题(LCS) 1、题目 2、题目解读 编辑 3、代码 四、多写一题 五、应用 二、最长上升子序列问题(LIS) 1、题目 2、题目解读 3、代码 四、多写一道 Ⅰ、题目解读 Ⅱ、代码 一、最长公共子序列问题&…...

【数据结构】千字深入浅出讲解栈(附原码 | 超详解)
🚀write in front🚀 📝个人主页:认真写博客的夏目浅石. 🎁欢迎各位→点赞👍 收藏⭐️ 留言📝 📣系列专栏:C语言实现数据结构 💬总结:希望你看完…...
自动驾驶V2X
1 SoC MDM9250 2 设备网络节点 mhi_swip0 rmnet_mhi0 3 网络协议栈log打印控制 include/linux/netdevice.h ethtool -s eth0 msglvl [level] ethtool -s eth0 msglvl 0x6001 4 URLs MHI initial design review https://lore.kernel.org/lkml/001601d52148$bd852840$388f78c0$c…...

零基础自学网络安全/渗透测试有哪些常见误区?
一、网络安全学习的误区 1.不要试图以编程为基础去学习网络安全 不要以编程为基础再开始学习网络安全,一般来说,学习编程不但学习周期长,且过渡到网络安全用到编程的用到的编程的关键点不多。一般人如果想要把编程学好再开始学习网络安全往…...

ConvMixer:Patches Are All You Need
Patches Are All You Need 发表时间:[Submitted on 24 Jan 2022]; 发表期刊/会议:Computer Vision and Pattern Recognition; 论文地址:https://arxiv.org/abs/2201.09792; 代码地址:https:…...
day10—编程题
文章目录1.第一题1.1题目1.2思路1.3解题2.第二题2.1题目2.2涉及的相关知识2.3思路2.4解题1.第一题 1.1题目 描述: 给定一个二维数组board,代表棋盘,其中元素为1的代表是当前玩家的棋子,0表示没有棋子,-1代表是对方玩…...

如何测量锂电池的电量
锂电池在放电时我们有时需要知道电池的实时电量,如电池电量低了我们就需要及时给锂电池充电,避免电池过度放电。我手里的这个就是个单节锂电池电量显示模块,只需要将这个模块接到锂电池的正负极即可显示电量。这个模块的电量分为四档…...

菜鸟刷题Day6
⭐作者:别动我的饭 ⭐专栏:菜鸟刷题 ⭐标语:悟已往之不谏,知来者之可追 一.链表内指定区间反转:链表内指定区间反转_牛客题霸_牛客网 (nowcoder.com) 描述 将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转…...

DecimalFormat格式化显示数字
DecimalFormat 是 NumberFormat 的一个具体子类,用于格式化十进制数字,可以实现以最快的速度将数字格式化为你需要的样子。 DecimalFormat 类主要靠 # 和 0 两种占位符号来指定数字长度。0 表示如果位数不足则以 0 填充, # 表示只要有可能就…...

cpu中缓存简介
一级缓存是什么: 一级缓存都内置在CPU内部并与CPU同速运行,可以有效的提高CPU的运行效率。一级缓存越大,CPU的运行效率越高,但受到CPU内部结构的限制,一级缓存的容量都很小。 CPU缓存(Cache Memory…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

企业如何增强终端安全?
在数字化转型加速的今天,企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机,到工厂里的物联网设备、智能传感器,这些终端构成了企业与外部世界连接的 “神经末梢”。然而,随着远程办公的常态化和设备接入的爆炸式…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

招商蛇口 | 执笔CID,启幕低密生活新境
作为中国城市生长的力量,招商蛇口以“美好生活承载者”为使命,深耕全球111座城市,以央企担当匠造时代理想人居。从深圳湾的开拓基因到西安高新CID的战略落子,招商蛇口始终与城市发展同频共振,以建筑诠释对土地与生活的…...