Linux网络 | 理解Web路径 以及 实现一个简单的helloworld网页

        前言:本节内容承接上节课的http相关的概念, 主要是实现一个简单的接收http协议请求的服务。这个程序对于我们理解后面的http协议的格式,报头以及网络上的资源的理解, 以及本节web路径等等都有着重要作用。 可以说我们就用代码来理解这些东西。 那么废话不多说, 现在开始我们的学习吧。

        ps:本节内容建议先看一下上一篇文章http的相关概念哦:linux网络 | 深度学习http的相关概念-CSDN博客

目录

 准备文件

 makefile

HttpServer.hpp

类内成员

封装sockfd

start

 ThreadRun

 全部代码

运行结果

响应书写

Web路径


 准备文件

        首先准备文件:

这里面Httpserver.cc用来运行接收http请求的服务。 HttpServer.hpp用来定义http请求。Log.hpp就是一个打印日志的小组件, Socket.hpp同样是套接字的组件。 到使用直接调用相关接口即可。(Log.hpp和Socket.hpp如何实现不讲解, 如果想要知道, 请看博主的相关文章)

日志程序:

linux进程间通信——命名管道、 日志程序_进程间通信日志系统-CSDN博客

Socket套接字:

linux网络 | 序列化反序列化的概念 与 结合网络计算器深度理解-CSDN博客

 makefile

        先将mkefile准备出来:

HttpServer:Httpserver.cc
	g++ -o $@ $^ -std=c++11 -g -lpthread
.PHONY:clean
clean:
	rm -rf HttpServer

HttpServer.hpp

类内成员

class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport) 
        :port_(port)  
    {}

    static void* ThreadRun(void* args)
    {

    }

    void start()
    {


    }



    ~HttpServer()
    {};
private:
    Socket listensock_;
    uint16_t port_;
};

        类内的成员变量就是port_端口号, 到时候启动服务, 就输入一个端口号来启动我们的服务。 然后listensock是我们要接收到哪个主机的请求。 所以我们可以在开始工作的时候再初始化的同时直接accept进行连接。 这个ThreadRun是因为博主要用线程来管理服务, 这个函数就是线程要执行的方法。

封装sockfd

        封装sockfd就是对scokfd进行一下封装:

struct ThreadData
{
    int sockfd;
};

         这么做的目的是为了能够将ThreadData的指针传给线程, 让线程拿到sockfd。就是ThreadRun这个函数。 这个函数创建在类内必须是静态成员。 否则就不能作为线程的执行方法。 而变成静态成员又不能直接使用sockfd。 所以我们就使用了ThreadData*类型的对象传给线程方法。 这样线程就能使用sockfd了。 

start

        看一下start函数, start就是服务启动后, 就执行这个函数。 先初始化, 再绑定, 然后开启监听。 然后就接收服务就行。 当有请求发来时, 那么listensock就能与对方建立连接获得sockfd。 拿到sockfd就封装起来传给线程, 让线程去执行。 

    void start()
    {
        listensock_.InitSocket();  //初始化sockfd
        listensock_.Bind(port_);   //绑定
        listensock_.Listen();      //监听
        for (;;)   
        {
            string clientip;         //请求方ip
            uint16_t clientport;     //请求方port
            //建立连接:
            int sockfd = listensock_.Accept(&clientip, &clientport);
            
            //接收请求
            pthread_t tid;
            ThreadData* td = new ThreadData();
            td->sockfd = sockfd;
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }

 ThreadRun

        线程执行的过程就是创建一个缓冲区, 然后从sockfd中读取数据到缓冲区当中。 


    static void* ThreadRun(void* args)
    {
        pthread_detach(pthread_self());          //先让线程分离。
        //将args,也就是封装起来的ThreadData类型强转一下。 
        ThreadData* td = static_cast<ThreadData*>(args);
        //创建缓冲区。
        char buffer[10240];
        ssize_t n = read(td->sockfd, buffer, sizeof(buffer) - 1);  //从某个地方读取, 然后读取的数据放到buffer里面。 从哪里读取, 
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer;
        }
        close(td->sockfd);
        delete td;
        return nullptr;
    }

 全部代码
 

#ifndef BE0E1813_421A_4BCD_A33B_77432A3CA8D7
#define BE0E1813_421A_4BCD_A33B_77432A3CA8D7

