当前位置: 首页 > news >正文

[Linux#58][HTTP] 自己构建服务器 | 实现网页分离 | 设计思路

目录

一. 最简单的HTTP服务器

二.服务器 2.0

Protocol.hpp

httpServer.hpp

子进程的创建和退出

子进程退出的意义

父进程关闭连接套接字

httpServer.cc

argc (argument count)

argv (argument vector)

三.服务器和网页分离

思考与补充:


一. 最简单的HTTP服务器

基于上一篇文章的理论:

我们可以尝试实现一个简单的 HTTP 服务器,它可以接受客户端连接并返回一个 "Hello World" 网页。为了详细说明这段代码,让我们逐行进行解释。

#include <sys/socket.h>  // 引入套接字相关的头文件
#include <netinet/in.h>  // 引入处理IPv4地址的头文件
#include <arpa/inet.h>   // 引入INET相关函数的头文件
#include <unistd.h>      // 引入UNIX标准函数,如close()
#include <stdio.h>       // 引入标准输入输出头文件
#include <string.h>      // 引入字符串处理函数的头文件
#include <stdlib.h>      // 引入标准库函数,如atoi()

这些头文件包含了程序所需的各种函数和类型:

  • sys/socket.h: 提供套接字函数和数据结构。
  • netinet/in.h: 提供了用于处理 IPv4 地址的结构和函数。
  • arpa/inet.h: 提供了用于操作 IP 地址的函数,如 inet_addr
  • unistd.h: 提供了 UNIX 标准函数,如 close
  • stdlib.h: 提供了一些标准库函数,如 atoi
void Usage() {printf("usage: ./server [ip] [port]\n");
}

定义了一个 Usage 函数,该函数打印使用说明,说明程序需要两个命令行参数,即 IP 地址和端口号。

int main(int argc, char* argv[]) {

程序的 main 函数开始。

    if (argc != 3) {Usage();return 1;}

检查命令行参数的数量。如果参数数量不等于 3(程序名、IP 地址和端口号

    int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {perror("socket"); // 如果创建失败,打印错误信息return 1;}

创建一个套接字AF_INET 表示使用 IPv4,SOCK_STREAM 表示使用 TCP。

    struct sockaddr_in addr; // 定义一个地址结构体addr.sin_family = AF_INET; // 设置为IPv4地址族addr.sin_addr.s_addr = inet_addr(argv[1]); // 设置IP地址addr.sin_port = htons(atoi(argv[2])); // 设置端口号,并转换为网络字节序

设置服务器端地址:

  • sin_family:家族类型为 AF_INET,即 IPv4。
  • sin_addr.s_addr:将命令行参数中的 IP 地址转化为网络字节序的二进制地址。
  • sin_port:将命令行参数中的端口号转化为网络字节序的端口号。
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if (ret < 0) {perror("bind"); // 如果绑定失败,打印错误信息return 1;}

套接字绑定到指定的 IP 地址和端口。

    ret = listen(fd, 10);if (ret < 0) {perror("listen"); // 如果监听失败,打印错误信息return 1;}

开始监听连接,允许最多 10 个连接等待队列。

    for (;;) {struct sockaddr_in client_addr; // 定义客户端地址结构体socklen_t len = sizeof(client_addr); // 定义长度变量int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);if (client_fd < 0) {perror("accept"); // 如果接受连接失败,打印错误信息continue; // 继续下一次循环}

进入一个无限循环,持续接受客户端的连接:

  • client_addr:用于存储客户端的地址。
  • len:保存地址 client_addr 的长度。
  • accept:接受一个客户端连接。

如果 accept 失败,打印错误信息并继续下一次循环。

        char input_buf[1024 * 10] = {0};ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);if (read_size < 0) {perror("read"); // 如果读取失败,打印错误信息close(client_fd); // 关闭客户端套接字continue; // 继续下一次循环}printf("[Request] %s\n", input_buf);

定义一个缓冲区并读取客户端数据:

  • input_buf:存储从客户端读取的数据。
  • read:从客户端套接字读取数据至缓冲区。

如果读取失败,打印错误信息,关闭客户端套接字,并继续下一次循环。

        char buf[1024] = {0};const char* hello = "<h1>hello world</h1>";sprintf(buf, "HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s", strlen(hello), hello);write(client_fd, buf, strlen(buf));

定义一个缓冲区并发送响应:

  • hello:要发送的 HTML 内容。
  • sprintf:格式化 HTTP 响应,包括头部和内容。
  • write:将响应发送回客户端。
        close(client_fd); // 关闭客户端套接字}

关闭客户端连接。

    close(fd); // 关闭服务器套接字return 0; // 正常退出
}

关闭服务器套接字并正常退出程序。

总结:

该程序是一个基本的 HTTP 服务器,负责监听指定的 IP 地址和端口,接受客户端连接,读取请求并发送一个包含 "Hello World" 的 HTML 响应。它通过使用 UNIX 系统调用(如 socketbindlistenaccept 等)来实现这一功能。

完整代码:

#include <sys/socket.h>  // 引入套接字相关的头文件
#include <netinet/in.h>    // 引入处理IPv4地址的头文件
#include <arpa/inet.h>     // 引入INET相关函数的头文件
#include <unistd.h>        // 引入UNIX标准函数,如close()
#include <stdio.h>         // 引入标准输入输出头文件
#include <string.h>        // 引入字符串处理函数的头文件
#include <stdlib.h>        // 引入标准库函数,如atoi()// 打印服务器的使用方法
void Usage() {printf("usage: ./server [ip] [port]\n");
}int main(int argc, char* argv[]) {// 确保命令行参数数量正确(应为3个:程序名、IP地址和端口号)if (argc != 3) {Usage();return 1;}// 创建一个基于IPv4的TCP套接字int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) {perror("socket"); // 如果创建失败,打印错误信息return 1;}struct sockaddr_in addr; // 定义一个地址结构体addr.sin_family = AF_INET; // 设置为IPv4地址族addr.sin_addr.s_addr = inet_addr(argv[1]); // 设置IP地址addr.sin_port = htons(atoi(argv[2])); // 设置端口号,并转换为网络字节序// 将套接字绑定到指定的IP地址和端口int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if (ret < 0) {perror("bind"); // 如果绑定失败,打印错误信息return 1;}// 开始监听传入的连接,允许最多10个连接同时等待ret = listen(fd, 10);if (ret < 0) {perror("listen"); // 如果监听失败,打印错误信息return 1;}// 无限循环,持续接受客户端的连接for (;;) {struct sockaddr_in client_addr; // 定义客户端地址结构体socklen_t len = sizeof(client_addr); // 定义长度变量// 接受一个客户端连接,并将客户端的地址信息存储在client_addr中int client_fd = accept(fd, (struct sockaddr*)&client_addr, &len);if (client_fd < 0) {perror("accept"); // 如果接受连接失败,打印错误信息continue; // 继续下一次循环}// 定义一个缓冲区,用于存储从客户端读取的数据char input_buf[1024 * 10] = {0};// 从客户端读取数据,最多读取缓冲区大小-1字节ssize_t read_size = read(client_fd, input_buf, sizeof(input_buf) - 1);if (read_size < 0) {perror("read"); // 如果读取失败,打印错误信息close(client_fd); // 关闭客户端套接字continue; // 继续下一次循环}// 打印接收到的请求printf("[Request] %s\n", input_buf);// 定义一个缓冲区,用于存储响应数据char buf[1024] = {0};// 定义要发送的HTML内容const char* hello = "<h1>hello world</h1>";// 格式化HTTP响应消息,包括HTTP头部和HTML内容sprintf(buf, "HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s", strlen(hello), hello);// 将响应消息发送回客户端write(client_fd, buf, strlen(buf));// 关闭客户端套接字close(client_fd);}// 关闭服务器套接字close(fd);return 0; // 正常退出
}

二.服务器 2.0

Protocol.hpp

#pragma once#include <iostream>
#include <string>using namespace std;//客户端
class httpRequest
{public:httpRequest(){};~httpRequest(){};public:string inbuffer;//缓冲区//简单一点主要看一下http的细节// string reqline;//请求行// vector<std::string> reqheader;//报头// string body;//请求正文//第一行细分// string method;// string url;// string httpversion;
};//服务器
class httpResponse
{public:string outbuffer;//缓冲区
};

httpServer.hpp

#pragma once
// 确保头文件只被包含一次#include "Protocol.hpp"
// 包含自定义的协议处理头文件,可能定义了 httpRequest 和 httpResponse 类#include <iostream>
#include <string>
#include <stdlib.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <functional>using namespace std;// 定义错误码枚举
enum {USAGG_ERR = 1, // 使用错误SOCKET_ERR,     // 套接字创建错误BIND_ERR,       // 绑定错误LISTEN_ERR      // 监听错误
};const int backlog = 5;
// 定义监听队列的最大长度// 定义函数类型别名,用于处理HTTP请求和响应的回调函数
typedef function<void(const httpRequest&, httpResponse&)> func_t;// 处理HTTP请求的函数
void handlerEntery(int sock,func_t callback)
{// 1. 读到完整的http请求// 2. 反序列化// 3. httprequst, httpresponse, callback(req, resp)// 4. resp序列化// 5. sendchar buffer[4096];httpRequest req;httpResponse resp;ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);//大概率我们直接就能读取到完整的http请求if(n>0){buffer[n]=0;req.inbuffer=buffer;callback(req,resp);send(sock,resp.outbuffer.c_str(),resp.outbuffer.size(),0);}
}// HTTP服务器类
class httpServer {
public:// 构造函数,初始化端口号和监听套接字httpServer(const uint16_t port) : _port(port), _listensock(-1) {}// 初始化服务器void initServer() {// 创建套接字_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0) {exit(SOCKET_ERR); // 如果创建失败,退出程序}// 绑定套接字到本地地址和端口struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = INADDR_ANY; // 绑定到任意地址if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0) {exit(BIND_ERR); // 如果绑定失败,退出程序}// 设置套接字为监听状态if (listen(_listensock, backlog) < 0) { // 设置监听队列长度exit(LISTEN_ERR); // 如果监听失败,退出程序}}// 启动服务器,处理请求void start(func_t func) {// 忽略子进程结束的信号signal(SIGCHLD, SIG_IGN);for (;;) { // 无限循环,等待连接// 接受新连接struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(_listensock, (struct sockaddr *)&peer, &len);if (sock < 0) {continue; // 如果接受失败,继续下一次循环}// 创建子进程处理连接int fd = fork();if (fd == 0) {close(_listensock); // 子进程关闭监听套接字handlerEntery(sock, func); // 处理HTTP请求close(sock); // 处理完毕后关闭连接套接字exit(0); // 子进程退出}close(sock); // 父进程关闭连接套接字}}// 析构函数~httpServer() {// 可以在这里进行资源清理}private:uint16_t _port; // 服务器监听的端口号int _listensock; // 监听套接字的文件描述符
};

⭕ 如何理解这个地方子进程的退出和关闭?

start 函数中,服务器通过 fork() 系统调用创建子进程来处理每个新的连接。以下是对子进程退出和关闭操作的详细解释:

子进程的创建和退出

  1. 创建子进程:
    • int fd = fork();: 这行代码是创建子进程的关键。fork() 调用会创建一个新的子进程。在父进程中,fork() 返回子进程的进程ID;在子进程中,fork() 返回0。
  1. 在子进程中 (if (fd == 0)):
    • close(_listensock);: 子进程不需要监听新的连接,因此它关闭监听套接字。这是因为监听套接字由父进程负责,并且所有子进程都会继承父进程的文件描述符。
    • handlerEntery(sock, func);: 子进程调用 handlerEntery 函数来处理HTTP请求。这个函数会读取请求、反序列化、调用回调函数处理请求、序列化响应并发送响应。
    • close(sock);: 在处理完请求并发送响应后,子进程关闭与客户端的连接套接字,因为它不再需要这个套接字。
    • exit(0);: 子进程通过 exit(0) 退出。这个调用会导致子进程终止,并且操作系统会回收子进程占用的所有资源。

子进程退出的意义

  • 资源回收: 当子进程退出时,操作系统会自动回收子进程所占用的所有资源,包括打开的文件描述符、内存等。这是非常重要的,因为如果不回收资源,可能会导致资源泄漏。
  • 避免僵尸进程: 在调用 fork() 之前,父进程通过 signal(SIGCHLD, SIG_IGN); 忽略了 SIGCHLD 信号。这意味着当子进程结束时,父进程不会收到通知,操作系统会自动清理掉子进程,防止产生僵尸进程。

父进程关闭连接套接字

  • close(sock);: 在父进程中,fork() 返回的是子进程的ID,因此父进程不会进入 if (fd == 0) 块。父进程也不需要这个与客户端的连接套接字,因为它只负责监听新的连接,所以它关闭这个套接字。
    总结来说,子进程的退出和关闭操作确保了每个HTTP请求都能被单独的子进程处理,并且在处理完成后,子进程能够干净地退出,不会留下僵尸进程或资源泄漏。父进程继续监听新的连接请求,而子进程则负责处理已经接受的连接。

httpServer.cc

#include "httpServer.hpp"
#include <memory>// 打印程序使用方法的函数
void Usage(const string& proc) {cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}// 处理HTTP GET请求的函数,参数为请求和响应对象
void Get(const httpRequest &req, httpResponse &resp)
{cout << "----------------http start---------------" << endl;cout << req.inbuffer << endl;cout << "----------------http end-----------------" << endl;string respline = "HTTP/1.1 200 OK\r\n";// string respheader;string respblank = "\r\n";//随便做一个网页string body="<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>hello world</h1></head><body><p>你好呀 祝你天天开心~</p></body></html>";//序列化resp.outbuffer += respline;resp.outbuffer += respblank;resp.outbuffer += body;
}// 程序入口点
int main(int argc, char* argv[]) {// 检查命令行参数数 量是否正确if (argc != 2) {// 如果参数数量不正确,显示使用方法并退出Usage(argv[0]);exit(USAGG_ERR); // 假设 USAGG_ERR 是一个定义的错误代码}// 将命令行参数转换为端口号uint16_t serverport = static_cast<uint16_t>(atoi(argv[1]));// 使用智能指针创建httpServer实例,自动管理内存std::unique_ptr<httpServer> server(new httpServer(serverport));// 初始化服务器server->initServer();// 启动服务器,并传入Get函数作为处理HTTP请求的回调server->start(Get);// 程序正常结束return 0;
}

⭕ 解释argc 和argv的设计与运用

在C和C++程序中,argcargvmain 函数的两个参数,它们用于处理命令行参数。

argc (argument count)

argc 是一个整数,代表传递给程序的命令行参数的数量。它至少总是为1,因为 argv[0] 总是包含程序的名称或路径。

argv (argument vector)

argv 是一个指向字符指针的指针,它指向一个字符串数组,这些字符串包含了程序的命令行参数。argv[0] 是程序的名称或路径,argv[1] 是第一个命令行参数,依此类推。
以下是 argcargv 在上述代码中的设计与运用:

  1. 程序入口点:
int main(int argc, char* argv[]) {

这里 main 函数接收 argcargv 作为参数。

  1. 检查参数数量:
if (argc != 2) {

程序期望用户输入一个命令行参数,即端口号。如果 argc 不等于2(程序名称和一个参数),则说明用户没有正确输入参数。

  1. 打印使用方法:
Usage(argv[0]);

如果参数数量不正确,程序调用 Usage 函数,并传递 argv[0] 作为参数,这通常是程序的名称。Usage 函数会打印出如何正确使用程序的信息。

  1. 获取端口号:
uint16_t serverport = static_cast<uint16_t>(atoi(argv[1]));

程序将 argv[1](第一个命令行参数,即用户输入的端口号字符串)转换为整数,并将其存储在 serverport 变量中。

  1. 启动服务器:
    程序使用 serverport 来初始化和启动 httpServer 实例。
    通过这种方式,argcargv 提供了一种灵活的方式来从命令行接收用户输入,使得程序可以根据不同的输入执行不同的操作。在上述代码中,它们用于指定HTTP服务器监听的端口号。如果用户没有提供正确的参数,程序会提示正确的使用方法并退出。

我们发现udp、tcp、http所有的底层逻辑都是差不多的,而我们只要写上层逻辑就好了。

这里我们主要说原理,下面1-5的工作我们都不做了,所以httpRequest,httpResponse也都给一个缓冲区就行了。

callback 的是 Get 函数

下面我们用浏览器充当客户端发起请求看一下结果

无法访问,我们来开放一下端口号,腾讯云可以直接在小程序上开,就还挺方便的~

然后就可以看到

报头我们暂时不要后面慢慢填,正文部分我们搞一个网页。
网页不会写,可以搜一下w3cschool html教程

这里我们先写到Get函数里,后面我们在分离

void Get(const httpRequest &req, httpResponse &resp)
{cout << "----------------http start---------------" << endl;cout << req.inbuffer << endl;cout << "----------------http end-----------------" << endl;string respline = "HTTP/1.1 200 OK\r\n";// string respheader;string respblank = "\r\n";//随便做一个网页string body="<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>hello world</h1></head><body><p>你好呀 祝你天天开心~</p></body></html>";//序列化resp.outbuffer += respline;resp.outbuffer += respblank;resp.outbuffer += body;
}

虽然我们在响应的时候没有带响应报头,但是我们的浏览器依旧是能识别的,这里想说的是现在浏览器很智能了,可以不用告诉它正文是什么也可以根据正文内容识别这是什么东西,但是有的浏览器做不到。这里我们用的是chrome浏览器。

如果我们要加一个报头里面可以带一些属性呢?

如Content-Type ,告诉别人返回的是什么资源。网上可以搜一下Content-Type 对照表,来进行添加

三.服务器和网页分离

简单实现之后,我们来解决服务器和网页分离,然后通过服务器把网页返回

引入:

在C++中,istringstream 类是在 <sstream> 头文件中定义的,所以你需要包含这个头文件来使用 istringstream 对象。下面是如何在代码中包含它的示例:

#include <sstream>
int main() {std::string line = "一些文本";std::istringstream iss(line);// ... 使用 iss ...return 0;
}

在这个例子中,istringstream 被用来从字符串 line 中读取数据,就像从文件中读取一样。

运用更新:

#pragma once#include <iostream>
#include <string>
#include <sstream>
using namespace std;class Util
{
public:// xxx yyy zzz\r\naaastatic string GetOneline(string &buffer, const string &sep){auto pos = buffer.find(sep);if (pos == string::npos)return "";string sub = buffer.substr(0, pos);return sub;}
};const string sep = "\r\n";//切割符class httpRequest
{public:httpRequest(){};~httpRequest(){};void parse(){// 1. 从inbuffer中拿到第一行,分隔符\r\nstring line = Util::GetOneline(inbuffer, sep);if (line.empty())return;// 2. 从请求行中提取三个字段istringstream iss(line);iss >> method >> url >> httpversion;}public:string inbuffer;// string reqline;// vector<std::string> reqheader;// string body;string method;string url;string httpversion;
};//服务器
class httpResponse
{public:string outbuffer;//缓冲区
};
#pragma once#include <iostream>
#include <string>using namespace std;class Util
{
public:// xxx yyy zzz\r\naaastatic string GetOneline(string &buffer, const string &sep){auto pos = buffer.find(sep);if (pos == string::npos)return "";string sub = buffer.substr(0, pos);return sub;}
};

什么是web根目录

实际上未来一个web服务器写好之后,可不仅仅有这些代码。每一个web服务器都有web根目录,未来所有图片、视频、音频等各种web资源都在这个目录下,按照目录结构组织号好,未来想请求资源就从url请求。那如何保证按照我们的需求在指定路径下去寻找呢?

设计如下目录

err.html

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>404 Not Found</title><style>body {text-align: center;padding: 150px;}h1 {font-size: 50px;}body {font-size: 20px;}a {color: #008080;text-decoration: none;}a:hover {color: #005F5F;text-decoration: underline;}</style></head><body><div><h1>404</h1><p>页面未找到<br></p><p>您请求的页面可能已经被删除、更名或者您输入的网址有误。<br>请尝试使用以下链接或者自行搜索:<br><br><a href="https://www.baidu.com">百度一下></a></p></div></body></html>

index.html:

<!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><!-- <form action="/a/b/hello.html" method="post">name: <input type="text" name="name"><br>password: <input type="password" name="passwd"><br><input type="submit" value="提交"></form> --><h1>这个是我们的首页</h1><!-- <img src="/image/1.png" alt="这是一直猫" width="100" height="100"> 根据src向我们的服务器浏览器自动发起二次请求 --><!-- <img src="/image/2.jpg" alt="这是花"> --></body></html>

实现分离:

现在我们给网页添加一下功能,比如说网页是支持点击然后跳转链接的

跳转成功啦~


思考与补充:

1.请求和响应怎么保证应用层完整读取完毕了呢?

首先我们发现http请求都是字符串按行为单位,所以

  1. 我可以读取完整的一行
  2. while(读取完整一行) --> 所有的请求行+请求行报文全部读完 --> 直到空行!
  3. 我们没说正文也是按行为单位分开的没有办法保证把正文读完,但是我们能保证把报头读完,而报头里有一个Content-Length:xxx(代表正文长度)
  4. 解析出来内容长度,在根据内容长度,读取正文即可!

2.请求和响应是怎么做到序列化和反序列化的?

  • http是用的特殊字符自己实现的。http序列化什么都不做直接发就好了,反序列化 :第一行+请求/响应报头,只要按照\r\n将字符串1->n即可!不用借助任何东西如Json
  • protobuf等。而正文序列化反序列也不用做直接发送就行了。如果你的正文携带结构化数据就要自己处理了。
  • 上面我们也通过写代码的方式,验证上面说的东西。
  • 以前写udp和tcp我们都写过服务端用过套接字,这里还是直接拿过来用啦

3. 如何监视查看网页端

按图片操纵即可

就可以查看到啦

下篇文章将继续讲解网页对图片的插入,和 http 设计的详细解读~

相关文章:

[Linux#58][HTTP] 自己构建服务器 | 实现网页分离 | 设计思路

目录 一. 最简单的HTTP服务器 二.服务器 2.0 Protocol.hpp httpServer.hpp 子进程的创建和退出 子进程退出的意义 父进程关闭连接套接字 httpServer.cc argc (argument count) argv (argument vector) 三.服务器和网页分离 思考与补充&#xff1a; 一. 最简单的HTT…...

7.MySQL内置函数

目录 日期函数时间函数字符串函数数学函数其他函数 日期函数 函数名称描述current_date()当前日期current_time()当前时间current_timesamp()当前时间戳date(datetime)返回datetime参数的日期部分date_add(date, interval d_value_tyep)在date中添加日期函数或时间。interval后…...

如何快速自定义一个Spring Boot Starter!!

目录 引言&#xff1a; 一. 我们先创建一个starter模块 二. 创建一个自动配置类 三. 测试启动 引言&#xff1a; 在我们项目中&#xff0c;可能经常用到别人的第三方依赖&#xff0c;又是引入依赖&#xff0c;又要自定义配置&#xff0c;非常繁琐&#xff0c;当我们另一个项…...

【音视频】ffmpeg其他常用过滤器filter实现(6-4)

最近一直在研究ffmpeg的过滤器使用&#xff0c;发现挺有意思的&#xff0c;这里列举几个个人感觉比较有用的过滤器filter&#xff0c;如下是代码实现&#xff0c;同样适用于命令行操作&#xff1a; 1、视频模糊&#xff1a;通过boxblur可以将画面进行模糊处理&#xff0c;第1个…...

云栖3天,云原生+ AI 多场联动,新产品、新体验、新探索

云栖3天&#xff0c;云原生 AI 20场主题分享&#xff0c;三展互动&#xff0c;为开发者带来全新视听盛宴 2024.9.19-9.21 云栖大会 即将上演“云原生AI”的全球盛会 展现最新的云计算技术发展与 AI技术融合之下的 “新探索” 一起来云栖小镇 见证3天的云原生AI 前沿探索…...

jackson对于对象序列化的时候默认空值和手动传入的null的不同处理

Jackson 在序列化对象时如何处理默认的空值和手动传入的 null&#xff0c;其实归结于它的序列化机制和注解配置。默认情况下&#xff0c;Jackson 不区分 手动设置的 null 和 对象中字段的默认空值&#xff0c;但可以通过配置来改变其行为。具体细节如下&#xff1a; 1. 默认行为…...

L8打卡学习笔记

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 SVM与集成学习 SVMSVM线性模型SVM非线性模型SVM常用参数 集成学习随机森林导入数据查看数据信息数据分析随机森林模型预测结果结果分析 个人总结 SVM 超平面&…...

VBA解除Excel工作表保护

Excel工作表保护解除 工作表保护后无法编辑内容&#xff0c;可能是密码忘记&#xff0c;不可暴力破解隐私 1 打开需的Excel 2 Alt F11 打开代码编辑&#xff0c;点击任意代码编辑项&#xff0c;将如下代码复制&#xff0c;并运行。 Public Sub GetWorkbookPassword()Dim w1 A…...

bash: unzip: 未找到命令,sudo: nano:找不到命令

在 Ubuntu/Debian 系统上 打开终端并运行以下命令&#xff1a; sudo apt update sudo apt install unzip在 CentOS/RHEL 系统上 打开终端并运行以下命令&#xff1a; sudo yum install unzip在 macOS 上 如果您使用的是 macOS&#xff0c;可以使用 Homebrew 安装 unzip&#…...

tauri开发配置文件和文件夹访问路径问题

文件夹没权限&#xff1a;Unhandled Promise Rejection: path not allowed on the configured scope: /Users/song/Library/Application Support/com.pakeplus.app/assets/default.png 没有文件夹&#xff0c;需要先创建&#xff1a;Unhandled Promise Rejection: path: /Users…...

【web安全】——信息收集

一、收集域名信息 1.1域名注册信息 工具&#xff1a;站长之家 whois查询 SEO综合查询 1.2子域名收集 原理&#xff1a;字典爆破&#xff0c;通过字典中的各种字符串与主域名拼接&#xff0c;尝试访问。 站长之家 直接查询子域名 ip138.com https://phpinfo.me/domain/ …...

赵长鹏今日获释,下一步会做什么?币安透露2024年加密货币牛市的投资策略!

中国时间2024年9月28日&#xff0c;加密货币行业的风云人物赵长鹏&#xff08;Changpeng Zhao&#xff0c;简称CZ&#xff09;终于从监狱获释。他因在担任币安首席执行官期间未能有效执行反洗钱(AML)计划而被判刑四个月。赵长鹏的获释引发了广泛关注&#xff0c;不仅因为他是全…...

SpringMVC之ContextHolder

员工不必为自己的弱点而太多的忧虑&#xff0c;而是要大大地发挥自己的优点&#xff0c;使自己充满自信&#xff0c;以此来解决自己的压抑问题。我自己就有许多地方是弱项&#xff0c;常被家人取笑小学生水平&#xff0c;若我全力以赴去提升那些弱的方面&#xff0c;也许我就做…...

什么是SQL注入?

SQL注入是一种安全漏洞&#xff0c;攻击者通过在应用程序的输入字段中插入恶意SQL代码&#xff0c;从而操控数据库。此类攻击通常利用应用程序未对用户输入进行适当验证和清理的弱点。 工作原理&#xff1a; 输入字段&#xff1a;攻击者在登录表单或搜索框等输入区域插入恶意…...

混合密码系统——用对称密钥提高速度,用公钥密码保护会话密钥

混合密码系统&#xff08;Hybrid Cryptosystem&#xff09;是一种结合了多种密码学技术和算法的加密方案&#xff0c;旨在充分利用不同密码算法的优势&#xff0c;以提供更强大的安全性、更高的效率或更好的功能特性。以下是对混合密码系统的详细解释&#xff1a; 组成要素 对…...

Three.js粒子系统与特效

目录 粒子系统基础常见粒子系统特效粒子系统基础 基础的粒子系统 使用THREE.ParticleSystem和THREE.ParticleBasicMaterial实现: // 导入Three.js库 import * as THREE from three...

Tableau数据可视化入门

目录 一、实验名称 二、实验目的 三、实验原理 四、实验环境 五、实验步骤 1、Tableau界面引导 2、数据来源 3、数据预处理操作 4、制作中国各个地区的利润图表 4.1条形图 4.2气泡图 5、制作填充地球图 一、实验名称&#xff1a; 实验一&#xff1a;Tableau数据可视…...

Linux云计算 |【第四阶段】RDBMS1-DAY2

主要内容&#xff1a; 常用函数&#xff08;函数分类1&#xff1a;单行、分组&#xff1b;函数分类2&#xff1a;字符、数学、日期、流程控制&#xff09;、分组查询group by、连接查询 一、常用函数 1. 按使用方式分类 ① 单行函数 单行函数&#xff08;Scalar Functions&…...

后台监控中的云边下控耗时、边缘采集耗时 、云边下控量

云边下控耗时&#xff1a;指云端控制边缘设备的时间&#xff0c;从云端下发指令到边缘设备响应完成的时间。该指标反映了云端控制边缘设备的效率和响应速度。 边缘采集耗时&#xff1a;指边缘设备采集数据到云端处理完成的时间&#xff0c;包括数据采集、传输、处理等环节。该…...

【学习笔记】手写 Tomcat 四

目录 一、Read 方法返回 -1 的问题 二、JDBC 优化 1. 创建配置文件 2. 创建工具类 3. 简化 JDBC 的步骤 三、修改密码 优化返回数据 创建修改密码的页面 注意 测试 四、优化响应动态资源 1. 创建 LoginServlet 类 2. 把登录功能的代码放到 LoginServlet 类 3. 创…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)

概述 在 Swift 开发语言中&#xff0c;各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过&#xff0c;在涉及到多个子类派生于基类进行多态模拟的场景下&#xff0c;…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

React Native在HarmonyOS 5.0阅读类应用开发中的实践

一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强&#xff0c;React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 &#xff08;1&#xff09;使用React Native…...

测试markdown--肇兴

day1&#xff1a; 1、去程&#xff1a;7:04 --11:32高铁 高铁右转上售票大厅2楼&#xff0c;穿过候车厅下一楼&#xff0c;上大巴车 &#xffe5;10/人 **2、到达&#xff1a;**12点多到达寨子&#xff0c;买门票&#xff0c;美团/抖音&#xff1a;&#xffe5;78人 3、中饭&a…...

oracle与MySQL数据库之间数据同步的技术要点

Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异&#xff0c;它们的数据同步要求既要保持数据的准确性和一致性&#xff0c;又要处理好性能问题。以下是一些主要的技术要点&#xff1a; 数据结构差异 数据类型差异&#xff…...

CSS设置元素的宽度根据其内容自动调整

width: fit-content 是 CSS 中的一个属性值&#xff0c;用于设置元素的宽度根据其内容自动调整&#xff0c;确保宽度刚好容纳内容而不会超出。 效果对比 默认情况&#xff08;width: auto&#xff09;&#xff1a; 块级元素&#xff08;如 <div>&#xff09;会占满父容器…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...