【计算机网络】HTTP协议以及简单的HTTP服务器实现

news2024/9/25 23:21:30

文章目录

  • 一、HTTP协议
    • 1.认识URL
    • 2.urlencode和urldecode
    • 3.HTTP协议格式
    • 4.HTTP的方法
    • 5.HTTP的状态码
    • 6.HTTP常见Header
    • 7.重定向
    • 8.长连接
    • 9.会话保持
    • 10.基本工具
  • 二、简单的HTTP服务器实现
    • 1.err.hpp
    • 2.log.hpp
    • 3.procotol.hpp
    • 4.Sock.hpp
    • 5.Util.hpp
    • 6.httpServer.hpp
    • 7.httpServer.cc
    • 8.总结分析

一、HTTP协议

虽然我们说, 应用层协议是我们程序猿自己定的.

但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输议)就是其中之一。

1.认识URL

平时我们俗称的 “网址” 其实就是说的 URL

在这里插入图片描述

2.urlencode和urldecode

像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.

比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.

转义的规则如下:

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

在这里插入图片描述

“+” 被转义成了 “%2B”

urldecode就是urlencode的逆过程;

encode:对特殊符号和汉字编码为%XX

decode:服务器(软件)收到url请求–自己对特殊%XX进行解码

urldecode工具

3.HTTP协议格式

HTTP请求

在这里插入图片描述

首行: [方法] + [url] + [版本]

Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束

Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

HTTP响应

在这里插入图片描述

首行: [版本号] + [状态码] + [状态码解释]

Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束

Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中

状态码有200,400,302,307,500,404等等

状态码描述:404 -> Not Found 200 -> OK

在这里插入图片描述

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

a.首先读取完整的一行

b.while(读取完整的一行)-所有的请求行+请求报头全部读完-直到空行

c.我们能保证把报头读完,报头有一个属性:Content-Length:XXX正文长度

d.解析出来内容长度,再根据内容长度,读取正文即可。

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

http自己实现的,第一行+请求/响应报头,只要按照\r\n将字符串1->n即可,正文则不需要做

响应的正文可以是html/css/js/图片/视频/音频等

4.HTTP的方法

在这里插入图片描述

其中最常用的就是GET方法和POST方法.

5.HTTP的状态码

在这里插入图片描述

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)

6.HTTP常见Header

Content-Type: 数据类型(text/html等)

Content-Length: Body的长度

Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;

User-Agent: 声明用户的操作系统和浏览器版本信息;

referer: 当前页面是从哪个页面跳转过来的;

location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;

Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

7.重定向

我们使用手机或者浏览器的时候,屏幕什么也没有点,就跳转到了其他的页面,比如自动跳转到广告商等,这是通过重定向完成的。重定向分为临时重定向和永久重定向。

重定向过程:客户端向服务器发送http请求,服务器给客户端发送的http响应的状态码为3XX,附带一个新的url,然后客户端重新向新的服务器发起请求—临时重定向。假如一个公司的一个网站,因为各种原因(比如同时访问数量受限等),所以重新写了一个网站,但是客户不知道新的网站链接,只知道老的链接,所以公司对老的链接进行永久重定向,客户在访问以前的网站的时候自动跳转到新的网站—永久重定向

在这里插入图片描述

8.长连接

其实我们看到的网页,实际上可能由多种元素构成,即一张完整的网页需要多次http请求

http网页中可能包含多个元素,如果频繁的发起http请求,http是基于TCP的,TCP是面向连接的,就会有频繁创建连接的问题(客户端和服务器都需要对连接进行管理,先描述再组织,会对连接创建对应的内核数据结构,对连接的管理就变成了对数据结构的管理,就会有时间和空间的成本)

所以就提出了长连接,长连接需要client和server都要支持,建立好一条连接,获取一份资源的时候,通过同一条连接来完成

Connection:Keep-alive  --支持长连接
Connection:close   --不支持长连接

9.会话保持

我们使用网页的bilibili的时候,我们登录一次之后,后面的一段时间都不需要我们重新进行登录了,关闭页面,然后重新点进去也不需要重新登录,这就是http的会话保持做到的

会话保持严格意义不是http天然具备的,而是后面使用发现需要的

http协议是无状态的,即http不关心上一次和下一次的请求,只负责当前请求的传输,但是用户需要,因为用户查看新的网页是常规操作,如果发送页面跳转,那么新的页面也就无法识别是哪一个用户了,为了让用户一经登录,可以在整个网站按照自己的身份进行随意访问,就需要会话保持。