#include<iostream>
#include"Socket.hpp"
#include"Log.hpp"
#include<pthread.h>


//创建端口号。
static const int defaultport = 8080;


struct ThreadData
{
    int sockfd;
};



class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport) 
        :port_(port)  
    {}

    static void* ThreadRun(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        char buffer[10240];
        ssize_t n = read(td->sockfd, buffer, sizeof(buffer) - 1);  //从某个地方读取, 然后读取的数据放到buffer里面。 从哪里读取, 
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer;
        }
        close(td->sockfd);
        delete td;
        return nullptr;
    }

    void start()
    {
        listensock_.InitSocket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for (;;)
        {
            string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip, &clientport);
            pthread_t tid;
            ThreadData* td = new ThreadData();
            td->sockfd = sockfd;
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }



    ~HttpServer()
    {};
private:
    Socket listensock_;
    uint16_t port_;
};







#endif /* BE0E1813_421A_4BCD_A33B_77432A3CA8D7 */

运行结果

        先启动服务

        然后就是打开浏览器, 输入我们的服务器ip:端口号。 就能请求到这个服务了。 然后就能看到我们的服务这里有了反应: 

        这就说明, 我们浏览器, 确实能够访问到我们的创建的http服务。 

        在上面获得的这些信息中, 我们看一下这个User-Agent。 这个就是请求到服务器的机器的信息。 就是我们利用一台机器访问一个网站或者网页,笼统的说叫做资源。 然后浏览器就把我们的机器的信息传给了服务端。服务端接收的时候就能接收到了。

响应书写

        我们可以将响应单独封装起来。 如下:

    static void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);  //从某个地方读取, 然后读取的数据放到buffer里面。 从哪里读取, 
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer;
            //
            //返回响应的过程。


            
        }

        close(sockfd);
    }

    static void* ThreadRun(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        
        HandlerHttp(td->sockfd);
      
        delete td;
        return nullptr;
    }

        到时候线程执行方法, 就执行ThreadRun函数就行了。然后ThreadRun函数就去处理请求。 

HandleHttp就是处理请求的函数。 这个函数里面是先接收请求。 然后就进行响应。

        关于响应我们上节内容讲到过, 第一行就是对响应行。 然后下面的多行就是报头。 最后报头和正文部分有空行。 现在我们书写一下:

    static void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);  //从某个地方读取, 然后读取的数据放到buffer里面。 从哪里读取, 
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer;
            //
            //返回响应的过程。
            
            //先写正文内容
            string text = "hello world";

            //然后写一下响应行
            string response_line = "HTTP/1.0 200 OK\r\n";

            //然后再写报头, 这里我们的报头只写正文的长度, 因为我们只讲了正文长度。 剩下的属性后面再讲。
            string response_header = "Content-Length: ";
            response_header += to_string(text.size());
            response_header += "\r\n";

            //空行
            string blank_line = "\r\n";

            //然后把所有的数据加到要发送到相应里面。
            string response;
            response += response_line;
            response += response_header;
            response += blank_line;
            response += text;
            //发送
            send(sockfd, response.c_str(), response.size(), 0);
        }

        close(sockfd);
    }

然后我们运行之后, 再从浏览器进行请求就请求到了字符串资源:hello world:

         这样我们就能实现一个简单的处理http请求的服务了。

Web路径

        我们知道, 我们的http服务, 我们平时使用浏览器, 都是访问一个网页, 或者访问一张图片。 

        其实, 我们的正文这里是可以写一个网页, 作为响应发送给请求方。 那么网页怎么写, 这个是Web的知识点,本节不做讲解(其实博主也不会, 但是博主特意找了一个学过Web的朋友, 请他把他的Web大作业给了博主。 现在博主有一个html了)。

        我们这里简单的使用一下Web语句, 我们把正文部分改成下面的语句:

string text = "<html><body>hello world</body></html>";  //其中html代表html文档, body就是代表网页正文。 /就是代表正文结尾或者文档结尾。

        我们也可以给hello world做成标签:

string text = "<html><body><h3>hello world</h3></body></html>";  //h1到h6是六级标签, /的意思同样是结束标志

         然后我们再运行就能看到这个world变了。

        我们做出一个简单的网页之后。 

        但是我们知道, 我们要访问的资源, 其实是带有路径的。 而路径的根目录是web根目录。 那么这个web路径和我们上面写的正文字符串有什么联系呢?

        那么我们这里还要知道另一个知识点, 就是如果我们只是访问ip + 端口号。 那么浏览器会默认给我们访问/(web根目录), 访问的是web根目录下面的资源; 如果后面加了路径, 那么就被称作web路径, 访问的是对应的web路径下面的资源。 

        但是我们上面是使用的字符串作为正文啊, 不是一个路径, 就是一个字符串。所以无论我们后面加什么路径, 访问得到的相应都是hello world。 

 

        所以我们上面写的字符串其实不是正统的网页写法。 正统的网页, 应该是一个文件!!!

        所以, 我们现在就可以捋清楚服务器响应请求的过程:

        就是我们现在可以写出自己的报头, 写出请求行。 然后我们可以把资源目录(web目录)拼接到正文。

        然后用户在访问我们的服务器时, 就可以根据他想要的资源, 访问相应的路径!!!!

 

         那么, 这个资源目录怎么拼接到正文, 知道了这个, 我们也就知道了什么是Web路径。 

        Web根目录, 其实是可以自己指定的, 可以是linux的根目录, 也可以是当前目录,也可以是其他路径。

        现在, 我们定义一个Web根目录为当前目录下的wwwroot文件:

const string wwwroot =  "./wwwroot"; //定义web根目录为当前目录。

         所以, 未来, 我们的wwwroot文件夹就是一个web目录。 以后我们的网页, 我们的图片, 都放到这个文件下面。 用户访问网页的时候, 只能以该目录为根节点, 往下访问!!!!而往下访问到的任何一个路径, 都叫做Web路径!!!

        有了Web目录之后, 我们以后写的html代码就写到Web目录里面: 

        为了让我们的正文不再是静态的代码, 而是一个根据我们用户的请求, 想要访问的网页, 那么我们可以使用read函数读取网页。 

        接下来的工作就是读取文件!

        封装代码:

   static string ReadWebContent(string path)
    {
        //注意, 这里读取文件可能读到的是不完整的!所以有坑, 但是本节内容不管。
        ifstream in(path);   //打开对应路径的文件
        if (!in.is_open()) return "404";   //文件路径没有, 直接返回404

        string content;
        string line;
        while (getline(in, line))  //一行一行的读取
        {
            content += line;
        }
        in.close();
        return content;   //读取完毕之后返回结果
    }

        封装好了函数之后, 我们以后text就直接等于text = ReadWebContent(某个路径)就行了:

         而这个路径, 不正是用户发来的请求里面包含的吗?

        所以,我们从请求里面提取路径,而我们说路径是在url里面的, 而url又在请求行的第二个部分。只要我们从这里面得到路径, 假如是/a/b/c, 那么我们再wwwroot += /a/b/c, 不就等于我们要访问的是./wwwroot/a/b/c了吗?所以, wwwroot,就叫做Web根目录!而且, 我们还可以直接创建一个配置文件,就叫config

        这样以后, 我们的Web根目录, 就根据我们的想法, 想在哪就在哪!不需要改变程序, 只需要改配置文件!!!!

         所以, 接下来的工作就是在处理请求:我们需要重新定义一个请求类:


//有了这个请求类之后, 我们以后所有的请求都放到这里对象里面。
class Request
{
public:
    void Deserialize(string req)
    {
        string tmp;
        int pos = 0;

        //切割字符串
        while (true)
        {
            pos = req.find(sep);
            if (pos == string::npos) break;
            string temp = req.substr(0, pos);
            if (temp.empty()) break;

            req_header.push_back(temp);
            req.erase(0, pos + sep.size());
        }



        //剩下的都是正文
        text = req;

        DebugPrint();
    }

    //对请求进行打印。
    void DebugPrint()
    {
        cout << "------------------------------------------------------------" << endl;
        for(auto& e : req_header)
        {
            cout << e << endl << endl; 
        }
        cout << text << endl;
    }
public:
    vector<string> req_header;    //请求行
    string text;    //请求正文

};

        然后我们测试一下我们写的请求类是否正确, 先不写了响应, 先Debug一下:

 

        是正确的, 接下来, 我们就要拿到url。 所以我们要给request进一步分割:

        然后就可以拿到路径了。 但是这里还有最后一个问题。就是如果我们的用户访问的是根目录/, 那么不久拿到了当前路径下的所有资源了吗? 但是实际上我们在访问网页的时候只会访问到一个网页, 比如www.baidu.com, 我们是不是就访问到了一个首页? 所以, 我们的路径还要处理一下:

        然后我们的所有代码就完成了, 下面看一下运行结果:

        我们可以发现, 我们访问到了!以上就是我们的所有内容啦, 下面是全部代码!

