HTTP协议初识·上篇

news2025/1/19 3:17:14

目录

认识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); // 第三个参数默认 0
            if (_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 进行通信的fd
                struct 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: 利用孤儿进程特性
                    // TODO
                    close(sock);
                    exit(0);
                }
                /* 一定要关掉,否则就会造成文件描述符泄漏,但是这里的关掉要注意了,这里只是把文件描述符的计数-1
                 子进程已经继承过去了,所以这里也可以看做,父进程立马把文件描述符计数-1,只有当子进程关闭的时候,这个文件描述符真正的被关闭了
                 所以后面申请的链接使用的还是这个4号文件描述符,因为计算机太快了
                 close(sock);   */

                /* father
                  那么父进程干嘛呢? 直接等待吗? -- 显然不能,这样又会回归串行运行了,因为等待的时候会阻塞式等待
                  且这里并不能用非阻塞式等待,因为万一有一百个链接来了,就有一百个进程运行,如果这里非阻塞式等待
                  一但后面没有链接到来的话.那么accept这里就等不到了,这些进程就不会回收了 */

                // 不需要等待了 version 2
                waitpid(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 test
    cout << "----------------------- 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 test
    cout << "----------------------- 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); // 第三个参数默认 0
            if (_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. send
            char 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 进行通信的fd
                struct 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 2
                waitpid(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\n
        std::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;             // 未来可以进行修改  变成 ./wwwroot
        path += 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.coloradmin.cn/o/926031.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JS的事前循环Event Loop

前言 javascript是单线程脚本语言&#xff0c;并非指js只有一个线程&#xff0c;而是同一时刻只能有一个线程在工作。 js异步如何实现 如果 JS 中不存在异步&#xff0c;只能自上而下执行&#xff0c;如果上一行解析时间很长&#xff0c;那么下面的代码就会被阻塞。 对于用户…

【直接收藏】前端 VUE 高阶面试题(三)

86.说说vue生命周期&#xff0c;发送请求在生命周期的哪个阶段&#xff0c;为什么不可以是beforeMount&#xff0c;mounted中 回答&#xff1a; 1、vue的生命周期 1&#xff09;、生命周期是什么&#xff1f; Vue 实例有一个完整的生命周期&#xff0c;也就是从开始创建、初始…

C++信息学奥赛1148:连续出现的字符

代码题解&#xff1a; #include <iostream> #include <string> using namespace std; int main() {int n;// 输入一个整数ncin>>n;cin.ignore();string str1;// 输入一行字符串getline(cin,str1);for(int i0;i<str1.length();i){int a0;for(int ji;j<…

自定义拖拽功能,上下拖拽改变盒子高度

核心在于监听鼠标的move来改变div的高度&#xff0c;抽成了组件 <template><div ref"container" class"drag"><z-tooltip v-if"isShowIcon" effect"dark" content"格式化" placement"top-start"&…

Windows11 安装 nvm node版本管理工具

在 Windows 11 上安装并配置 NVM 与 Node.js 版本管理工具 引言&#xff1a; Node.js 是一款强大的开发工具&#xff0c;而版本管理工具 NVM 则可以帮助我们在不同的项目中灵活地切换和管理 Node.js 版本。本篇博客将为大家介绍如何在 Windows 11 操作系统上安装 NVM&#xff…

【Go Web 篇】从零开始:构建最简单的 Go 语言 Web 服务器

随着互联网的迅速发展&#xff0c;Web 服务器成为了连接世界的关键组件之一。而在现代编程语言中&#xff0c;Go 语言因其卓越的性能和并发能力而备受青睐。本篇博客将带你从零开始&#xff0c;一步步构建最简单的 Go 语言 Web 服务器&#xff0c;让你对 Go 语言的 Web 开发能力…

什么是确认测试报告?确认测试报告的用途和周期?

确认测试又称有效性测试&#xff0c;其任务是验证软件的功能和性能及其他特性是否与用户的要求一致。确认测试需要提供的资料包括&#xff1a; 软件需求规格说明书&#xff1a;列出了软件的功能和性能要求&#xff0c;是确认测试的依据。 确认测试计划&#xff1a;制定了确认…

JUC集合、map线程安全

文章目录 在并发场景下&#xff0c;集合产生的问题解决方案VectorsynchronizedListCopyOnWriteArrayList写时赋值技术什么是写实复制技术&#xff1a; HashSet线程不安全问题解决办法 HashMap线程不安全总结特点 在并发场景下&#xff0c;集合产生的问题 现在下面这段代码&…

视频分割合并工具说明

使用说明书&#xff1a;视频分割合并工具 欢迎使用视频生成工具&#xff01;本工具旨在帮助您将视频文件按照指定的规则分割并合并&#xff0c;以生成您所需的视频。 本程序还自带提高分辨率1920:1080&#xff0c;以及增加10db声音的功能 软件下载地址 https://github.com/c…

kafka--技术文档--spring-boot集成基础简单使用

阿丹&#xff1a; 查阅了很多资料了解到&#xff0c;使用了spring-boot中整合的kafka的使用是被封装好的。也就是说这些使用其实和在linux中的使用kafka代码的使用其实没有太大关系。但是逻辑是一样的。这点要注意&#xff01; 使用spring-boot整合kafka 1、导入依赖 核心配…

【DEVOPS】Jenkins使用问题 - 控制台输出乱码

0. 目录 1. 问题描述2. 解决方案3. 最终效果4. 总结 1. 问题描述 部门内部对于Jenkins的使用采取的是Master Slave Work Node的方式&#xff0c;即作为Master节点的Jenkins只负责任务调度&#xff0c;具体的操作由对应的Slave Work Node去执行。 最近团队成员反馈一个问题&a…

高忆管理股票分析:1年期LPR下调10个基点 融资成本稳中有降

8月21日&#xff0c;中国人民银行授权全国银行间同业拆借中心发布&#xff0c;最新借款商场报价利率(LPR)为&#xff1a;1年期种类报3.45%&#xff0c;较上一期下降10个基点&#xff1b;5年期以上种类报4.20%&#xff0c;与前一期相等。 上海高忆私募基金&#xff08;百度搜索高…

2023深圳智博会,正运动助力智能装备“更快更准”更智能!

■展会名称&#xff1a; 2023 深圳国际智能装备产业博览会暨深圳国际电子装备产业博览会&#xff08;以下简称“EeIE 智博会”&#xff09; ■展会日期 2023年8月29日-31日 ■展馆地点 深圳国际会展中心(宝安新馆) ■展位号 3B030 正运动技术&#xff0c;作为国内领先的…

C++动态规划DP Dynamic Programming实现B3635 硬币问题B3636 文字工作

DP动态规划的基本手段及如何解决问题 1. 那带一个问题&#xff0c;只要解决几个对应的小一点规模的问题就能得到问题本身的解 2. 设计一张表格&#xff0c;每一个格子都是一个问题的解 3. 一步步完成这张表格&#xff0c;根据一个数据&#xff0c;往表格前面的数据查找 4. …

APT80DQ40BG-ASEMI低功耗半导体APT80DQ40BG

编辑&#xff1a;ll APT80DQ40BG-ASEMI低功耗半导体APT80DQ40BG 型号&#xff1a;APT80DQ40BG 品牌&#xff1a;ASEMI 封装&#xff1a;TO-3P 恢复时间&#xff1a;&#xff1e;50ns 正向电流&#xff1a;80A 反向耐压&#xff1a;400V 芯片个数&#xff1a;2 引脚数量…

java八股文面试[JVM]——类加载器

一、类加载器的概念 类加载器是Java虚拟机用于加载类文件的一种机制。在Java中&#xff0c;每个类都由类加载器加载&#xff0c;并在运行时被创建为一个Class对象。类加载器负责从文件系统、网络或其他来源中加载类的字节码&#xff0c;并将其转换为可执行的Java对象。类加载器…

Kaniko在containerd中无特权快速构建并推送容器镜像

目录 一、kaniko是什么 二、kaniko工作原理 三、kanijo工作在Containerd上 基于serverless的考虑&#xff0c;我们选择了kaniko作为镜像打包工具&#xff0c;它是google提供了一种不需要特权就可以构建的docker镜像构建工具。 一、kaniko是什么 kaniko 是一种在容器或 Kube…

机器学习基础11-算法比较(基于印第安糖尿病Pima Indians 数据集)

比较不同算法的准确度&#xff0c;选择合适的算法&#xff0c;在处理机器学习的问题时是非常重要的。本节将介绍一种模式&#xff0c;在scikit-learn中可以利用它比较不同的算法&#xff0c;并选择合适的算法。你可以将这种模式作为自己的模板&#xff0c;来处理机器学习的问题…

如何备份系统?很简单,2个方法教会你!

在计算机使用过程中&#xff0c;系统故障、病毒攻击、意外损坏等问题可能导致数据丢失和系统无法正常运行。为了保障数据安全和系统稳定&#xff0c;如何备份系统是至关重要的。本文将介绍备份系统的2个方法&#xff0c;帮助用户轻松备份系统&#xff0c;确保数据的安全和系统的…

什么是网络中的服务质量 (QoS),其相关技术和关键指标有哪些?

QoS&#xff08;Quality of Service&#xff0c;服务质量&#xff09;指一个网络能够利用各种基础技术&#xff0c;为指定的网络通信提供更好的服务能力&#xff0c;是网络的一种安全机制&#xff0c;是用来解决网络延迟和阻塞等问题的一种技术。QoS的保证对于容量有限的网络来…