我们看腾讯视频的时候,对于需要会员的视频,我们可以通过链接直接获取而不进行登录吗,答案是不行的,凡是对网页访问有权限要求的网页,在被获取之前,全部都要做判断,进行身份认证。

会话保持有两种方法:

第一种方案:我们在浏览器进行登录输入信息之后,浏览器会把我们用户输入的信息:用户名&&密码保持起来,这个被称为cookie数据。cookie分为cookie文件级数据和cookie内存级数据。往后只要访问同一个网站,浏览器就会自动推送历史保留信息给服务器

我们登录的时候,浏览器保存了cookie文件,每次请求账号密码都要进行推送,然后服务器再返回资源,但是这里有个问题,如果黑客在我们的电脑上种植了木马病毒,获取了我们的cookie文件,这样我们的账号密码就泄漏了,那么黑客使用他的浏览器登录我们的账号,此时服务器会误认为这个非法用户是你,如果是QQ,微信对我们就会造成很大的影响

第二种方案:我们进行登录的时候输入用户名和密码,此时服务器端会根据用户的信息形成一个session文件,它有唯一的名称:session id来进行唯一标识,然后将当前用户的session id返回给用户,此时浏览器的cookie文件中保存的是session id。此后用户发送http request 和session id给服务器,服务器根据session id进行鉴权,此时client保存了cookie,server保存了session。

如果黑客获取了用户的session,此时服务器还是会误认为这个非法用户是你,但是此时我们已经将矛盾转移了,此时黑客盗取的是公司的私密数据,有法律进行维护,此外还配合了其他的策略来缓解该类问题,比如我们在很短的时间内从一个地区到另一个地区,QQ就会提醒我们异地登录,需要重新进行登录等等

10.基本工具

这里介绍两个基本工具:postman 和 fiddler
在这里插入图片描述

postman不是抓包工具,而是一个模拟客户端—浏览器的行为的工具

fiddler是一个抓包工具,http工具

postman和fiddler的原理:

浏览器将请求发送给fiddler,此时fiddler可以作为代理服务器看待,然后就fiddler将请求转发给服务器,服务器响应的信息通过fiddler转发到用户的手中,而postman则就是相当于客户端向服务器发起请求

我们对下文的程序进行抓包:

我们可以看到,我们的账号和密码都是明文的,所以POST和GET都是不安全的,只有是否私密的区别,安全需要HTTPS来完成。

二、简单的HTTP服务器实现

1.err.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR
};

2.log.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <unistd.h>

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"

#define NUM 1024

const char *to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}

void logMessage(int level, const char *format, ...)
{

    char logprefix[NUM];
    snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
             to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    vsnprintf(logcontent, sizeof(logcontent), format, arg);

    std::cout << logprefix << logcontent << std::endl;

    FILE *log = fopen(LOG_NORMAL, "a");
    FILE *error = fopen(LOG_ERR, "a");

    if (log && error)
    {
        FILE *cur = nullptr;
        if (level == DEBUG || level == NORMAL || level == WARNING)
            cur = log;
        if (level == ERROR || level == FATAL)
            cur = error;
        if (cur)
            fprintf(cur, "%s%s\n", logprefix, logcontent);
        fclose(log);
        fclose(error);
    }
}

3.procotol.hpp

#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "Util.hpp"

const std::string sep = "\r\n";
const std::string default_root = "./wwwroot";
const std::string home_page = "index.html";
const std::string html_404 = "wwwroot/404.html";

class HttpRequest
{
public:
    HttpRequest() {}
    ~HttpRequest() {}

public:
    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, ./wwwroot/
        if (path[path.size() - 1] == '/')
            path += home_page;

        // 4.获取path对应资源的后缀
        // ./wwwroot.index.html
        // ./wwwroot.1.jpg
        auto pos = path.rfind(".");
        if (pos == std::string::npos)
            suffix = ".html";
        else
            suffix = path.substr(pos);

        // 5.得到资源的大小
        struct stat st;
        int n = stat(path.c_str(), &st);
        if (n == 0)
            size = st.st_size;
        else
            size = -1;
    }

public:
    std::string inbuffer;

    std::string method;
    std::string url;
    std::string httpversion;
    std::string path;
    std::string suffix;
    int size;
};

class HttpResponse
{
public:
    std::string outbuffer;
};

4.Sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "log.hpp"
#include "err.hpp"

static const int backlog = 5;