#ifndef BE0E1813_421A_4BCD_A33B_77432A3CA8D7
#define BE0E1813_421A_4BCD_A33B_77432A3CA8D7

#include<iostream>
#include"Socket.hpp"
#include"Log.hpp"
#include<sstream>
#include<pthread.h>
#include<vector>
#include<fstream>


//创建端口号。
static const int defaultport = 8080;
const string wwwroot =  "./wwwroot"; //定义web根目录为当前目录下的wwwroot 。
const string sep = "\r\n";
const string homepage = "index.html";

class HttpServer;

struct ThreadData
{
    ThreadData(int sock) 
        : sockfd(sock)
    {}
    int sockfd;
};


//有了这个请求类之后, 我们以后所有的请求都放到这里对象里面。
class Request
{
public:
    void Deserialize(string req)
    {
        string tmp;
        int pos = 0;

        //切割字符串
        while (true)
        {
            pos = req.find(sep);
            if (pos == string::npos) break;
            string temp = req.substr(0, pos);
            if (temp.empty()) break;

            req_header.push_back(temp);
            req.erase(0, pos + sep.size());
        }

        parse();
        //剩下的都是正文
        text = req;

        DebugPrint();
    }

    //对请求进行打印。
    void DebugPrint()
    {
        cout << "------------------------------------------------------------" << endl;
        for(auto& e : req_header)
        {
            cout << e << endl << endl; 
        }

        cout << method << endl << url << endl <<  http_version << endl << path << endl; 

        cout << text << endl;
    }

    //解析第一行
    void parse()
    {
        stringstream ss(req_header[0]);  //stringstream要包含头文件sstream 
        ss >> method >> url >> http_version;

        path = wwwroot;   //    ./wwwroot
        if (url == "/" || url == "/index.html") 
        {
            path += "/";  //   ./wwwroot/
            path += homepage;     //    ./wwwroot/a/b/c
        }
        else
        {
            path += url;
        }
    }
public:
    vector<string> req_header;    //请求行
    string text;    //请求正文

    string method;
    string url;
    string http_version;

    string path;
};



class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport) 
        :port_(port)  
    {}

    static string ReadWebContent(string str)
    {
        //注意, 这里读取文件可能读到的是不完整的!所以有坑, 但是本节内容不管。
        ifstream in(str);
        if (!in.is_open()) return "404";   //文件路径没有, 直接返回404

        string content;
        string line;
        while (getline(in, line))
        {
            content += line;
        }
        in.close();
        return content;
    }

    static void HandlerHttp(int sockfd)
    {
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);  //从某个地方读取, 然后读取的数据放到buffer里面。 从哪里读取, 
        if(n > 0)
        {
            buffer[n] = 0;

            //处理请求
            Request req;
            req.Deserialize(buffer);
            req.DebugPrint();

 
            
            //返回响应的过程。
            //先写正文内容
            string text = ReadWebContent(req.path);
            //然后写一下响应行
            string response_line = "HTTP/1.0 200 OK\r\n"; 

            //然后再写报头, 这里我们的报头只写正文的长度, 因为我们只讲了正文长度。 剩下的属性后面再讲。
            string response_header = "Content-Length: ";
            response_header += to_string(text.size());
            response_header += "\r\n";

            //空行
            string blank_line = "\r\n";

            //然后把所有的数据加到要发送到相应里面。
            string response;
            response += response_line;
            response += response_header;
            response += blank_line;
            response += text;
            //发送
            send(sockfd, response.c_str(), response.size(), 0);
        }

        close(sockfd);
    }

    static void* ThreadRun(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        
        HandlerHttp(td->sockfd);
      
        delete td;
        return nullptr;
    }

    void start()
    {
        listensock_.InitSocket();
        listensock_.Bind(port_);
        listensock_.Listen();
        for (;;)
        {
            string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip, &clientport);
            pthread_t tid;
            ThreadData* td = new ThreadData(sockfd);
            td->sockfd = sockfd;
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }



    ~HttpServer()
    {};
