【Linux网络编程】应用层:HTTP协议 | URL | 简单实现一个HTTP服务器 | 永久重定向与临时重定向
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git
文章目录
- 前言
- HTTP协议
- URL
- urlencode 和 urldecode(了解)
- HTTP协议请求与响应格式
- 简单实现一个HTTP服务器
- HTTP请求及反序列化
- HTTP请求的基本框架
- 封装反序列化
- 获取每一行:Getline
- 进一步解析请求行
- 进一步解析请求报头
- 测试
- HTTP响应及序列化
- HTTP响应基本框架
- 封装序列化
- 使用telnet进行抓包
- 完整代码
- 测试
- 为什么只访问首页时但是依然有下面的图片
- 理解网站页面跳转
- HTTP 常见 Header
- 关于connection报头
- HTTP的状态码
- 永久重定向与临时重定向
- HTTP请求方法
- HTTP常见方法
- GET方法
- POST方法
- 完整HTTP代码
前言
实现一个简单的HTTP服务器点击链接获取源码
HTTP协议
应用层有很多协议,其中HTTP协议就是其中一个,HTTP协议(超文本传输协议)也是较为重要的一个协议。
在互联网世界中, HTTP(HyperText Transfer Protocol, 超文本传输协议) 是一个至关重要的协议。 它定义了客户端(如浏览器) 与服务器之间如何通信, 以交换或传输超文本(如 HTML 文档)。
HTTP 协议是客户端与服务器之间通信的基础。 客户端通过 HTTP 协议向服务器发送请求, 服务器收到请求后处理并返回响应。 HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。
URL
说到URL大家可能不熟悉,但是说到“网址”大家就一目了然,实际上,“网址”就是URL。
- 域名会自动转化成IP地址,称为DNS
- 协议名称和端口号是强关联的,在浏览器这个网址中将端口号默认忽略了,当浏览器发起请求时会自动拼接端口号80(指明端口)
HTTP做的工作:用户在网络上获取资源(图片、文本、视频等)时是在服务器端获取,所有的资源都是在服务器端。通过某种协议(如http协议或者https协议)来标识用户所需要的资源,然后返回给用户。
我们知道Linux适合做后端开发,那么这些服务器端的资源是在Linux操作系统中。而Linux操作系统一切皆文件,必须得找到对应的资源,需要通过路径来标识,因此在URL后半部分就是路径。
因此,URL就是统一资源定位符,域名+文件路径,标识互联网中唯一的文件资源。
urlencode 和 urldecode(了解)
像 / ? :
等这样的字符, 已经被 url
当做特殊意义理解了. 因此这些字符不能随意出现。比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。
转义的规则如下:
将需要转码的字符转为 16 进制, 然后从右到左, 取 4 位(不足 4 位直接处理), 每 2 位做一位, 前面加上%
, 编码成%XY
格式。
HTTP协议请求与响应格式
HTTP请求
在HTTP请求中,格式如下:
-
首行:称之为请求行,格式为 方法(POST、GET、HEAD…)+URI(即URL后半部分—>用户访问资源的路径)+版本号
-
HTTP的方法:
HTTP响应
在HTTP请求中,格式如下:
- 这里的DATA是用户需要的资源内容:网页,图片的二进制、视频的二进制、音频的二进制等
HTTP中如何将报头和有效载荷进行分离(封装)? 通过空行即\r\n
如何HTTP的请求和响应读到一个完整的报头?通过换行符可以知道读到一个完整的报文。
如果请求和响应中包含了正文(DATA),如何保证读到一个完整的报头?怎么知道正文部分的长度?在之前的网络版本计算器中只有一个字段,拥有有效载荷长度的字段。在HTTP的请求和应答中报头属性中有一个公共属性叫做Content-Length: xxx
,无论是请求还是响应,如果有正文部分,那么这个字段一定包含,这样就能读到一个完整的报文。
简单实现一个HTTP服务器
HTTP请求及反序列化
HTTP请求的基本框架
在HTTP请求中,需要基本的属性(请求行、请求报头、空行、请求正文)以及进一步显示出具体属性(使用的方法、使用的URL、使用的HTTP版本、请求报头以KV形式显示)
class HttpRequest
{
public:HttpRequest():_blank_line(base_sep) {}void Deserialize(std::string &reqstr){}~HttpRequest() {}
private:// 请求的基本格式std::string _req_line;std::vector<std::string> _req_handers;std::string _blank_line;std::string _body_text;//具体属性std::string _method;std::string _url;std::string _version;std::unordered_map<std::string,std::string> _headers_kv;
};
封装反序列化
在反序列化中:
- 首先需要通过
Getline
从reqstr
中获取请求行 - 获取请求报头:通过一个
do-while
循环来读取所有的请求头部信息。每次循环调用Getline(reqstr)
获取一行数据,并将它存储在header
中 - 获取请求正文
- 进一步解析请求行和请求头
void Deserialize(std::string &reqstr)
{// 基本的反序列化_req_line=Getline(reqstr);std::string header;do{header=Getline(reqstr);if(header.empty()) break;else if(header==base_sep) break;_req_handers.push_back(header);}while(true);if(!reqstr.empty())_body_text=reqstr;//进一步反序列化ParseReqLine();ParseReqHeader();
}
获取每一行:Getline
寻找base_sep
的位置,如果找不到说明没有分隔符,是一个空串;如果找到了base_sep
就分割从0
到base_sep
之间的字符串。然后更新reqstr
,即删除已经提取的部分。如果 line
是空字符串(即没有有效的行内容),则返回 base_sep
,表示一个空行或分隔符。否则,返回提取的有效行内容 line
。
const static std::string base_sep="\r\n";std::string Getline(std::string &reqstr)
{auto pos=reqstr.find(base_sep);if(pos==std::string::npos) return std::string();std::string line=reqstr.substr(0,pos);reqstr.erase(0,line.size()+base_sep.size());return line.empty()?base_sep:line;
}
进一步解析请求行
解析一个请求行,并将解析的结果存储到类的成员变量 _method
、_url
和 _version
中。
代码通过 std::stringstream
提供的流提取操作符 (>>
) 来从 ss 中依次提取数据并赋值给 _method
、_url
和 _version
。
- 第一个
>> _method
:将从流中提取的第一个单词(如 GET)赋值给_method
。通常情况下,_method 代表 HTTP 请求的方法(例如 GET、POST 等)。 - 第二个
>> _url
:提取流中的第二个单词(如 /index.html),并将其赋值给_url
。这个字符串通常代表请求的 URL 路径。 - 第三个 >>
_version
:提取流中的第三个单词(如 HTTP/1.1),并将其赋值给_version
。这个字符串代表请求的 HTTP 版本。
void ParseReqLine()
{std::stringstream ss(_req_line);ss>>_method>>_url>>_version;
}
进一步解析请求报头
未解析的解析报头是以:
分割的,因此这里使用:
作为分隔符。
将每个请求头的键值对存储到 _headers_kv
容器中。它通过遍历 _req_handers
,提取出每个请求头的键和值,并将它们插入到一个键值对容器中。
const static std::string line_sep=": ";void ParseReqHeader(){for(auto &header:_req_handers){auto pos=header.find(line_sep);if(pos==std::string::npos) continue;std::string k=header.substr(0,pos); // [)std::string v=header.substr(pos+line_sep.size());if(k.empty()||v.empty()) continue;_headers_kv.insert(std::make_pair(k,v));}}
测试
HTTP响应及序列化
HTTP响应基本框架
class HttpResponse
{
public:HttpResponse():_version(httpversion),_blank_line(base_sep){}void AddCode(int code){_status_code=code;_desc="OK";}void AddHeader(const std::string &k,const std::string &v){_headers_kv[k]=v;}void AddBodyText(const std::string body_text){_resp_body_text=body_text;}std::string Serialize(){}~HttpResponse(){}private:// 基本属性std::string _version;int _status_code;std::string _desc;std::unordered_map<std::string,std::string> _headers_kv;// 响应的基本格式std::string _status_line;std::vector<std::string> _resp_handers;std::string _blank_line;std::string _resp_body_text;
};
封装序列化
在HTTP响应序列化中:
- 构建状态行,并将其存储在
_status_line
字符串中 - 构建响应头,遍历
_headers_kv
容器,它是一个存储 HTTP 响应头的键值对容器。每一行头部字符串会被push_back
到_resp_handers
容器中,最终该容器保存了所有响应头的字符串 - 正式序列化,将之前构建的各部分拼接成最终的响应报文字符串
responsestr
std::string Serialize()
{// 构建状态行_status_line=_version+spacesep+std::to_string(_status_code)+spacesep+_desc+base_sep;// 构建应答报头for(auto &hander:_headers_kv){std::string header_line=hander.first+line_sep+hander.second+base_sep;_resp_handers.push_back(header_line);}// 空行和正文// 空行已经初始化,正文已经保存在_resp_body_text中// 正式序列化std::string responsestr=_status_line;for(auto &line:_resp_handers){responsestr+=line;}responsestr+=_blank_line;responsestr+=_resp_body_text;return responsestr;
}
使用telnet进行抓包
telnet 127.0.0.1 8888
通过这个测试,得到响应
完整代码
class HttpResponse
{
public:HttpResponse():_version(httpversion),_blank_line(base_sep){}void AddCode(int code){_status_code=code;_desc="OK";}void AddHeader(const std::string &k,const std::string &v){_headers_kv[k]=v;}void AddBodyText(const std::string body_text){_resp_body_text=body_text;}std::string Serialize(){// 构建状态行_status_line=_version+spacesep+std::to_string(_status_code)+spacesep+_desc+base_sep;// 构建应答报头for(auto &hander:_headers_kv){std::string header_line=hander.first+line_sep+hander.second+base_sep;_resp_handers.push_back(header_line);}// 空行和正文// 空行已经初始化,正文已经保存在_resp_body_text中// 正式序列化std::string responsestr=_status_line;for(auto &line:_resp_handers){responsestr+=line;}responsestr+=_blank_line;responsestr+=_resp_body_text;return responsestr;}~HttpResponse(){}private:// 基本属性std::string _version;int _status_code;std::string _desc;std::unordered_map<std::string,std::string> _headers_kv;// 响应的基本格式std::string _status_line;std::vector<std::string> _resp_handers;std::string _blank_line;std::string _resp_body_text;
};
html
<!DOCTYPE html>
<html>
<head><title>南桥几晴秋(gwj.cn)</title> <meta charset="UTF-8">
</head>
<body><div id="container" style="width:800px"><div id="header" style="background-color:#FFA500;"><h1 style="margin-bottom:0;">南桥几晴秋</h1></div><div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;"><b>Menu</b><br>HTML<br>CSS<br>JavaScript</div><div id="content" style="background-color:#EEEEEE;height:200px;width:700px;float:left;">这是一个测试</div><div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">Copyright © gwj.com</div></div><div><img src="/image/1.jpg" alt="一张图片"></div>
</body>
</html>
测试
在浏览器中输入:http://119.3.220.34:8888/
,得到如下界面:
为什么只访问首页时但是依然有下面的图片
获得一个完整的网页,浏览器要先得到html,根据html的标签检测出我们还要获取的其他资源,浏览器会继续发起HTTP请求。
理解网站页面跳转
我们在使用网站时,点击某个按钮进行跳转,实际上是在访问wwwroot
中的文件
在我自己的HTTP服务器中显示如下:
在现代Web开发中,HTML文件的内容通常属于前端部分,而通过后端调用和渲染这些内容是常见的架构设计。
HTTP 常见 Header
- Content-Type: 数据类型(text/html 等)
- Content-Length: Body 的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的
- Location: 搭配 3xx 状态码使用, 告诉客户端接下来要去哪里访问
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
关于connection报头
HTTP
中的 Connection
字段是 HTTP
报文头的一部分, 它主要用于控制和管理客户端与服务器之间的连接状态。
- 管理持久连接: Connection 字段还用于管理持久连接(也称为长连接) 。 持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接, 以便在同一个连接上发送多个请求和接收多个响应。
- HTTP/1.1: 在
HTTP/1.1
协议中, 默认使用持久连接。 当客户端和服务器都不明确指定关闭连接时, 连接将保持打开状态, 以便后续的请求和响应可以复用同一个连接。 - HTTP/1.0: 在 HTTP/1.0 协议中, 默认连接是非持久的。 如果希望在
HTTP/1.0
上实现持久连接, 需要在请求头中显式设置Connection: keep-alive
语法格式
• Connection: keep-alive
: 表示希望保持连接以复用 TCP 连接。
• Connection: close
: 表示请求/响应完成后, 应该关闭 TCP 连接。
HTTP的状态码
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定
向), 504(Bad Gateway)
永久重定向与临时重定向
什么叫做重定向?
用户浏览器想目标服务器发起请求,但是目标服务器想用户浏览器推送了一个服务器,此时用户浏览器向推送的服务器进行请求,重定向的地址在location
字段中。
所谓的永久与临时无非就是字面意思,永久即下次再请求时直接去新的服务器,会修改客户的地址,临时不对客户做任何影响。
永久重定向:当你决定将一个页面永久迁移到新地址时,使用永久重定向是正确的做法。例如,网站改版、页面URL结构优化或域名更换时。
临时重定向:当你需要在短期内将页面临时指向另一个URL时使用,比如进行系统维护、A/B测试或者临时更新页面内容时。
if(req.Path()=="wwwroot/redir")
{std::string reire_path="https://www.qq.com";resp.AddCode(302,_code_to_desc[302]);resp.AddHeader("Location",reire_path);
}
HTTP请求方法
HTTP常见方法
GET方法
- 用途:用于请求URL指定的资源
- 特性:指定资源经服务器端解析后返回响应内容
GET一般用来获取静态资源,也可以通过URL向服务器传递参数
std::string Method()
{LOG(DEBUG,"Client request method is %s\n",_method.c_str());return _method;
}
POST方法
- 用途:用于传输实体的主体, 通常用于提交表单数据
- 特性:可以发送大量的数据给服务器, 并且数据包含在请求体中
POST方法可以通过httprequest的正文来进行参数传递
POST方法传递参数比GET方法更私密,登录信息不回显示在浏览器中。但是都不安全。要想保证安全必须对http的参数部分进行加密。
完整HTTP代码
#pragma once#include<iostream>
#include<string>
#include<vector>
#include<functional>
#include<sstream>
#include<fstream>
#include<unordered_map>const static std::string base_sep="\r\n";
const static std::string line_sep=": ";
const static std::string prefixpath="wwwroot"; // web根目录
const static std::string homepage="index.html"; // web根目录
const static std::string httpversion="HTTP/gwj_1.0";
const static std::string spacesep=" ";
const static std::string suffixsep=".";
const static std::string html_404="404.html";
const static std::string arg_sep="?";class HttpRequest
{
private:std::string Getline(std::string &reqstr){auto pos=reqstr.find(base_sep);if(pos==std::string::npos) return std::string();std::string line=reqstr.substr(0,pos);reqstr.erase(0,line.size()+base_sep.size());return line.empty()?base_sep:line;}void ParseReqLine(){std::stringstream ss(_req_line);ss>>_method>>_url>>_version;if(strcasecmp(_method.c_str(),"GET")==0){auto pos=_url.find(arg_sep);if(pos!=std::string::npos){_body_text=_url.substr(pos+arg_sep.size());_url.resize(pos); // 只保留了?前面的字符}}_path+=_url;if(_path[_path.size()-1]=='/'){_path+=homepage;}auto pos=_path.rfind(suffixsep);if(pos!=std::string::npos){_suffix=_path.substr(pos);}else{_suffix=".default";}}void ParseReqHeader(){for(auto &header:_req_handers){auto pos=header.find(line_sep);if(pos==std::string::npos) continue;std::string k=header.substr(0,pos); // [)std::string v=header.substr(pos+line_sep.size());if(k.empty()||v.empty()) continue;_headers_kv.insert(std::make_pair(k,v));}}public:HttpRequest():_blank_line(base_sep),_path(prefixpath){}void Deserialize(std::string &reqstr){// 基本的反序列化_req_line=Getline(reqstr);std::string header;do{header=Getline(reqstr);if(header.empty()) break;else if(header==base_sep) break;_req_handers.push_back(header);}while(true);if(!reqstr.empty())_body_text=reqstr;//进一步反序列化ParseReqLine();ParseReqHeader();}std::string Url(){LOG(DEBUG,"Client Want path %s\n",_path.c_str());return _url;}std::string Path(){LOG(DEBUG,"Client Want %s\n",_url.c_str());return _path;}std::string Suffix(){return _suffix;}std::string Method(){LOG(DEBUG,"Client request method is %s\n",_method.c_str());return _method;}std::string GetRequestBody(){LOG(DEBUG,"Client request method is %s, args: %s, request path: %s\n",_method.c_str(),_body_text.c_str(),_path.c_str());return _body_text;}void Print(){std::cout<<"-----------------------------------------------"<<std::endl;std::cout<<"###"<<_req_line<<std::endl;for(auto &header:_req_handers){std::cout<<"@@@"<<header<<std::endl;}std::cout<<"***"<<_blank_line;std::cout<<">>>"<<_body_text<<std::endl;std::cout<<"Method: "<<_method<<std::endl;std::cout<<"URL: "<<_url<<std::endl;std::cout<<"Version: "<<_version<<std::endl;for(auto header_kv:_headers_kv){std::cout<<"< "<<header_kv.first<<" > : < "<<header_kv.second<<" >"<<std::endl;}}~HttpRequest() {}
private:// 请求的基本格式std::string _req_line;std::vector<std::string> _req_handers;std::string _blank_line;std::string _body_text;//具体属性std::string _method;std::string _url;std::string _path;std::string _suffix; // 资源后缀std::string _version;std::unordered_map<std::string,std::string> _headers_kv;
};class HttpResponse
{
public:HttpResponse():_version(httpversion),_blank_line(base_sep){}void AddCode(int code,const std::string &desc){_status_code=code;_desc=desc;}void AddHeader(const std::string &k,const std::string &v){_headers_kv[k]=v;}void AddBodyText(const std::string body_text){_resp_body_text=body_text;}std::string Serialize(){// 构建状态行_status_line=_version+spacesep+std::to_string(_status_code)+spacesep+_desc+base_sep;// 构建应答报头for(auto &hander:_headers_kv){std::string header_line=hander.first+line_sep+hander.second+base_sep;_resp_handers.push_back(header_line);}// 空行和正文// 空行已经初始化,正文已经保存在_resp_body_text中// 正式序列化std::string responsestr=_status_line;for(auto &line:_resp_handers){responsestr+=line;}responsestr+=_blank_line;responsestr+=_resp_body_text;return responsestr;}~HttpResponse(){}private:// 基本属性std::string _version;int _status_code;std::string _desc;std::unordered_map<std::string,std::string> _headers_kv;// 响应的基本格式std::string _status_line;std::vector<std::string> _resp_handers;std::string _blank_line;std::string _resp_body_text;
};using func_t=std::function<HttpResponse(HttpRequest&)>;class HttpServer
{
private:std::string GetFileContent(const std::string path){std::ifstream in(path,std::ios::binary);if(!in.is_open()) return std::string();in.seekg(0,in.end);int filesize=in.tellg(); //读写偏移量,计算文件大小in.seekg(0,in.beg);std::string content;content.resize(filesize);in.read((char*)content.c_str(),filesize);in.close();return content;}public:HttpServer(){_mime_type.insert(std::make_pair(".html","text/html"));_mime_type.insert(std::make_pair(".jpg","image/jpeg"));_mime_type.insert(std::make_pair(".png","image/png"));_mime_type.insert(std::make_pair(".default","text/html"));_code_to_desc.insert(std::make_pair(100,"Continue"));_code_to_desc.insert(std::make_pair(200,"OK"));_code_to_desc.insert(std::make_pair(201,"Created"));_code_to_desc.insert(std::make_pair(301,"Moved Permanently"));_code_to_desc.insert(std::make_pair(302,"Found"));}
// #define TESTstd::string HandlerHttpRequest(std::string &reqstr) //reqstr被客户序列化过{
#ifdef TESTstd::cout<<"------------------------------------------------------"<<std::endl;std::cout<<reqstr;std::string responsestr = "HTTP/1.1 200 OK\r\n";responsestr += "Content-Type: text/html\r\n";responsestr += "\r\n";responsestr += "<html><h1>hello Linux, hello gwj!</h1></html>";return responsestr;#elsestd::cout << "---------------------------------------" << std::endl;std::cout << reqstr;std::cout << "---------------------------------------" << std::endl;HttpRequest req;HttpResponse resp; req.Deserialize(reqstr);// req.Method();if(req.Path()=="wwwroot/redir"){std::string reire_path="https://www.qq.com";resp.AddCode(302,_code_to_desc[302]);resp.AddHeader("Location",reire_path);}else if (!req.GetRequestBody().empty()){if(IsServiceExists(req.Path())){resp=_service_lists[req.Path()](req);}}else{std::string content = GetFileContent(req.Path());if (content.empty()){content = GetFileContent("wwwroot/404.html");resp.AddCode(404, _code_to_desc[404]);resp.AddHeader("Content-Length", std::to_string(content.size()));resp.AddHeader("Content-Type", _mime_type[".html"]);resp.AddBodyText(content);}else{resp.AddCode(200, _code_to_desc[200]);resp.AddHeader("Content-Length", std::to_string(content.size()));resp.AddHeader("Content-Type", _mime_type[req.Suffix()]);resp.AddBodyText(content);}}return resp.Serialize();#endif}void InsertService(const std::string servicename,func_t f){std::string s=prefixpath+servicename;_service_lists[s]=f;}bool IsServiceExists(const std::string &servicename){auto iter=_service_lists.find(servicename);if(iter==_service_lists.end()) return false;else return true;}~HttpServer() {}private:std::unordered_map<std::string,std::string> _mime_type;std::unordered_map<int,std::string> _code_to_desc;std::unordered_map<std::string,func_t> _service_lists;
};
相关文章:

【Linux网络编程】应用层:HTTP协议 | URL | 简单实现一个HTTP服务器 | 永久重定向与临时重定向
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站 🌈个人主页: 南桥几晴秋 🌈C专栏: 南桥谈C 🌈C语言专栏: C语言学习系…...

电压调整电路汇总
目录: 一、LDO 1、LM1117 2、NCV33275 3、TLE42764 4、TPS7B67xx-Q1 5、总结 二、DCDC转换器 1、LM2576与LM2596 2、MC34063 一、LDO 1、LM1117 LM1117 是一款在 800mA 负载电流下具有 1.2V 压降的低压降稳压器。 LM1117 提供可调节电压版本,…...

day28 文件IO及进程线程基础
讨论光标共享情况 1.dup和dup2定义变量赋值都共享光标 2.使用两个描述符调用两次open函数打开同一个文件,不共享光标 #include <myhead.h>int main(int argc, const char *argv[]) {//1、描述符赋值给新的变量char buff[1024] "abcdefg";int ne…...

【Azure 架构师学习笔记】- Azure Function (1) --环境搭建和背景介绍
本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Function 】系列。 前言 随着无服务计算的兴起和大数据环境中的数据集成需求, 需要使用某些轻量级的服务,来实现一些简单操作。因此Azure Function就成了微软云上的一个必不可少的组成部分。 …...
前端文件下载
这里写自定义目录标题 前端文件下载方法使用a标签使用iframe标签二进制流 前端文件下载方法 使用a标签 /*** 文件下载方法 使用a标签* 存在浏览器下载时,太快的话,会取消上次的下载请求* param {*} href* param {*} filename*/ export function downlo…...

前端成长之路:HTML(3)
在HTML中,有列表标签。列表最大的特点是整齐、简洁、有序,用列表进行布局会更加自由方便。根据使用的情景不同,可以将列表分为三大类:无序列表、有序列表和自定义列表。 无序列表 在HTML中使用<ul>标签定义一个无序列表&a…...

无人机自动机库的功能与作用!
一、无人机自动机库的功能 智能停放与管理 无人机自动机库为无人机提供了一个安全、可靠的停放环境。通过先进的感知技术和安全防护措施,它能够实时监测周围环境,确保无人机免受恶劣天气或潜在风险的侵害。 无人机在机库内可以实现智能停放࿰…...

ubuntu 新建脚本shell并增加图标 双击应用实现python运行
1.使用nano创建shell脚本文件 需要在终端窗口中输入“nano”以打开文本编辑器。 nano 在创建脚本文件前,我们要了解脚本文件是如何运行的: 直接运行:直接在终端直接输入需要运行的脚本文件名称,系统或用缺省版本的shell运行脚…...
ANR 分析SOP
遇到ANR问题不要慌,大部分情况下可能是系统or测试手段问题,我们按照如下关键字排查定位 文章目录 1 是否是 heapdump 导致?1.1 dump开始1.2 dump结束 1 是否是 heapdump 导致? 使用 hprof: heap dump 关键词过滤,在d…...

COLA学习之环境搭建(三)
小伙伴们,你们好,我是老寇,上一节,我们学习了COLA代码规范,继续跟老寇学习COLA环境搭建 首先,打开GitHub,搜索 COLA 请给这个COLA项目点个Star,养成好习惯,然后Fork到自…...

CSS输入框动态伸缩动效
前言 下面我们将会做出如下图输入框样式,并且附上组件代码,有特殊需求的可以自行优化同理,下拉框的话只要把el-input标签修改掉即可 MyInput组件 <template><div class"my-input" click.stop"showInput !showInput…...

hbuilder 安卓app手机调试中基座如何设置
app端使用基座 手机在线预览功能 1.点击运行 2.点击运行到手机或者模拟器 3.制作自定义调试基座 4.先生成证书【可以看我上一篇文档写的有】,点击打包 5.打包出android自定义调试基座【android_debug.apk】,【就跟app打包一样需要等个几分钟】 6.点击运行到手…...

探索视觉与语言模型的可扩展性
✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…...
sock_recvmsg函数
sock_recvmsg 是一个在 Linux 内核中用于处理接收网络数据的函数。它通常与套接字 (socket) 操作相关,特别是在网络协议栈中用于处理从网络中接收到的数据。这个函数是内核的一部分,提供了一种机制把接收到的数据从网络协议栈转移到用户空间,或者在内核内进一步处理。 以下是…...

HCIA笔记8--DHCP、Telnet协议
1. DHCP介绍 对于主机的网络进行手动配置,在小规模的网络中还是可以运作的,但大规模网络是无力应对的。因此就有了DHCP协议来自动管理主机网络的配置。 DHCP(Dynamic Host Configuration Protocol): 动态主机配置协议,主要需要配置的参数有…...
Scala的单例对象
在Scala中,单例对象是一种特殊的类,它只能有一个实例,并且这个实例在需要时会自动创建。单例对象在Scala中通过object关键字来定义,它类似于Java中的静态成员和方法,但更加灵活和强大。 定义单例对象 以下是定义一个…...

【笔记】分布式任务调度平台XXL-JOB
这篇笔记主要记录以下内容: (1)第一次启动xxl-job的过程 (2)模块、文件、数据库(表和字段)的作用 (3)极少的源码解读(XxlJobConfig) 有点像实…...

PDFMathTranslate,PDF多语言翻译,批量处理,学术论文,双语对照(WIN/MAC)
分享一个非常实用的PDF文档翻译项目——PDFMathTranslate。作为一个经常逛GitHub的开发者,我总喜欢翻看各种项目附带的论文,虽然大多时候是瞎研究,但却乐在其中。该项目能够完美保留公式、图表、目录和注释,对于需要阅读外文文献的…...

zerotier实现内网穿透(访问内网服务器)
moo 内网穿透工具 实用工具:zerotier 目录 内网穿透工具 Windows下zerotier安装 ubuntu系统下的zerotier安装 使用moon加速 Windows下zerotier安装 有了网络之后,会给你一个网络id,这个网络id是非常重要的,其它设备要加入…...
Formality:set_svf命令
相关阅读 Formalityhttps://blog.csdn.net/weixin_45791458/category_12841971.html?spm1001.2014.3001.5482 svf文件的全称是Setup Verification for Formality,即Design Compiler提供给Formality的设置验证文件,它的作用是为Formality的指导模式(Gui…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
电脑桌面太单调,用Python写一个桌面小宠物应用。
下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡,可以响应鼠标点击,并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...