class Sock
{
public:
    void Socket()
    {
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            logMessage(FATAL, "create socket error");
            exit(SOCKET_ERR);
        }

        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof opt);
        logMessage(NORMAL, "create socket success");
    }

    void Bind(const uint16_t &port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);

        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(port);

        int n = bind(_listensock, (struct sockaddr *)&local, sizeof local);
        if (n < 0)
        {
            logMessage(FATAL, "bind socket error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL, "bind socket success");
    }

    void Listen()
    {
        int n = listen(_listensock, backlog);
        if (n < 0)
        {
            logMessage(FATAL, "listen socket error");
            exit(LISTEN_ERR);
        }

        logMessage(NORMAL, "listen socket success");
    }

    int Accept()
    {
        struct sockaddr_in peer;

        socklen_t len = sizeof(peer);
        int sock = accept(_listensock, (struct sockaddr *)&peer, &len);

        return sock;
    }

    int Fd()
    {
        return _listensock;
    }

    void Close()
    {
        if (_listensock > 0)
            close(_listensock);
    }

private:
    int _listensock;
};

5.Util.hpp

#pragma once

#include <string>
#include <fstream>

class Util
{
public:
    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;
    }

    static bool readFile(const std::string resource, char *buffer, int size)
    {
        std::ifstream in(resource, std::ios::binary);
        if (!in.is_open())
            return false;

        in.read(buffer, size);

        in.close();
        return true;
    }
};

6.httpServer.hpp

#pragma once 

#include <functional>
#include <sys/wait.h>
#include "Sock.hpp"
#include "protocol.hpp"
#include "log.hpp"
#include "err.hpp"
#include "Util.hpp"

namespace server
{
    static const int defaultport = 8080;

    using func_t = std::function<bool(const HttpRequest &, HttpResponse &)>;

    class httpServer
    {
    public:
        httpServer(const func_t func, const uint16_t &port = defaultport)
            : _func(func), _port(port)
        {
        }

        void initServer()
        {
            _sock.Socket();
            _sock.Bind(_port);
            _sock.Listen();
        }

        void handlerEvent(int sock)
        {
            // 1. 读到完整的http请求
            // 2. 反序列化
            // 3. httprequst, 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);
            if (n > 0)
            {
                buffer[n] = 0;
                req.inbuffer = buffer;
                req.parse();
                _func(req, resp); // req -> resp
                send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);
            }
        }

        void start()
        {
            for (;;)
            {
                int sock = _sock.Accept();
                if (sock < 0)
                    continue;

                pid_t id = fork();
                if (id == 0)
                {
                    _sock.Close();
                    if (fork() > 0)
                        exit(0);
                    handlerEvent(sock);
                    close(sock);
                    exit(0);
                }
                close(sock);

                waitpid(id, nullptr, 0);
            }
        }

        ~httpServer()
        {
        }

    private:
        Sock _sock;
        uint16_t _port;
        func_t _func;
    };
}

7.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";
}

std::string suffixToDesc(const std::string suffix)
{
    std::string ct = "Content-Type: ";
    if (suffix == ".html")
        ct += "text/html";
    else if (suffix == ".jpg")
        ct += "application/x-jpg";
    ct += "\r\n";
    return ct;
}

// 1. 服务器和网页分离,html
// 2. url -> / : web根目录
bool Get(const HttpRequest &req, HttpResponse &resp)
{
    // for test
    cout << "----------------------http start---------------------------" << endl;
    cout << req.inbuffer;
    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;
    std::cout << "suffix: " << req.suffix << std::endl;
    std::cout << "size: " << req.size << "字节" << std::endl;
    cout << "----------------------http end---------------------------" << endl;

    // std::string respline = "HTTP/1.1 200 OK\r\n";
    std::string respline = "HTTP/1.1 307 Permanent Redirect\r\n";
    std::string respheader = suffixToDesc(req.suffix);

    respheader += "Location: https://www.qq.com/\r\n";
    std::string respblank = "\r\n";
    // std::string body = "<html lang=\"en\"><head><meta charset=\"UTF-8\"><title>for test</title><h1>hello world</h1></head><body><p>多情只有春庭月,犹为离人照落花</p></body></html>";

    std::string body;
    body.resize(req.size + 1);
    if (!Util::readFile(req.path, (char *)body.c_str(), req.size))
    {
        Util::readFile(html_404, (char *)body.c_str(), req.size);
    }

    respheader += "Content-Length: ";
    respheader += to_string(body.size());
    respheader += "\r\n";

    resp.outbuffer += respline;
    resp.outbuffer += respheader;
    resp.outbuffer += respblank;

    cout << "----------------------http response start---------------------------" << endl;
    std::cout << "resp.outbuffer" << resp.outbuffer << std::endl;
    cout << "----------------------http response end---------------------------" << endl;
    resp.outbuffer += body;

    return true;
}