private:
    Socket listensock_;
    uint16_t port_;
};







#endif /* BE0E1813_421A_4BCD_A33B_77432A3CA8D7 */

 

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!  

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2.5万字 - 用TensorFlow和PyTorch分别实现五种经典模型

在深度学习领域&#xff0c;TensorFlow和PyTorch是两大广泛使用的框架&#xff0c;各有其独特的特性和优势。随着人工智能技术的快速发展&#xff0c;越来越多的开发者需要熟练掌握这两种工具&#xff0c;以便在实际项目中选择适合的框架进行高效开发。 目录 入门友好介绍 Te…

【C++】2029:【例4.15】水仙花数

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;我的做法思路分析优势不足之处 &#x1f4af;老师的做法思路分析优势不足 &#x1f4af;对比和优化实现方式对比优化思路和操作1. 直接分解数字的各位…

结合长短期记忆网络(LSTM)和无迹卡尔曼滤波器(UKF)的技术在机器人导航和状态估计中的应用前景

结合长短期记忆网络(LSTM)和无迹卡尔曼滤波器(UKF)的技术在机器人导航和状态估计中具有广泛的应用前景。如有滤波、导航方面的代码定制需求,可通过文末卡片联系作者获得帮助 文章目录 结合LSTM和UKF的背景结合LSTM和UKF的优势应用实例研究现状MATLAB代码示例结论结合LSTM和…

Android14 CTS-R6和GTS-12-R2不能同时测试的解决方法

背景 Android14 CTS r6和GTS 12-r1之后&#xff0c;tf-console默认会带起OLC Server&#xff0c;看起来olc server可能是想适配ATS(android-test-station)&#xff0c;一种网页版可视化、可配置的跑XTS的方式。这种网页版ATS对测试人员是比较友好的&#xff0c;网页上简单配置下…

告别Kibana:Elasticsearch 桌面客户端的新变革

告别Kibana&#xff1a;Elasticsearch 桌面客户端的新变革 在大数据处理与分析领域&#xff0c;Elasticsearch 及其相关技术的应用日益广泛。长期以来&#xff0c;Kibana 在数据可视化与查询管理方面占据重要地位&#xff0c;但随着技术的不断发展&#xff0c;用户对于更高效、…

HTML5实现喜庆的新年快乐网页源码

HTML5实现喜庆的新年快乐网页源码 前言一、设计来源1.1 主界面1.2 关于新年界面1.3 新年庆祝活动界面1.4 新年活动组织界面1.5 新年祝福订阅界面1.6 联系我们界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载结束语 HTML5实现喜庆的新年快乐网页源码&#xff0c;春节新年网…

【广州计算机学会、广州互联网协会联合主办 | ACM独立出版 | 高录用】第四届大数据、信息与计算机网络国际学术会议(BDICN 2025)

第四届大数据、信息与计算机网络国际学术会议&#xff08;BDICN 2025&#xff09;定于2025年01月10-12日在中国广州举行。会议旨在为从事“大数据”、“计算机网络”与“信息”研究的专家学者、工程技术人员、技术研发人员提供一个共享科研成果和前沿技术&#xff0c;了解学术发…

C语言函数栈帧的创建和销毁

文章目录 一、寄存器二、函数栈帧的创建和销毁1.什么是函数栈帧&#xff1f;2.案例代码-讲解3.总结函数栈帧 一、寄存器 寄存器(Register)是中央处理机、主存储器和其他数字设备中某些特定用途的存储单元。寄存器是集成电路中非常重要的一种存储单元&#xff1b;其可用来暂存指…

我的博客年度之旅:感恩、成长与展望

目录 感恩有你 技能满点 新年新征程 嘿&#xff0c;各位技术大佬、数码潮咖还有屏幕前超爱学习的小伙伴们&#xff01;当新年的钟声即将敲响&#xff0c;我们站在时光的交汇点上&#xff0c;回首过往&#xff0c;满心感慨&#xff1b;展望未来&#xff0c;豪情满怀。过去的这…

