HTTP协议初识·上篇

目录

认识URL

urlencode和urldecode

如何编码解码和验证过程

一个基本的网络服务器的流程

代码验证请求与响应

准备工作

HTTPServer.hpp

Protocol.hpp

makefile

1请求

HTTPServer.hpp

1.0函数handlerHttp-基本流程

再次处理

HttpServer.cc(新建文件)

测试1 -- 请求测试

云服务器响应1

云服务器响应2

云服务器响应2解析

手机测试解析

游览器推送解析

爬虫原理

2响应

一个简单的网页

修改一下

测试2 -- 响应测试

telnet,一个测试工具,有兴趣可以去了解

客户端视角

服务端视角

浏览器请求视角

乱码解决方法

添加报头

测试3 -- 乱码解决

云服务器的配置较低可能申请失败

3分割字段

准备工作

新文件Util.hpp

2.0新函数getOneline分割出一行一行的字段

2.1新函数parse和认识新接口stringstream

HttpServer.cc修改

2.2打印工作放到外面来

测试结果4分割字段

注意细节

5修改默认web起始目录

补充知识

wwwroot是一个目录

http.conf是一个配置文件

5.1parse修改

6首页设置

6.1首页默认放到wwwroot下的一级目录中

6.2加一个判断就可以解决访问首页的问题

6.3再加一个path的打印

测试5路径拼接测试

1拼接路径

2拼接首页

全部源码

HttpServer.cc

HttpServer.hpp

makefile

Protocol.hpp

Util.hpp

关于浏览器引起的一些认识


认识URL

urlencode和urldecode

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


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

实例

wd:word缩写

如何编码解码和验证过程

一个基本的网络服务器的流程

代码验证请求与响应

准备工作

当前准备代码是基于上篇文章删改过来的,一个干净的多线程服务器

一个简单的协议定制_清风玉骨的博客-CSDN博客参考 :一个简单的协议定制_清风玉骨的博客-CSDN博客 

HTTPServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>#include "Protocol.hpp"namespace server
{enum{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR};static const uint16_t gport = 8080;static const int gbacklog = 5; // 10、20、50都可以,但是不要太大比如5千,5万using func_t = std::function<bool(const HttpRequest &, HttpResponse &)>;    // 回调class HttpServer{public:HttpServer(func_t func, const uint16_t &port = gport) : _func(func), _listensock(-1), _port(port){}void initServer(){// 1. 创建socket文件套接字对象 -- 流式套接字_listensock = socket(AF_INET, SOCK_STREAM, 0); // 第三个参数默认 0if (_listensock < 0){exit(SOCKET_ERR);}// 2.bind绑定自己的网路信息 -- 注意包含头文件struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);      // 这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到local.sin_addr.s_addr = INADDR_ANY; // 接受任意ip地址if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){exit(BIND_ERR);}// 3. 设置socket 为监听状态 -- TCP与UDP不同,它先要建立链接之后,TCP是面向链接的,后面还会有“握手”过程if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面再填这个坑{exit(LISTEN_ERR);}}void start(){for (;;) // 一个死循环{// 4. server 获取新链接// sock 和 client 进行通信的fdstruct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock, (struct sockaddr *)&peer, &len);if (sock < 0){continue;}/* 这里直接使用多进程版的代码进行修改version 2 多进程版(2) -- 注意子进程会继承父进程的一些东西,父进程的文件操作符就会被子进程继承,即父进程的文件操作符那个数字会被继承下来,指向同一个文件,但是文件本身不会被拷贝一份也就是说子进程可以看到父进程创建的文件描述符sock和打开的listensock     */pid_t id = fork();if (id == 0) // 当id为 0 的时候就代表这里是子进程{/* 关闭不需要的文件描述符 listensock -- 子进程不需要监听,所以我们要关闭这个不需要的文件描述符即使这里不关,有没有很大的关系,但是为了防止误操作我们还是关掉为好   */close(_listensock);if (fork() > 0)exit(0); // 解决方法1: 利用孤儿进程特性// TODOclose(sock);exit(0);}/* 一定要关掉,否则就会造成文件描述符泄漏,但是这里的关掉要注意了,这里只是把文件描述符的计数-1子进程已经继承过去了,所以这里也可以看做,父进程立马把文件描述符计数-1,只有当子进程关闭的时候,这个文件描述符真正的被关闭了所以后面申请的链接使用的还是这个4号文件描述符,因为计算机太快了close(sock);   *//* father那么父进程干嘛呢? 直接等待吗? -- 显然不能,这样又会回归串行运行了,因为等待的时候会阻塞式等待且这里并不能用非阻塞式等待,因为万一有一百个链接来了,就有一百个进程运行,如果这里非阻塞式等待一但后面没有链接到来的话.那么accept这里就等不到了,这些进程就不会回收了 */// 不需要等待了 version 2waitpid(id, nullptr, 0);}}~HttpServer() {}private:int _listensock;uint16_t _port;func_t _func;};} // namespace server

Protocol.hpp

#pragma once#include <iostream>
#include <string>class HttpRequest
{
public:std::string inbuffer;
};class HttpResponse
{
public:std::string outbuffer;
};

makefile

cc=g++
httpserver:HttpServer.cc$(cc) -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f httpserver

这里因为有天然的客户端(游览器),所以我们先暂时不需要写客户端

1请求

HTTPServer.hpp

1.0函数handlerHttp-基本流程

再次处理

记得把sock(套接字)传进来

HttpServer.cc(新建文件)

#include "HttpServer.hpp"
#include <memory>using namespace std;
using namespace server;void Usage(std::string proc)
{cerr << "Usage:\n\t" << proc << " port\r\n\r\n";
}bool Get(const HttpRequest &req, HttpResponse &resp)
{// for testcout << "----------------------- http start ------------------------------------" << endl;cout << req.inbuffer << endl;   // 暂时不做其他处理,直接打印出来看请求内容cout << "------------------------ http end -------------------------------------" << endl;return true;
}// ./httpServer 8080    -- 这里实际是80端口号,不过这里是为了测试就不用了,并且80也绑定不了,前一千多号基本内部资源无法绑定
int main(int argc, char *argv[])
{if(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = atoi(argv[1]);unique_ptr<HttpServer> httpsvr(new HttpServer(Get, port));httpsvr->initServer();httpsvr->start();return 0;
}

测试1 -- 请求测试

云服务器响应1

云服务器响应2

实际中应该如下图,这其实是一个高并发的请求,它一次性会请求多个,并不是一个 

云服务器响应2解析

手机测试解析

游览器推送解析

爬虫原理

2响应

一个简单的网页

可以参考这个网页教程 HTML 简介_w3cschool

在VScode中创建一个这种后缀的文件,输入 ! 后按Tab键,得到一个网页的基本格式

 

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body></body>
</html>

修改一下

        修剪成一行,因为是在c++中硬编码,所以使用起来比较麻烦,当遇到特殊字符的时候注意一下要斜杠转义一下,防止报错

测试2 -- 响应测试

telnet,一个测试工具,有兴趣可以去了解

客户端视角

服务端视角

浏览器请求视角

        这次测试,明明我们并没有填写响应报头,并且还没有报头的长度,但是明显浏览器可以很好的解决这几点,并且暂时还没有出现很大的问题,把内容解释出来了。

        现在在浏览器已经很智能了,它已经可以识别出内容是什么,是文本还是网页,甚至图片或者视频,这方面Chrome做的比较成熟,但是有些游览器并不会做这些,比如火狐它就会以文本的形式显示出来,网页并不会帮你做解释。

        基于这一点,我们还是要填写好自己的报头,告诉浏览器我们发送过去的是一个网页。

乱码解决方法

其实这里大概可以了,不过为了完善,我们先填写好报头再进一步测试

添加报头

测试3 -- 乱码解决

云服务器的配置较低可能申请失败

浏览器会一次性高并发申请很多很多,不过有结果就行了,这方面不是我们要考虑的,这次测试有几次出错了,我感觉就是这个原因

3分割字段

 

准备工作

新文件Util.hpp

2.0新函数getOneline分割出一行一行的字段

2.1新函数parse和认识新接口stringstream

后者是一种流 

HttpServer.cc修改

2.2打印工作放到外面来

测试结果4分割字段

注意细节

5修改默认web起始目录

补充知识

wwwroot是一个目录

http.conf是一个配置文件

这边我们为了方便就直接写入代码中,不进行配置了

default-root

5.1parse修改

6首页设置

这时候拼接会有一个问题出现

6.1首页默认放到wwwroot下的一级目录中

当访问根目录的情况下,会直接给path拼接首页路径

6.2加一个判断就可以解决访问首页的问题

6.3再加一个path的打印

测试5路径拼接测试

1拼接路径

2拼接首页

全部源码

HttpServer.cc

#include "HttpServer.hpp"
#include <memory>using namespace std;
using namespace server;void Usage(std::string proc)
{cerr << "Usage:\n\t" << proc << " port\r\n\r\n";
}// 1. 服务器和网页分离,html
// 2. url -> / : web根目录
bool Get(const HttpRequest &req, HttpResponse &resp)
{// for testcout << "----------------------- http start ------------------------------------" << endl;cout << req.inbuffer << endl; // 暂时不做其他处理,直接打印出来看请求内容std::cout << "method: " << req.method << std::endl;std::cout << "url: " << req.url << std::endl;std::cout << "httpversion: " << req.httpversion << std::endl;std::cout << "path: " << req.path << std::endl;cout << "------------------------ http end -------------------------------------" << endl;std::string respline = "HTTP/1.1 200 OK\r\n";std::string respheader = "Content-Type: text/html\r\n";std::string respblank = "\r\n"; // 空行// 网页 -- 自己写一个简单的, 不要在C++中写html,这里是测试,很不方便std::string body = "<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>七夕节日我竟然在搞这个?</h1></head><body><p>好寂寞~</p></body></html>";// 直接拼接就可以了,本身很简单resp.outbuffer += respline;resp.outbuffer += respheader;resp.outbuffer += respblank;resp.outbuffer += body;return true;
}// ./httpServer 8080    -- 这里实际是80端口号,不过这里是为了测试就不用了,并且80也绑定不了,前一千多号基本内部资源无法绑定
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);exit(0);}uint16_t port = atoi(argv[1]);unique_ptr<HttpServer> httpsvr(new HttpServer(Get, port));httpsvr->initServer();httpsvr->start();return 0;
}

HttpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>#include "Protocol.hpp"namespace server
{enum{USAGE_ERR = 1,SOCKET_ERR,BIND_ERR,LISTEN_ERR};static const uint16_t gport = 8080;static const int gbacklog = 5; // 10、20、50都可以,但是不要太大比如5千,5万using func_t = std::function<bool(const HttpRequest &, HttpResponse &)>;    // 回调class HttpServer{public:HttpServer(func_t func, const uint16_t &port = gport) : _func(func), _listensock(-1), _port(port){}void initServer(){// 1. 创建socket文件套接字对象 -- 流式套接字_listensock = socket(AF_INET, SOCK_STREAM, 0); // 第三个参数默认 0if (_listensock < 0){exit(SOCKET_ERR);}// 2.bind绑定自己的网路信息 -- 注意包含头文件struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);      // 这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到local.sin_addr.s_addr = INADDR_ANY; // 接受任意ip地址if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){exit(BIND_ERR);}// 3. 设置socket 为监听状态 -- TCP与UDP不同,它先要建立链接之后,TCP是面向链接的,后面还会有“握手”过程if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面再填这个坑{exit(LISTEN_ERR);}}void HandlerHttp(int sock){// 1. 读到完整的http请求// 2. 反序列化// 3. 反序列化后得到httprequest, 回调填写httpresponse, 利用_func(req, resp)// 4. 序列化resp// 5. sendchar buffer[4096];HttpRequest req;HttpResponse resp;size_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);   // 大概率我们直接能读取到完整的http请求if(n > 0){buffer[n] = 0;req.inbuffer = buffer;req.parse();_func(req, resp); // 可以根据bool返回值进行判断,这里就不判断了send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);}}void start(){for (;;) // 一个死循环{// 4. server 获取新链接// sock 和 client 进行通信的fdstruct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock, (struct sockaddr *)&peer, &len);if (sock < 0){continue;}/* 这里直接使用多进程版的代码进行修改version 2 多进程版(2) -- 注意子进程会继承父进程的一些东西,父进程的文件操作符就会被子进程继承,即父进程的文件操作符那个数字会被继承下来,指向同一个文件,但是文件本身不会被拷贝一份也就是说子进程可以看到父进程创建的文件描述符sock和打开的listensock     */pid_t id = fork();if (id == 0) // 当id为 0 的时候就代表这里是子进程{/* 关闭不需要的文件描述符 listensock -- 子进程不需要监听,所以我们要关闭这个不需要的文件描述符即使这里不关,有没有很大的关系,但是为了防止误操作我们还是关掉为好   */close(_listensock);if (fork() > 0) exit(0); // 解决方法1: 利用孤儿进程特性HandlerHttp(sock);close(sock);exit(0);}/* 一定要关掉,否则就会造成文件描述符泄漏,但是这里的关掉要注意了,这里只是把文件描述符的计数-1子进程已经继承过去了,所以这里也可以看做,父进程立马把文件描述符计数-1,只有当子进程关闭的时候,这个文件描述符真正的被关闭了所以后面申请的链接使用的还是这个4号文件描述符,因为计算机太快了close(sock);   *//* father那么父进程干嘛呢? 直接等待吗? -- 显然不能,这样又会回归串行运行了,因为等待的时候会阻塞式等待且这里并不能用非阻塞式等待,因为万一有一百个链接来了,就有一百个进程运行,如果这里非阻塞式等待一但后面没有链接到来的话.那么accept这里就等不到了,这些进程就不会回收了 */// 不需要等待了 version 2waitpid(id, nullptr, 0);}}~HttpServer() {}private:int _listensock;uint16_t _port;func_t _func;};} // namespace server

makefile

cc=g++
httpserver:HttpServer.cc$(cc) -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f httpserver

Protocol.hpp

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <sstream> // 可以直接以空格作为分隔符来进行分割#include "Util.hpp"const std::string sep = "\r\n"; // 分隔符
const std::string default_root = "./wwwroot";    // web起始目录,前面的 ./ 加不加都可以
const std::string home_page = "index.html";     // 默认首页,任何服务器都会有这个默认首页class HttpRequest
{
public:HttpRequest() {}~HttpRequest() {}void parse()    // 解析{// 1. 从inbuffer中拿到第一行,分隔符\r\nstd::string line = Util::getOneline(inbuffer, sep);if(line.empty()) return;// 2. 从请求行中提取三个字段 -- 下面放开的三个std::cout << "line: " << line << std::endl; // 打印出来显示一下std::stringstream ss(line);                 // 可以直接以空格作为分隔符来进行分割ss >> method >> url >> httpversion;// 3. 添加web默认路径path = default_root;             // 未来可以进行修改  变成 ./wwwrootpath += url;                     // 到这一步之后就会  变成 ./wwwroot/a/b/c.html// 未来访问路径都会从这个路径下开始访问// 这边会遇到一个问题,当url是一个 / 的时候就不行,拼接的时候会变成 ./wwwroot/ 没有具体目标if(path[path.size()-1] == '/') path += home_page;   // 加一个判断就行了}public:std::string inbuffer;/*  我们可以细分许多字段,当需要什么就可以添加什么,这里为了简洁就不做这些工作了std::string reqline; // 请求行std::vector<std::string> reqheader; // 请求报头std::string body;   // 请求正文*/std::string method; // 请求方法std::string url;std::string httpversion; // 请求版本std::string path;       // web默认路径
};class HttpResponse
{
public:std::string outbuffer;
};

Util.hpp

#pragma once#include <iostream>
#include <string>class Util
{
public:// XXXX XXX XXX\r\nYYYYY  -- 格式// 第二个参数是分隔符,暴露在外部,让外部传进来static std::string getOneline(std::string &buffer, const std::string &sep)  // 类内静态方法可以直接使用 -- 为了方便写,就定义成静态的{auto pos = buffer.find(sep);if(pos == std::string::npos) return "";     // 没有找到分隔符std::string sub = buffer.substr(0, pos);    // [ ) 左闭右开 拿到这一行字段buffer.erase(0, sub.size() + sep.size());   // 删除这一行return sub;}
};

关于浏览器引起的一些认识

浏览器是一款工业级软件

        浏览器是一款工业级别的软件,它是电脑默认安装的软件当中开发工作量最大的软件!!!

        它的开发难度非常大!即使是一款简单的基本浏览器都是几百万行起步(操作系统上千万行),开发成本特别高,这一点ps、vs studio、cad、12306之类的软件都是几百万行起步(工业级别),这些软件一般都是国外的(确是得承认,这一点国外做的比较好),从国内很多的游览器都是套壳Chrome(开源)中可以看出来(上图中有显示)

那么我们有能力做出来吗?为什么不做?

        有!但是没必要!就算是操作系统我们国内业界也是有能力做出来的,但是没有意义!

        就好比,做一款软件要三年,花上100人,每年30万,一年就算三千万,开发成本就将近一个亿就没了,并且投入市场,并不是就立马见效,在这个赛道上已经有人做了,做了几十年,比你的好并且稳定,那么结果显而易见,成绩并不会好,可能就直接噶掉了,这不就是打水漂吗?所以没必要!这一点之前华为的鸿蒙因为套壳Linux被骂的很惨,其实没必要,我们知道重新开发就意味着新的生态(参考以前的文章),没有人用就等于白搭,明白这一点我们就知道这其实是怎么一个情况了

那当别人突然不给我们用软件呢?

        那就拿以前的版本做二次开发,这才是编程世界是最普遍的现象,也是最适合的方法

那我们的开发资源到哪里去了呢?

        在那些没有人做的领域中去,大家都是从零开始,把较大的风险规避,把那些资源投入到可能让我们领先的地方,这才是正确的做法

身为一个有见识的程序员,我们得知道这一点,千万不能道听途说,随意相信那些自媒体(为黑而黑),我们得有自己的观点,知行合一

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/104799.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

iOS开发之查看静态库(.a/.framework)中包含的.o文件和函数符号(ar,nm命令)

.a/.framework其实是把编译生成的.o文件&#xff0c;打包成一个.a/.framework文件。a的意思是archive/归档的意思。 查看静态库.a文件包含的内容用下面的命令解压&#xff1a; ar x xxx.a 用ar命令打包静态库&#xff1a; 参数r是将后面的*.o或者*.a文件添加到目标文件中 参数…

031 - 浮点类型(近似值 FLOAT,DOUBLE)

-FLOAT&#xff0c;DOUBLE&#xff1a; FLOAT和DOUBLE类型代表近似数字数据值。MySQL将四个字节用于单精度值&#xff0c;并将八个字节用于双精度值。 对于FLOAT&#xff0c;SQL标准允许对FLOAT括号中的关键字后面的位以精度&#xff08;而不是指数的范围&#xff09;进行可选规…

全流程R语言Meta分析核心技术高阶应用

查看原文>>>全流程R语言Meta分析核心技术高阶应用 目录 专题一、Meta分析的选题与检索 专题二、Meta分析与R语言数据清洗及统计方法 专题三、R语言Meta分析与作图 专题四、R语言Meta回归分析 专题五、R语言Meta诊断分析 专题六、R语言Meta分析的不确定性 专题…

YoloV5环境搭建记录

https://github.com/ultralytics/yolov5/ 1、在Anaconda Promptx新建conda虚拟环境 conda create -n py39_yolov5 python3.9 2、激活虚拟环境 conda activate py39_yolov5 3、虚拟环境下装所需依赖 conda install pytorch1.12.1 torchvision0.13.1 torchaudio0.12.1 cpuo…

代码仓库设置访问权限

通过设置IP白名单的IP范围和访问控制&#xff0c;限制用户的访问和上传下载权限&#xff0c;大大增强仓库的安全性。 对于测试环境和生产环境都不应该增加“允许提交代码”权限 只有开发环境才允许

嵌入式Linux人脸检测libfacedetection

人脸检测 此库依赖Opencv&#xff0c;所以首先要移植Opencv到板子上。 笔者使用LVGL搭建了一个界面&#xff0c;界面有些卡顿&#xff08;主要原因是文件存取较慢&#xff09;&#xff0c;演示效果如下&#xff1a; OpenCV 首先要交叉编译Opencv 参考&#xff1a;https://…

一生一芯9——ubuntu22.04安装valgrind

这里安装的valgrind版本是3.19.0 下载安装包 在选定的目录下打开终端&#xff0c;输入以下指令 wget https://sourceware.org/pub/valgrind/valgrind-3.19.0.tar.bz2直至下载完成 解压安装包 输入下面指令解压安装包 tar -xvf valgrind-3.19.0.tar.bz2.tar.bz2注&#xf…

春秋云镜 CVE-2019-16113

春秋云镜 CVE-2019-16113 Bludit目录穿越漏洞 靶标介绍 在Bludit<3.9.2的版本中&#xff0c;攻击者可以通过定制uuid值将文件上传到指定的路径&#xff0c;然后通过bl-kernel/ajax/upload-images.php远程执行任意代码。 启动场景 漏洞利用 exp https://github.com/Kenun…

AWS 提示证书签名过期无法自动更新

如果域名没有通过验证的话&#xff0c;证书的过去是没有办法自动更新的。 验证的方式也非常简单&#xff0c;通过下面的配置&#xff0c;把 CNAME添加到你的域名上面&#xff0c;AWS 就可会自动完成验证了。 当添加完成后&#xff0c;AWS 验证需要的时间大致在 30 分钟到 1 个…

优美而高效:解决服务器通信问题

题目背景 在这个问题中&#xff0c;我们面临着一幅服务器分布图。图中的每个单元格可能有服务器&#xff08;标记为1&#xff09;或者没有&#xff08;标记为0&#xff09;。我们的任务是找出能够与至少一台其他服务器进行通信的服务器数量。 算法思路 为了解决这个问题&…

HTTP原理与实现

一、基本概念 一、基本原理* 1、全称&#xff1a; HyperText Transfer Protocol (超文本传输协议) 2、底层实现协议&#xff1a;建立在 TCP/IP 上的无状态连接。 3、基本作用&#xff1a;用于客户端与服务器之间的通信&#xff0c;规定客户端和服务器之间的通信格式。包括请…

婚恋交友h5多端小程序开源版开发

婚恋交友h5多端小程序开源版开发 以下是婚恋交友H5多端小程序的功能列表&#xff1a; 用户注册和登录&#xff1a;用户可以通过手机号码或第三方账号注册和登录。个人信息填写&#xff1a;用户可以填写个人基本信息&#xff0c;包括姓名、性别、年龄、身高、体重、学历、职业等…

Qt应用开发(拓展篇)——示波器/图表 QCustomPlot

一、介绍 QCustomPlot是一个用于绘图和数据可视化的Qt C小部件。它没有进一步的依赖关系&#xff0c;提供友好的文档帮助。这个绘图库专注于制作好看的&#xff0c;出版质量的2D绘图&#xff0c;图形和图表&#xff0c;以及为实时可视化应用程序提供高性能。 QCustomPl…

Docker-Consul

Docker-Consul 一、介绍1.什么是服务注册与发现2.什么是consul3.consul提供的一些关键特性&#xff1a; 二、consul 部署1.环境准备2.consul服务器3.查看集群信息4.通过 http api 获取集群信息 三、registrator服务器1.安装 Gliderlabs/Registrator2.测试服务发现功能是否正常3…

基于深度学习的图像风格迁移发展总结

前言 本文总结深度学习领域的图像风格迁移发展脉络。重点关注随着GAN、CUT、StyleGAN、CLIP、Diffusion Model 这些网络出现以来&#xff0c;图像风格迁移在其上的发展。本文注重这些网络对图像风格迁移任务的影响&#xff0c;以及背后的关键技术和研究&#xff0c;并总结出一…

Qt(C++)计算一段程序执行经过的时间

一、前言 在许多应用程序和系统中,需要对经过的时间进行计算和记录。例如 可能想要测量某个操作的执行时间,或者记录一个过程中经过的时间以进行性能分析。在这些场景下,准确地计时是非常重要的。 Qt提供了一个功能强大的计时器类QElapsedTimer,可以方便地记录经过的时间…

PostMan 测试项目是否支持跨域

使用PostMan可以方便快速的进行跨域测试。 只需要在请求头中手动添加一个Origin的标头&#xff0c;声明需要跨域跨到的域&#xff08;IP&#xff1a;端口&#xff09;就行&#xff0c;其余参数PostMan会自动生成。添加此标头后&#xff0c;请求会被做为一条跨域的请求来进行处…

python+django+协同过滤算法-基于爬虫的个性化书籍推荐系统(包含报告+源码+开题)

为了提高个性化书籍推荐信息管理的效率&#xff1b;充分利用现有资源&#xff1b;减少不必要的人力、物力和财政支出来实现管理人员更充分掌握个性化书籍推荐信息的管理&#xff1b;开发设计专用系统--基于爬虫的个性化书籍推荐系统来进行管理个性化书籍推荐信息&#xff0c;以…

硬盘中病毒是什么原因?硬盘格式化能清除病毒吗

“我的电脑中了一个非常顽固的病毒&#xff0c;朋友建议我进行硬盘格式化来彻底清除病毒。不知道是不是真的有用&#xff0c;半信半疑下进行了硬盘格式化。当我完成操作后&#xff0c;我发现有些工作文件没有备份到。这可怎么办&#xff1f;想问下大家有没有什么方法去恢复数据…

JS逆向系列之商指针数据解密

文章目录 声明案例地址y解密算法分析ecryptByPrivateKey 解密算法分析写代码前的流程梳理参考代码往期逆向文章推荐声明 本文章中所有内容仅供学习交流,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请私信我立即删除! 案例地址 aHR0cDovL…