// ./httpServer port
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));

    unique_ptr<httpServer> httpsvr(new httpServer(Get));
    httpsvr->initServer();
    httpsvr->start();

    return 0;
}

8.总结分析

一个用户看到的网页结果,可能是多个资源结合而成的,所以要获取一个网页效果,我们的浏览器一定会 发起多次http请求。

我们进行数据提交的时候,本质前端要通过form表单提交的,浏览器会自动将form表单中的内容转换成GET/POST方法请求

GET和POST的区别

如下是GET的方式

在这里插入图片描述

如下是POST的方式

在这里插入图片描述

二者的区别如下:

GET通过URL传递参数,具体:http://ip:port/XXX/YYY?name=value&name1=value1

POST提交参数通过http请求的正文提交参数

POST方法通过正文的提交参数,所以一般用户看不到,私密性更好,私密性 != 安全性,GET方法不私密

无论是GET还是POST方法,都不安全,要谈安全,必须加密 --https

通过URL传递参数,注定了不能太大

但是POST方法,通过正文,正文可以很大,甚至可以是其他的东西

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

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

相关文章

阿里云ECS使用docke搭建redis服务

目录 1.确保正确安装好docker 2.安装redis镜像 3.创建容器设置端口映射 1.确保正确安装好docker 安装教程&#xff1a; 阿里云ECS(CentOS镜像)安装docker-CSDN博客https://blog.csdn.net/qq_62262918/article/details/135686614?spm1001.2014.3001.5501 2.安装redis镜像…

年龄性别预测1:年龄性别数据集说明(含下载地址)

年龄性别预测1&#xff1a;年龄性别数据集说明(含下载地址) 目录 年龄性别预测1&#xff1a;年龄性别数据集说明(含下载地址) 1.前言 2.MegaAge_Asian 3.MORPH 4.IMDB-WIKI 5.数据集下载 6.年龄性别预测和识别(Python/C/Android) 1.前言 本项目将实现年龄性别预测和识…

Java医药WMS进销存系统

技术架构&#xff1a; jdk8 IntelliJ IDEA maven Mysql5.7 有需要的可以私信我。 系统功能与介绍&#xff1a; 医药进销存系统&#xff0c;主要分两种角色&#xff1a;员工、客户。本系统具有进销存系统的通用性&#xff0c;可以修改为其它进销存系统&#xff0c;如家电进…

仿三方智能对话分析原始会话窗口

设计效果如下&#xff1a; 设计要求如下&#xff1a; 1、顶部播放条播放时&#xff0c;文字内容自动滚动。 监听audio事件timeupdate&#xff0c;只要播放器在播放就会触发该事件。每行文字有开始时间begin。判断当前时间&#xff08;currentTime&#xff09;<开始时间&am…

【分布式技术】Elastic Stack部署,实操logstash的过滤模块常用四大插件

目录 一、Elastic Stack&#xff0c;之前被称为ELK Stack 完成ELK与Filebeat对接 步骤一&#xff1a;安装nginx做测试 步骤二&#xff1a;完成filebeat二进制部署 步骤三&#xff1a;准备logstash的测试文件filebeat.conf 步骤四&#xff1a;完成实验测试 二、logstash拥有…

【REMB 】翻译:草案remb-03

REMB REMB消息 以及 绝对时间戳选项 在带宽估计中的使用 :an absolute-value timestamp option for use in bandwidth estimatoin. 接收方带宽估计的RTCP消息 REMB 这位大神翻译的更好。 RTCP message for Receiver Estimated Maximum Bitrate draft-alvestrand-rmcat-remb-03…

vite多页面打包学习(一)

一、前期准备 首先初始化两套独立的vue实例和相关生态&#xff08;多页面嘛&#xff09;&#xff0c;如下 我在src文件下创建了pages大文件夹&#xff0c;并初始化了两套页面分别为index和page1&#xff0c;每套页面都有自己单独的组件、路由、状态、入口等等&#xff0c;这里…

python数字图像处理基础(十一)——光流估计

目录 概念Lucas-Kanade算法函数表达式 概念 光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”&#xff0c;根据各个像素点的速度矢量特征&#xff0c;可以对图像进行动态分析&#xff0c;例如目标跟踪。要求如下&#xff1a; 亮度恒定&#xff1a;同一点随着时间…

FPGA之分布RAM(1)