【数据库初阶】MySQL数据类型

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; 数据库初阶 &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 亲爱的小伙伴们&#xff0c;大家好&#xff01;在这篇文章中&#xff0c;我们将深入浅出地为大家讲解 MySQL…

webrtc 源码阅读 make_ref_counted模板函数用法

目录 1. 模板参数解析 1.1 typename T 1.2 typename... Args 1.3 typename std::enable_if::value, T>::type* nullptr 2. scoped_refptr 3. new RefCountedObject(std::forward(args)...); 4. 综合说明 5.在webrtc中的用法 5.1 peerConnectionFactory对象的构建过…

python参数传递不可变对象含可变子对象

当传递不可变对象时。不可变对象里面包含的子对象是可变的。则方法内修改了这个可变对象&#xff0c;源对象也发生了变化。 a (10, 20, [5, 6]) print("a", id(a))def test01(m):print("m", id(m))m[2][0] 888print("修改m后m的值为{}".forma…

qt5.15.2+visual studio2022 免安装版环境配置

1.环境准备 visual studio2022qt5.15.2&#xff08;免安装版本&#xff09; 2.环境配置 2.1 打开首选项 2.2 添加Qt版本 2.3 构建套件手动添加Qt 5.15.2&#xff08;msvc2019_64&#xff09;并配置如下 3.新建项目 问题1&#xff1a;qt creator 没有欢迎界面 解决办法&#…

KOI技术-事件驱动编程(Sping后端)

1 “你日渐平庸&#xff0c;甘于平庸&#xff0c;将继续平庸。”——《以自己喜欢的方式过一生》 2. “总是有人要赢的&#xff0c;那为什么不能是我呢?”——科比布莱恩特 3. “你那么憎恨那些人&#xff0c;和他们斗了那么久&#xff0c;最终却要变得和他们一样&#xff0c;…

华为消费级QLC SSD来了

近日&#xff0c;有关消息显示&#xff0c;华为的消费级SSD产品线&#xff0c;eKitStor Xtreme 200E系列&#xff0c;在韩国一家在线零售商处首次公开销售&#xff0c;引起了业界的广泛关注。 尽管华为已经涉足服务器级别的SSD制造多年&#xff0c;但直到今年6月才正式推出面向…

007-构建工具大进步:Amper Amper Amper!

Amper Amper Amper! 今天天气不好&#xff0c;送孩子上少年宫之后就在茶馆里坐着。突然看到一个帖子&#xff1a;Project configuration with Amper&#xff0c;看得心情大好。 用Kotlin也有个大概几年的时间&#xff0c;开发了几个小工具&#xff0c;感觉很是不错。但是配置…

STM32 高级 物联网通讯之LoRa通讯

目录 LoRa通讯基础知识 常见的3种通讯协议 远距离高速率的传输协议 近距离高速率传输技术 近距离低功耗传输技术 低功耗广域网 采用授权频段技术 非授权频段 LoRa简介 LoRa的特点 远距离 低功耗 安全 标准化 地理定位 移动性 高性能 低成本 LoRa应用 LoRa组…

SAP月结、年结前重点检查事项(后勤与财务模块)

文章目录 一、PP生产模块相关的事务检查二、SD销售模块相关的事务检查:三、MM物料管理模块相关的事务检查四、FICO财务模块相关的事务检查五、年结前若干注意事项【SAP系统PP模块研究】 #SAP #生产订单 #月结 #年结 一、PP生产模块相关的事务检查 1、月末盘点后,生产用料的…

重装操作系统后 Oracle 11g 数据库数据还原

场景描述&#xff1a; 由于SSD系统盘损坏&#xff0c;更换硬盘后重装了操作系统&#xff0c;Oracle数据库之前安装在D盘(另一个硬盘)&#xff0c;更换硬盘多添加一个盘符重装系统后盘符从D变成E&#xff0c;也就是之前的D:/app/... 变成了现在的 E:/app/...&#xff0c;重新安装…

2D图像测量到3D点云之物体三维尺寸测量!!!!

0&#xff0c;引言 本文将从双目采集的2D图像到3D点云进行转化&#xff0c;并进行物体尺寸测量&#xff0c;旨在为读者展示2D图像如何关联3D点云&#xff0c;并进行相关工业应用。 将2D图像转化为3D点云&#xff0c;并进行物体尺寸测量的技术&#xff0c;在工业领域有着广泛的…