SLICEM 资源可以实现分布式 RAM。可以实现的 RAM 类型&#xff1a; 单口 RAM 双端口 简单的双端口 四端口 下表给出了通过1SLICEM中的4个LUT可以实现的RAM类型 1.32 X2 Quad Port Distributed RAM 我们介绍过把 6 输入 LUT 当作 2 个 5输入 LUT 使用&#xff0c;在这里&a…

easyui渲染隐藏域<input type=“hidden“ />为textbox可作为分割条使用

最近在修改前端代码的时候&#xff0c;偶然发现使用javascript代码渲染的方式将<input type"hidden" />渲染为textbox时&#xff0c;会显示一个神奇的效果&#xff0c;这个textbox输入框并不会隐藏&#xff0c;而是显示未一个细条&#xff0c;博主发现非常适合…

【2015~2024】大牛直播SDK演化史

大牛直播SDK的由来 大牛直播SDK始于2015年&#xff0c;最初我们只是想做个低延迟的RTMP推拉流解决方案&#xff0c;用于移动单兵等毫秒级延迟的场景下&#xff0c;我们先是实现了Android平台RTMP直播推送模块&#xff0c;当我们用市面上可以找到的RTMP播放器测试时延的时候&am…

Debezium发布历史75

原文地址&#xff1a; https://debezium.io/blog/2019/10/22/audit-logs-with-kogito/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. 使用 Kogito 进行审核日志的管理服务 十月 22, 2019 作者&#xff1a; Mac…

三维重建(3)--单视几何

目录 一、无穷远点、无穷远线、无穷远平面 1、2D平面上的无穷远问题 2、3D平面上的无穷远问题 二、影消点与影消线 1、2D平面上的无穷远点&#xff0c;无穷远线变换 2、影消点 3、影消线 三、单视重构 1、两平行线夹角与影消线关系 2、单视图标定 一、无穷远点、无…

Apifox 国产接口自动化利器 之入门篇

Apifox 产品介绍 Apifox 是集 API 文档、API 调试、API Mock、API 自动化测试多项实用功能为一体的 API 管理平台&#xff0c;定位为 Postman Swagger Mock JMeter。旨在通过一套系统、一份数据&#xff0c;解决多个工具之间的数据同步问题。只需在 Apifox 中定义 API 文档…

Java 全栈知识点问题汇总(上)

Java 全栈知识点问题汇总&#xff08;上&#xff09; 1 Java 基础 1.1 语法基础 面向对象特性&#xff1f;a a b 与 a b 的区别3*0.1 0.3 将会返回什么? true 还是 false?能在 Switch 中使用 String 吗?对equals()和hashCode()的理解?final、finalize 和 finally 的不同…

Win10 打开文件突然鼠标变成一个蓝色大圈卡住点不了也打不开文件,重启电脑也是这样

环境: Win10 专业版 加密客户端环境 问题描述: Win10 打开桌面word文件突然鼠标变成一个蓝色大圈卡住点不了也打不开文件,重启电脑也是这样,只有蓝色圈变大没有鼠标指针出现圈卡着不会动,和那些有鼠标箭头加小蓝色圈不一样 解决方案: 某网上查看的,还是要自己排查…

OceanBase集群扩缩容

​ OceanBase 数据库采用 Shared-Nothing 架构&#xff0c;各个节点之间完全对等&#xff0c;每个节点都有自己的 SQL 引擎、存储引擎、事务引擎&#xff0c;天然支持多租户&#xff0c;租户间资源、数据隔离&#xff0c;集群运行的最小资源单元是Unit&#xff0c;每个租户在每…

LeetCode、2300. 咒语和药水的成功对数【中等,排序+二分】

文章目录 前言LeetCode、2300. 咒语和药水的成功对数【中等&#xff0c;排序二分】题目及类型思路及代码 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域…

聚铭入选“2023中国数字安全能力图谱(精选版)”安全运营领域

近日&#xff0c;国内权威数字安全领域第三方调研机构数世咨询正式发布《2023年度中国数字安全能力图谱&#xff08;精选版&#xff09;》。聚铭网络作为国内领先的安全运营商&#xff0c;凭借在细分领域突出优势&#xff0c;成功入选该图谱“安全运营”领域代表厂商。 据悉&a…

python tkinter 最简洁的计算器按钮排列

代码如下&#xff0c;只要再加上按键绑定事件函数&#xff0c;计算器既可使用了。 import tkinter as tk from tkinter.ttk import Separator,Buttonif __name__ __main__:Buttons [[%,CE,C,←],[1/x,x,√x,],[7, 8, 9, x],[4, 5, 6, -],[1, 2, 3, ],[, 0, ., ]]root tk.T…