Linux知识点 -- 网络基础(二)

news2024/9/24 7:16:18

Linux知识点 – 网络基础(二)(1)

文章目录

  • Linux知识点 -- 网络基础(二)(1)
  • 一、使用协议来实现一个网络版的计算器
    • 1.自定义协议
    • 2.守护进程
    • 3.使用json来完成序列化
  • 二、HTTP协议
    • 1.概念
    • 2.HTTP协议请求和响应的报文格式
    • 3.使用HTTP协议进行网络通信
    • 4.HTTP协议的方法
    • 5.HTTP协议的状态码
    • 6.HTTP协议的报头
    • 7.connetion选项


一、使用协议来实现一个网络版的计算器

1.自定义协议

定义结构体来表示我们需要交互的信息;
发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
这个过程叫做"序列化"和"反序列化”;

在这里插入图片描述

Sock.hpp
将套接字封装成对象,其中包含套接字的创建与连接成员函数

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "Log.hpp"

class Sock
{
private:
    const static int gbacklog = 20;

public:
    Sock() {}

    int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", listensock);
        return listensock;
    }

    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if (bind(sock, (struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }
    }

    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success");
    }

    // 一般经验:
    // const string& 输入型参数
    // string* 输出型参数
    // string& 输入输出型参数

    int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof src;
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }
        if(port)
        {
            *port = ntohs(src.sin_port);
        }
        if(ip)
        {
            *ip = inet_ntoa(src.sin_addr);
            return servicesock;
        }
    }

    bool Connect(int sock, const std::string& server_ip, const uint16_t& server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
        if(connect(sock, (struct sockaddr*)&server, sizeof server) == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    ~Sock() {}
};

TcpServer.hpp
封装TCP服务接口的类;
注意:类内回调函数由于参数有this指针,无法正-常回调,因此需要设置成static成员,再通过参数传进this指针,来访问类内非静态成员;

#pragma once

#include "Sock.hpp"
#include <vector>
#include <functional>
#include <pthread.h>

namespace ns_tcpserver
{
    using func_t = std::function<void(int)>;
    class TcpServer;
    class ThreadData // 传入回调函数的参数
    {
    public:
        ThreadData(int sock, TcpServer *server)
            : _sock(sock), _server(server)
        {
        }
        ~ThreadData() {}

    public:
        int _sock;
        TcpServer *_server; // 里面有TcpServer对象的指针,由于回调函数是静态成员函数,无法访问非静态成员
                            // 这里的TcpServer对象指针是用来在回调函数中访问非静态成员的
    };

    class TcpServer
    {
    private:
        //如果是类内成员函数,参数中是有this指针的,多线程回调会出问题
        //因此需设置成静态成员,才可以回调
        static void* ThreadRoutine(void* args)
        {
            pthread_detach(pthread_self());//线程分离
            ThreadData* td = static_cast<ThreadData*>(args);//类型转换
            td->_server->Excute(td->_sock);//通过对象this指针调用成员函数
            close(td->_sock);
            return nullptr;
        }
    public:
        TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
        {
            // 创建套接字,绑定并监听
            _listensock = _sock.Socket();
            _sock.Bind(_listensock, port, ip);
            _sock.Listen(_listensock);
        }

        // 将服务请求放入函数队列
        void BindService(func_t func)
        {
            _func.push_back(func);
        }

        // 执行服务
        void Excute(int sock)
        {
            for (auto &f : _func)
            {
                f(sock);
            }
        }

        void Start()
        {
            for (;;)
            {
                std::string cli_ip;
                uint16_t cli_port;
                int sock = _sock.Accept(_listensock, &cli_ip, &cli_port);
                if (sock == -1)
                {
                    continue;
                }
                logMessage(NORMAL, "create new link succsee, sock: %d", sock);

                // 多线程处理请求
                pthread_t tid;
                ThreadData *td = new ThreadData(sock, this);
                pthread_create(&tid, nullptr, ThreadRoutine, td);
            }
        }

        ~TcpServer()
        {
            if (_listensock >= 0)
            {
                close(_listensock);
            }
        }

    private:
        int _listensock;
        Sock _sock;
        std::vector<func_t> _func; // 回调函数列表
    };

}

Protocol.hpp
定制协议:
分别有计算请求的序列化和计算结果的序列化;

  • TCP协议的读写接口(read和write)都是将数据拷贝到缓冲区或者从缓冲区拷贝出来,并不是直接发送到对方主机;发送给对方主机是由TCP传输控制协议决定的
  • 由于TCP是面向字节流的协议,因此,发送和接受的次数,每次发送多少字符,都不受控制(UDP协议每次发送和接受的都是完整的报文),有可能每次接收到的不一定是完整的报文,也有可能一次读取了多个报文,所以需要自己定制协议解包代码;在读取时不能简单地receive,而需要对读取的数据进行解析;
  • 自主定制的协议使用"length\r\nx_ op_ y_\r\n"协议,前面加上数据长度;
    在这里插入图片描述
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include "Sock.hpp"

namespace ns_protocol
{
#define MYSELF 1
#define SPACE " "
#define SPACE_LEN strlen(SPACE)

#define SEP "\r\n"
#define SEP_LEN strlen(SEP) // 不能是sizeof,会统计\0

    class Request // 计算请求序列
    {
    public:
        Request() {}
        Request(int x, int y, char op)
            : _x(x), _y(y), _op(op)
        {
        }

        ~Request() {}

        std::string Serialize() // 序列化
        {
#ifdef MYSELF
            // 使用自定义序列化方案
            // 将请求传换成string:_x _op _y的形式
            std::string str;
            str = std::to_string(_x);
            str += SPACE;
            str += _op;
            str += SPACE;
            str += std::to_string(_y);
            return str;
#else
            // 使用现成方案
            std::cout << "to do" << std::endl;
#endif
        }

        bool Deserialized(const std::string &str) // 反序列化
        {
#ifdef MYSELF
            std::size_t left = str.find(SPACE);
            if (left == std::string::npos)
            {
                return false;
            }
            std::size_t right = str.rfind(SPACE);
            if (right == std::string::npos)
            {
                return false;
            }
            _x = atoi(str.substr(0, left).c_str());
            _y = atoi(str.substr(right + SPACE_LEN).c_str());
            if (left + SPACE_LEN > str.size())
            {
                return false;
            }
            else
            {
                _op = str[left + SPACE_LEN];
            }
            return true;

#else
            std::cout << "to do" << std::endl;
#endif
        }

    public:
        int _x;
        int _y;
        char _op; // + - * / %
    };

    class Response // 计算结果响应序列
    {
    public:
        Response() {}

        Response(int result, int code)
            : _result(result), _code(code)
        {
        }

        ~Response() {}

        std::string Serialize() // 序列化:_code _result
        {
#ifdef MYSELF
            // 使用自定义序列化方案
            // 将请求传换成string:_x _op _y的形式
            std::string str;
            str = std::to_string(_code);
            str += SPACE;
            str += std::to_string(_result);
            return str;
#else
            // 使用现成方案
            std::cout << "to do" << std::endl;
#endif
        }

        bool Deserialized(const std::string &str) // 反序列化
        {
#ifdef MYSELF
            std::size_t pos = str.find(SPACE);
            if (pos == std::string::npos)
            {
                return false;
            }

            _code = atoi(str.substr(0, pos).c_str());
            _code = atoi(str.substr(pos + SPACE_LEN).c_str());
            return true;

#else
            std::cout << "to do" << std::endl;
#endif
        }

    public:
        int _result; // 计算结果
        int _code;   // 计算结果的状态码:运算是否成功
    };

    // 临时方案
    // 期望返回的是一个完整地报文
    bool Recv(int sock, std::string* out)
    {
        //TCP是面向字节流的,无法保证独到的inbuffer是一个完整地请求
        //因此需要解析协议,查看数据是否完整
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof buffer - 1, 0);
        if (s > 0)
        {
            buffer[s] = 0;
            *out += buffer;
        }
        else if(s == 0)
        {
            //客户端退出
            return false;
        }
        else
        {
            //读取错误
            return false;
        }
        return true;
    }

    void Send(int sock, const std::string str)
    {
        send(sock, str.c_str(), str.size(), 0);
    }

    //添加报文
    // "XXXXXX"
    // "123\r\nXXXXXX\r\n"
    std::string Encode(std::string &s)
    {
        std::string new_package = std::to_string(s.size());
        new_package += SEP;
        new_package += s;
        new_package += SEP;
        return new_package;
    }


    //解析报文
    //规定报文的格式为:"length\r\nx_ op_ y_\r\n..."
    std::string Decode(std::string& buffer)
    {
        std::size_t pos = buffer.find(SEP);
        if(pos == std::string::npos)
        {
            return "";//如果没找到分隔符,返回空串
        }
        int size = atoi(buffer.substr(0, pos).c_str());
        int surplus = buffer.size() - pos - 2*SEP_LEN;
        if(surplus >= size)
        {
            //至少有一份合法的报文,可以手动提取了
            buffer.erase(0, pos + SEP_LEN);
            std::string s = buffer.substr(0, size);
            buffer.erase(0, size + SEP_LEN);
            return s;
        }
        else
        {
            return "";//没有完整地报文,继续接收
        }
    }
}

CalServer.cc
计算服务

  • 服务器运行时,对端如果直接关闭,我们收到的就是空的信息,send的也是已经关闭的文件描述符,就可能导致服务器关闭;
    方案一:对SIGPIPE信号忽略,这样即使正在发送信息时对方关闭,也可以保证服务器不退出;

    在这里插入图片描述
    方案二:接收到信息时,需要判断信息的完整性,读取是否成功
  • 一般经验:在server编写的时候,要有较为严谨的判断逻辑;
    一般服务器都是要忽略SIGPIPE信号的,防止在运行过程中出现非法写入的问题;
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <memory>
#include <signal.h>

using namespace ns_protocol;
using namespace ns_tcpserver;

static void Usage(const std::string &process)
{
    std::cout << "\nUsage: " << process << " port\n"
              << std::endl;
}

// 进行计算
static Response calculatorHelper(const Request &req)
{
    Response resp(0, 0);
    switch (req._op)
    {
    case '+':
        resp._result = req._x + req._y;
        break;
    case '-':
        resp._result = req._x - req._y;
        break;
    case '*':
        resp._result = req._x * req._y;
        break;
    case '/':
        if (0 == req._y)
            resp._code = 1;
        else
            resp._result = req._x / req._y;
        break;
    case '%':
        if (0 == req._y)
            resp._code = 2;
        else
            resp._result = req._x % req._y;
        break;
    default:
        resp._code = 3;
        break;
    }
    return resp;
}

void calculator(int sock)
{
    std::string inbuffer;//每次读取到的缓冲区
    while (true)
    {
        //1.读取成功
        bool res = Recv(sock, &inbuffer); // 读到了一个请求
        if(!res)
        {
            break;
        }
        //2.协议解析,保证得到一个完整的报文
        std::string package = Decode(inbuffer);
        if(package.empty())
        {
            continue; //如果读到的报文不完整,继续读取
        }
        logMessage(NORMAL, "%s", package.c_str());
        //3.保证该报文是一个完整的报文
        Request req;
        //4.反序列化,字节流->结构化
        req.Deserialized(package); // 反序列化
        //5.业务逻辑
        Response resp = calculatorHelper(req);
        //6.序列化
        std::string respString = resp.Serialize();//对计算结果进行序列化
        //7.添加长度信息,形成一个完整的报文
        respString = Encode(respString);
        //8.发送
        Send(sock, respString);//将结果序列发回给客户端
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    signal(SIGPIPE, SIG_IGN);

    std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));
    server->BindService(calculator);
    server->Start();

    return 0;
}

CalClient.cc
客户端

#include <iostream>
#include "Sock.hpp"
#include "Protocol.hpp"

using namespace ns_protocol;
static void Usage(const std::string &process)
{
    std::cout << "\nUsage: " << process << " serverIp serverPort\n"
              << std::endl;
}
// ./client server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);
    Sock sock;
    int sockfd = sock.Socket();
    if (!sock.Connect(sockfd, server_ip, server_port))
    {
        std::cerr << "Connect error" << std::endl;
        exit(2);
    }
    bool quit = false;
    std::string buffer;
    while (!quit)
    {
        // 1. 获取需求
        Request req;
        std::cout << "Please Enter # ";
        std::cin >> req._x >> req._op >> req._y;
        // 2. 序列化
        std::string s = req.Serialize();
        // std::string temp = s;
        // 3. 添加长度报头
        s = Encode(s);
        // 4. 发送给服务端
        Send(sockfd, s);
        // 5. 正常读取
        while (true)
        {
            bool res = Recv(sockfd, &buffer);
            if (!res)
            {
                quit = true;
                break;
            }
            std::string package = Decode(buffer);
            if (package.empty())
                continue;
            Response resp;
            resp.Deserialized(package);
            std::string err;
            switch (resp._code)
            {
            case 1:
                err = "除0错误";
                break;
            case 2:
                err = "模0错误";
                break;
            case 3:
                err = "非法操作";
                break;
            default:
                std::cout << resp._result << " [success]" << std::endl;
                break;
            }
            if(!err.empty()) std::cerr << err << std::endl;
            // sleep(1);
            break;//完整读取一个报文就退出
        }
    }
    close(sockfd);
    return 0;
}

运行结果:
在这里插入图片描述

2.守护进程

  • (1)前台进程:和终端关联的进程;在终端下能读取输入并作出反应(如bash);
    (2)任何xshell登陆,只允许一个前台进程和多个后台进程;
    (3)进程除了有自己的pid, ppid, 还有一个组ID;
    (4)在命令行中,同时用管道启动多个进程,多个进程是兄弟关系,父进程都是bash ->可以用匿名管道来进行通信;
    (5)而同时被创建的多个进程可以成为一个进程组的概念,组长一般是第一个进程
    (6)任何一次登陆,登陆的用户,需要有多个进程(组),来给这个用户提供服务的(bash),用户自己可以启动很多进程,或者进程组。我们把给用户提供服务的进程,或者用户自己启动的所有的进程或者服务,整体都是要属于一个叫做会话的机制中的。
    (7)当用户退出登陆的时候,整个会话中的进程组都会结束;
    想让一个进程不再属于用户的会话,而是自成一个会话,这个进程称为守护进程
    (8)如何将进程变为守护进程->setsid()接口;
    (9)setsid要成功被调用,必须保证当前进程不是进程组的组长,可以通过fork创建的子进程实现;
    (10)守护进程不能直接向显示器打印消息,一旦打印,会被暂停,终止;

  • 如何在Linux正确的写一个让进程守护进程化的代码:
    写一个函数,让进程调用这个函数,自动变成守护进程;

  • /dev/null文件
    可以理解为一个文件黑洞,可以向里面打印数据,也可以从里面读取,但都不会有实际的数据输入输出;
    因此可以将标准输入,标准输出,标准错误重定向到devnull文件中;

Daemon.hpp

#pragma once
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void MyDaemon()
{
    //1.忽略信号,SIPPIPE, SIGCHID
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    //2.不要让自己成为组长
    if(fork() > 0)
    {
        exit(0);//父进程退出,剩下子进程其实是孤儿进程
    }
    //3.调用setsid
    setsid();
    //4.标准输入,标准输出,标准错误的重定向,守护进程不能直接向显示器打印消息
    int devnull = open("/dev/null", O_RDONLY | O_WRONLY);
    if(devnull > 0)
    {
        dup2(0, devnull);
        dup2(1, devnull);
        dup2(2, devnull);
        close(devnull);
    }
}

CalServer.cc
在服务器进程中调用守护进程函数,让服务器进程成为守护进程;

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <memory>
#include <signal.h>
#include "Daemon.hpp"

using namespace ns_protocol;
using namespace ns_tcpserver;

static void Usage(const std::string &process)
{
    std::cout << "\nUsage: " << process << " port\n"
              << std::endl;
}

// 进行计算
static Response calculatorHelper(const Request &req)
{
    Response resp(0, 0);
    switch (req._op)
    {
    case '+':
        resp._result = req._x + req._y;
        break;
    case '-':
        resp._result = req._x - req._y;
        break;
    case '*':
        resp._result = req._x * req._y;
        break;
    case '/':
        if (0 == req._y)
            resp._code = 1;
        else
            resp._result = req._x / req._y;
        break;
    case '%':
        if (0 == req._y)
            resp._code = 2;
        else
            resp._result = req._x % req._y;
        break;
    default:
        resp._code = 3;
        break;
    }
    return resp;
}

void calculator(int sock)
{
    std::string inbuffer;//每次读取到的缓冲区
    while (true)
    {
        //1.读取成功
        bool res = Recv(sock, &inbuffer); // 读到了一个请求
        if(!res)
        {
            break;
        }
        //2.协议解析,保证得到一个完整的报文
        std::string package = Decode(inbuffer);
        if(package.empty())
        {
            continue; //如果读到的报文不完整,继续读取
        }
        logMessage(NORMAL, "%s", package.c_str());
        //3.保证该报文是一个完整的报文
        Request req;
        //4.反序列化,字节流->结构化
        req.Deserialized(package); // 反序列化
        //5.业务逻辑
        Response resp = calculatorHelper(req);
        //6.序列化
        std::string respString = resp.Serialize();//对计算结果进行序列化
        //7.添加长度信息,形成一个完整的报文
        respString = Encode(respString);
        //8.发送
        Send(sock, respString);//将结果序列发回给客户端
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    signal(SIGPIPE, SIG_IGN);
    MyDaemon();//让该进程成为守护进程,自成一个会话

    std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));
    server->BindService(calculator);
    server->Start();

    return 0;
}

运行结果:
在这里插入图片描述
注:
在这里插入图片描述
守护进程实际上是孤儿进程,但是没有被系统领养,而是自成会话

这样下来,服务器进程成为了守护进程,自成一个会话,即使用户退出登录,该进程也不会退出;

3.使用json来完成序列化

json:网络通信的格式

  • 在Linux上安装json:
    在这里插入图片描述
  • json实际上是一个结构化数据格式,里面是很多的kv结构:
    在这里插入图片描述
  • json库的使用:
    在这里插入图片描述
    StyleWriter对象,两个kv对象之间有换行符;
    StyleWriter对象的write函数会将root中的kv内容直接转换为对应的string;

    运行结果:
    在这里插入图片描述
    在这里插入图片描述
    FastWriter对象,中间没有换行符
    运行结果:
    在这里插入图片描述
    json里面是可以套json的
    在这里插入图片描述

使用json协议完成序列化和反序列化:
由于使用的是非cpp官方库,因此需要添加编译选项:
makefile

.PHONY:all
all:CalClient CalServer

CalClient:CalClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp
CalServer:CalServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp

.PHONY:clean
clean:
	rm -f CalClient CalServer

Protocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include "Sock.hpp"
#include <jsoncpp/json/json.h>

namespace ns_protocol
{
//#define MYSELF 1
#define SPACE " "
#define SPACE_LEN strlen(SPACE)

#define SEP "\r\n"
#define SEP_LEN strlen(SEP) // 不能是sizeof,会统计\0

    class Request // 计算请求序列
    {
    public:
        Request() {}
        Request(int x, int y, char op)
            : _x(x), _y(y), _op(op)
        {
        }

        ~Request() {}

        std::string Serialize() // 序列化
        {
#ifdef MYSELF
            // 使用自定义序列化方案
            // 将请求传换成string:_x _op _y的形式
            std::string str;
            str = std::to_string(_x);
            str += SPACE;
            str += _op;
            str += SPACE;
            str += std::to_string(_y);
            return str;
#else
            // 使用现成方案
            Json::Value root;
            root["x"] = _x;
            root["y"] = _y;
            root["op"] = _op;
            Json::FastWriter writer;
            return writer.write(root);  
#endif
        }

        bool Deserialized(const std::string &str) // 反序列化
        {
#ifdef MYSELF
            std::size_t left = str.find(SPACE);
            if (left == std::string::npos)
            {
                return false;
            }
            std::size_t right = str.rfind(SPACE);
            if (right == std::string::npos)
            {
                return false;
            }
            _x = atoi(str.substr(0, left).c_str());
            _y = atoi(str.substr(right + SPACE_LEN).c_str());
            if (left + SPACE_LEN > str.size())
            {
                return false;
            }
            else
            {
                _op = str[left + SPACE_LEN];
            }
            return true;

#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(str, root);//parse函数能够将序列化的json字符串直接读取到Value对象中
            _x = root["x"].asInt();
            _x = root["y"].asInt();
            _x = root["op"].asInt();//char类型实质也是int
            return true;
#endif
        }

    public:
        int _x;
        int _y;
        char _op; // + - * / %
    };

    class Response // 计算结果响应序列
    {
    public:
        Response() {}

        Response(int result, int code)
            : _result(result), _code(code)
        {
        }

        ~Response() {}

        std::string Serialize() // 序列化:_code _result
        {
#ifdef MYSELF
            // 使用自定义序列化方案
            // 将请求传换成string:_x _op _y的形式
            std::string str;
            str = std::to_string(_code);
            str += SPACE;
            str += std::to_string(_result);
            return str;
#else
            // 使用现成方案
            Json::Value root;
            root["code"] = _code;
            root["result"] = _result;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }

        bool Deserialized(const std::string &str) // 反序列化
        {
#ifdef MYSELF
            std::size_t pos = str.find(SPACE);
            if (pos == std::string::npos)
            {
                return false;
            }

            _code = atoi(str.substr(0, pos).c_str());
            _result = atoi(str.substr(pos + SPACE_LEN).c_str());
            return true;

#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(str, root);
            _code = root["code"].asInt();
            _result = root["result"].asInt();
            return true;
#endif
        }

    public:
        int _result; // 计算结果
        int _code;   // 计算结果的状态码:运算是否成功
    };

    // 临时方案
    // 期望返回的是一个完整地报文
    bool Recv(int sock, std::string* out)
    {
        //TCP是面向字节流的,无法保证独到的inbuffer是一个完整地请求
        //因此需要解析协议,查看数据是否完整
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (s > 0)
        {
            buffer[s] = 0;
            *out += buffer;
        }
        else if(s == 0)
        {
            //客户端退出
            return false;
        }
        else
        {
            //读取错误
            return false;
        }
        return true;
    }

    void Send(int sock, const std::string str)
    {
        int n = send(sock, str.c_str(), str.size(), 0);
        if(n < 0)
        {
            std::cout << "send error" << std::endl;
        }
    }

    //添加报头
    // "XXXXXX"
    // "123\r\nXXXXXX\r\n"
    std::string Encode(std::string &s)
    {
        std::string new_package = std::to_string(s.size());
        new_package += SEP;
        new_package += s;
        new_package += SEP;
        return new_package;
    }


    //解析报文
    //规定报文的格式为:"length\r\nx_ op_ y_\r\n..."
    std::string Decode(std::string& buffer)
    {
        std::size_t pos = buffer.find(SEP);
        if(pos == std::string::npos)
        {
            return "";//如果没找到分隔符,返回空串
        }
        int size = atoi(buffer.substr(0, pos).c_str());
        int surplus = buffer.size() - pos - 2*SEP_LEN;
        if(surplus >= size)
        {
            //至少有一份合法的报文,可以手动提取了
            buffer.erase(0, pos + SEP_LEN);
            std::string s = buffer.substr(0, size);
            buffer.erase(0, size + SEP_LEN);
            return s;
        }
        else
        {
            return "";//没有完整地报文,继续接收
        }
    }

}

运行结果:
在这里插入图片描述

二、HTTP协议

1.概念

  • 应用层:就是程序员基于socket接口之上编写的具体逻辑,有很多和文本处理相关的工作;http协议一定会有大量的文本分析和处理;

  • URL:我们平时说的网址,其结构如下;
    在这里插入图片描述
    其中,服务器地址IP就是域名,用来标识唯一的主机;冒号后面是端口号,标识特定主机上的特定进程;
    端口号后面是带层次的文件路径,其中第一个文件夹叫做web根目录;文件路径标识客户想访问的资源路径;
    URL:union resource local统一资源定位符,代表本次访问请求的资源位置,定位互联网中唯一的一份资源;
    在用户访问网络资源时,先通过url找到服务器上的特定文件资源,在进行读取或写入;

  • 如果用户想在url中包含url本身作为特殊字符使用的字符时,浏览器会自动对该字符进行编码,在服务端收到后,需要转回特殊字符;
    在这里插入图片描述在这里插入图片描述

2.HTTP协议请求和响应的报文格式

在这里插入图片描述
单纯在报文角度,http可以是基于行的文本协议;

  • 请求报文:
    请求行:方法 URL 协议版本
    http的方法为:
    在这里插入图片描述
    请求报头Header:多行kv结构,都是属性;
    空行:用来区分报头和有效载荷;
    请求正文(可以没有);

  • 响应报文:
    状态行:协议版本 状态码 状态码描述;
    响应报头;
    空行;
    响应正文;

3.使用HTTP协议进行网络通信

Log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./http.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif
    // va_list ap;
    // va_start(ap, format);
    // while()
    // int x = va_arg(ap, int);
    // va_end(ap); //ap=nullptr
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    // struct tm *localtime = localtime(&timestamp);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // vprintf(format, args);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    FILE *fp = fopen(LOGFILE, "a");
    // printf("%s%s\n", stdBuffer, logBuffer);
    fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    fclose(fp);
}

Sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "Log.hpp"

class Sock
{
private:
    const static int gbacklog = 20;

public:
    Sock() {}
    int Socket()
    {
        int listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (listensock < 0)
        {
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", listensock);
        return listensock;
    }
    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &local.sin_addr);
        if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }
    }
    void Listen(int sock)
    {
        if (listen(sock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(NORMAL, "init server success");
    }
    // 一般经验
    // const std::string &: 输入型参数
    // std::string *: 输出型参数
    // std::string &: 输入输出型参数
    int Accept(int listensock, std::string *ip, uint16_t *port)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr *)&src, &len);
        if (servicesock < 0)
        {
            logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }
        if(port) *port = ntohs(src.sin_port);
        if(ip) *ip = inet_ntoa(src.sin_addr);
        return servicesock;
    }
    bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());

        if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0) return true;
        else return false;
    }
    ~Sock() {}
};

Usage.hpp

#pragma once
#include <iostream>
#include <string>
void Usage(std::string proc)
{
    std::cout << "\nUsage: " << proc <<  " port\n" << std::endl;
}

Util.hpp
工具类,分割字符串

#pragma once

#include <iostream>
#include <vector>

class Util
{
public:
    // aaaa\r\nbbbbb\r\nccc\r\n\r\n
    static void cutString(std::string s, const std::string &sep, std::vector<std::string> *out)
    {
        std::size_t start = 0;
        while (start < s.size())
        {
            auto pos = s.find(sep, start);
            if (pos == std::string::npos) break;
            std::string sub = s.substr(start, pos - start);
            // std::cout << "----" << sub << std::endl;
            out->push_back(sub);
            start += sub.size();
            start += sep.size();
        }
        if(start < s.size()) out->push_back(s.substr(start));
    }
};

HttpServer.hpp

#pragma once

#include <iostream>
#include <signal.h>
#include <functional>
#include "Sock.hpp"

class HttpServer
{
public:
    using func_t = std::function<void(int)>;
private:
    int listensock_;
    uint16_t port_;
    Sock sock;
    func_t func_;
public:
    HttpServer(const uint16_t &port, func_t func): port_(port),func_(func)
    {
        listensock_ = sock.Socket();
        sock.Bind(listensock_, port_);
        sock.Listen(listensock_);
    }
    void Start()
    {
        signal(SIGCHLD, SIG_IGN);
        for( ; ; )
        {
            std::string clientIp;
            uint16_t clientPort = 0;
            int sockfd = sock.Accept(listensock_, &clientIp, &clientPort);
            if(sockfd < 0) continue;
            if(fork() == 0)
            {
                close(listensock_);
                func_(sockfd);
                close(sockfd);
                exit(0);
            }
            close(sockfd);
        }
    }
    ~HttpServer()
    {
        if(listensock_ >= 0) close(listensock_);
    }
};

HttpServer.cc
这里是主要的对http协议进行解析的代码,逐行解析,提取首行url,访问目标资源;

#include <iostream>
#include <memory>
#include <cassert>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "HttpServer.hpp"
#include "Usage.hpp"
#include "Util.hpp"
// 一般http都要有自己的web根目录
#define ROOT "./wwwroot" // ./wwwroot/index.html
// 如果客户端只请求了一个/,我们返回默认首页
#define HOMEPAGE "index.html"
void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 for test
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        // std::cout << buffer << "--------------------\n" << std::endl;
    }
    std::vector<std::string> vline; // 取出http请求的每一行
    Util::cutString(buffer, "\n", &vline);
    std::vector<std::string> vblock; // 取出第一行的每一个子串
    Util::cutString(vline[0], " ", &vblock);
    std::string file = vblock[1]; // 请求的资源
    std::string target = ROOT;
    if(file == "/") file = "/index.html";
    target += file; //请求的资源从web根目录下开始,如果不指定web根目录,就会访问Linux根目录
    std::cout << target << std::endl;
    std::string content;
    std::ifstream in(target); // 打开文件
    if(in.is_open())
    {
        std::string line;
        while(std::getline(in, line))
        {
            content += line;
        }
        in.close();
    }
    std::string HttpResponse;
    if(content.empty()) HttpResponse = "HTTP/1.1 404 NotFound\r\n";
    else HttpResponse = "HTTP/1.1 200 OK\r\n";
    HttpResponse += "\r\n";
    HttpResponse += content;
        // std::cout << "####start################" << std::endl;
        // for(auto &iter : vblock)
        // {
        //     std::cout << "---" << iter << "\n" << std::endl;
        // }
        // std::cout << "#####end###############" << std::endl;
        // 2. 试着构建一个http的响应
    send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0);
}
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::unique_ptr<HttpServer> httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest));
    httpserver->Start();
    return 0;
}

在目录下创建web根目录wwwroot,里面创建首页index.html;
在这里插入图片描述
index.html
在vscode下装插件,!table会出现网页模板;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lmx</title>
</head>
<body>
    <h3>这个一个Linux课程</h3>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
    <p>我是一个Linux的学习者,我正在进行http的测试工作!!</p>
</body>
</html>

运行结果:
在这里插入图片描述

4.HTTP协议的方法

在这里插入图片描述
其中最常用的是GET和POST方法;

  • 用户数据提交到服务器的流程:
    用户发起申请,形成表单,指明提交方法,表单中的数据,会被转成http request的一部分,之后收集用户数据,并把用户数据推送给服务器;

  • GET方法可以将数据从服务器端拿到客户端,也可以将客户端的数据提交到服务器;
    使用GET方法提交请求
    web目录结构:
    在这里插入图片描述
    index.html
    使用GET方法将进行提交
    在这里插入图片描述
    **input是按钮,其中的action是点击按钮后访问的文件,method是方法,这里是GET;
    下面的Username和Password是kv结构输入框,type是内容,name是标签;
    **
    运行结果:
    在这里插入图片描述
    使用浏览器访问建立好的网页,这是一个可以登陆的界面;
    在这里插入图片描述
    输入好用户名和密码后,点击登录;
    在这里插入图片描述
    跳转到如上界面,登陆的时候其实就是把用户信息提交给服务器;
    在这里插入图片描述
    在上面的网址栏可以看到自己输入的用户名和密码,?后面是参数,前面是提交的地址,就是将参数提交到目标文件中;
    服务器收到的请求:
    在这里插入图片描述
    这是因为get方法通过url传参,并将参数回显到url中;

  • POST方法用于将客户端的数据提交到服务器;
    使用POST方法提交请求
    insex.html
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
    点击登录:
    在这里插入图片描述
    服务器收到的请求:
    在这里插入图片描述
    POST是不会通过URL传参的,它通过正文传参;

总结

  • GET方法通过URL传参,回显输入的私密信息,不够私密;
  • POST方法通过正文传参,不会回显私密信息,私密性有保证;
  • 私密性不是安全性;
  • 登录和注册一般常用的是POST方法;
    内容较大也建议使用POST方法,因为POST方法里面有正文长度,方便整段读取;

5.HTTP协议的状态码

在这里插入图片描述

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

  • 重定向当网页进行请求时,需要跳转到其他网页;
    301:永久移动,直接重定向到另一个网也,不会返回原来的网页,影响用户后续的请求策略;
    302:临时移动,临时重定向到另一个网页,比如登陆界面,处理好后再返回原始网页,不影响用户后续的请求策略;
    307:临时重定向;

  • 重定向过程
    客户端向服务器发起http请求 -> 服务器返回30X重定向状态码,并携带新的网页地址信息 -> 客户端浏览器拿到新的地址后,自动向新的地址发起请求;
    在这里插入图片描述

重定向实验
HttpServer.cc
在这里插入图片描述
如果读取的文件不存在,返回的状态码为301,会进行重定向操作;
其中Location属性就是重定向后的目标文件地址;

#include <iostream>
#include <memory>
#include <cassert>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "HttpServer.hpp"
#include "Usage.hpp"
#include "Util.hpp"
// 一般http都要有自己的web根目录
#define ROOT "./wwwroot" // ./wwwroot/index.html
// 如果客户端只请求了一个/,我们返回默认首页
#define HOMEPAGE "index.html"
void HandlerHttpRequest(int sockfd)
{
    // 1. 读取请求 for test
    char buffer[10240];
    ssize_t s = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (s > 0)
    {
        buffer[s] = 0;
        std::cout << buffer << "\n--------------------\n"
                  << std::endl;
    }

    std::vector<std::string> vline; // 取出http请求的每一行
    Util::cutString(buffer, "\n", &vline);

    std::vector<std::string> vblock; // 取出第一行的每一个子串
    Util::cutString(vline[0], " ", &vblock);

    std::string file = vblock[1]; // 请求的资源
    std::string target = ROOT;

    if (file == "/")
        file = "/index.html";

    target += file; // 请求的资源从web根目录下开始,如果不指定web根目录,就会访问Linux根目录
    std::cout << target << std::endl;

    std::string content;      // 文件中的内容
    std::ifstream in(target); // 打开文件
    if (in.is_open())
    {
        std::string line;
        while (std::getline(in, line))
        {
            content += line;
        }
        in.close();
    }

    std::string HttpResponse;
    if (content.empty())
    {
        HttpResponse = "HTTP/1.1 301 NotFound\r\n";
        HttpResponse += "Location: http://47.115.213.66:8080/a/b/404.html\r\n";
    }
    else
        HttpResponse = "HTTP/1.1 200 OK\r\n";
    HttpResponse += "\r\n";
    HttpResponse += content;
    // std::cout << "####start################" << std::endl;
    // for(auto &iter : vblock)
    // {
    //     std::cout << "---" << iter << "\n" << std::endl;
    // }
    // std::cout << "#####end###############" << std::endl;
    // 2. 试着构建一个http的响应
    send(sockfd, HttpResponse.c_str(), HttpResponse.size(), 0);
}
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::unique_ptr<HttpServer> httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest));
    httpserver->Start();
    return 0;
}

index.html
客户端点击登陆后,会跳转到/a/b/notexit.html这个地址的文件;

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>lmx</title>
</head>

<body>
    <h3>Hello Guests!</h3>
    <form name="input" action="/a/b/notexit.html" method="POST">
        Username: <input type="text" name="user"> <br/>
        Password: <input type="password" name="pwd"> <br/>
        <input type="submit" value="登陆">
    </form>
</body>

</html>

404.html
重定向的目标文件;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>不存在</title>
</head>
<body>
    <h2>你访问的页面不存在</h2>
</body>
</html>

运行结果:
客户端访问网页HOME地址:
在这里插入图片描述
点击登陆后,访问/a/b/notexit.html这个地址的文件,但是这个文件是不存在的,文件读取返回结果为空,状态码为301,触发重定向;
在这里插入图片描述
重定向到了a/b/404.html这个文件;

6.HTTP协议的报头

Content-Type:数据类型(text/html等);
Content-Length:Body(正文)的长度;
Host:客户端告知服务器所请求的资源是在哪个主机的哪个端口上;
User-Agent:声明用户的操作系统和浏览器版本信息;
referer:当前页面是从哪个页面跳转过来的;
location:搭配3xx状态码使用,告诉客户端接下来要去哪里访问;
Cookie:用于在客户端存储少量信息.通常用于实现会话(session)的功能;

  • Content-Type、Content-Length
    添加内容类型及正文长度报头;
    在这里插入图片描述
  • Cookie会话管理
    http的特征:
    a.简单快速;
    b.无连接,指http不维护连接,连接是由TCP维护的;
    c.无状态,http不会记录用户曾经请求的网页,不会对用户的行为做记录;

    http协议是无状态的,但是我们平常在浏览器进行访问网页时,一般网站是会记录下我们的状态的,这是因为http协议为了支持常规用户的会话管理,支持两个报头属性Cookie(请求)、Set-Cookie(响应)
    用户登录后,曾经输入的用户名和密码等信息会保存为一个文件,在今后每次的http请求中,每次都会携带这个文件中的账户密码内容,这个文件就是cookie文件;
    cookie文件的创建与使用流程:
    当用户访问网站后,在网站上输入用户密码信息,之后服务器会将用户信息返回给客户端,客户端的浏览器会将用户信息保存,形成cookie文件,之后用户每次访问该网站,都会将cookie文件再次上传到服务器,进行用户星系比对,不用每次都重新输入信息了;
    在这里插入图片描述
    但是cookie文件中是将用户信息明文保存的,如果被黑客注入木马病毒,是能够盗取用户的私密信息;
    现在的新cookie方案:在网站认证用户信息后,服务端会形成一个用户唯一ID,session id,并返回给用户端,保存到cookie文件中;这样每次用户访问网站,上传的cookie文件都是用户在网站形成的唯一session id,就算被盗取,也不会暴露用户的私密信息;

验证cookie
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

7.connetion选项

在这里插入图片描述
keep-alive:长连接,网页该有的资源通过一个连接全部拿到;
close:短连接,处理完一个http请求后,就将连接关掉,每次都要建立连接获取图片等资源;

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

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

相关文章

Redis 事务实现原理

1. 什么是Redis事务 提到事务,我们可能马上会想到传统的关系型数据库中的事务,客户端首先向服务器发送BEGIN开启事务,然后执行读写操作,最后用户发送 COMMIT 或者 ROLLBACK 来提交或者回滚之前的操作。 但是Redis中的事务与关系型数据库是不一样的,Redis 通过 MULTI 命令开始…

GB28181学习(四)——网络设备信息查询

要求 源设备向目标设备发送信息查询命令&#xff0c;目标设备将结果通过查询应答命令返回给源设备&#xff1b;设备信息查询命令包括&#xff1a; 设备目录设备信息设备状态信息设备配置预置位、看守位巡航轨迹列表巡航轨迹PTZ精准状态存储卡状态等 信息查询的范围&#xff1a…

内网穿透对开发人员有什么作用?要怎么实现?快解析

在当今快节奏的互联网时代&#xff0c;软件开发人员需要时刻与内外部服务器进行通信和调试&#xff0c;只有这样才能带来良好的工作速度&#xff0c;顺利推动项目的进展。然而&#xff0c;由于受到网络环境的限制&#xff0c;有时候我们可能无法直接访问公司内网的服务器&#…

差分方程模型:蛛网模型

在完全竞争的市场经济中&#xff0c;一个时期某种消费品如猪肉的上市量远远大于需求量&#xff0c;由于销售不畅导致价格下降&#xff0c;生产者发现养猪赔钱&#xff0c;于是转而经营其它农副产品。过一段时间猪肉上市量就会下降&#xff0c;此时供不应求导致价格上涨&#xf…

【MySQL】MySQL索引的定义、分类、Explain、索引失效和优化

索引的介绍 索引是帮助MySQL高效获取数据的数据结构 MySQL在存储数据之外&#xff0c;数据库系统中还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种引用(指向)表中的数据&#xff0c;这样我们就可以通过数据结构上实现的高级查找算法来快速找到我们想要的数…

设计原则SOLID看这一篇就够了

文章目录 1.引言1.1. 背景1.2. 简要介绍 SOLID 原则1.1. 面向对象编程和设计的重要性 2. 单一职责原则&#xff08;SRP&#xff09;2.1. 定义和原理2.2. SRP 的好处与目标2.3. 例子和代码展示2.4. 如何识别和解决 SRP 原则的违反2.5. 注意事项和局限性 3. 开闭原则&#xff08;…

Centos7.9 一键脚本部署 LibreNMS 网络监控系统

前言&#xff1a; LibreNMS 是个以 PHP/MySQL 为基底的自动探索网络监控系统 LibreNMS 官网 版本23.8.2-52-g7bbe0a2 - Thu Sep 14 2023 22:33:23 GMT0700数据库纲要2023_09_01_084057_application_new_defaults (259)Web 服务器nginx/1.20.1PHP8.1.23Python3.6.8DatabaseMa…

Killer!永久禁用WindowsDefender

工具介绍 WinDefenderKiller&#xff0c;使用C写的通过注册表项永久禁用Windows Defende的一个工具。 关注【Hack分享吧】公众号&#xff0c;回复关键字【230726】获取下载链接 编译使用 执行以下命令编译&#xff1a; # x86_64-w64-mingw32-g -O2 disableWinDef.cpp -o win…

JS 原型和原型链

原型和原型链 1. 了解原型和原型链1.1 原型1.2 原型链 2. 原型2.1 prototype2.2 __proto__ 隐式原型 3. 原型链 1. 了解原型和原型链 1.1 原型 原型&#xff1a; prototype 又称显示原型 1、原型是一个普通对象 2、只有构造函数才具备该属性 3、公有属性可操作 1.2 原型链 原…

数据治理-元数据管理-元数据类型

定义 元数据&#xff0c;定义和描述其它数据的数据。 类型 业务元数据、技术元数据和操作元数据。在图书馆或信息科学中&#xff0c;可分为描述元数据、结构元数据、管理元数据。 业务元数据 主要关注数据的内容和条件&#xff0c;另包括与数据治理相关的详细信息。业务元数据…

logback异步appender日志源码详解

背景&#xff1a; 日常打印日志时&#xff0c;使用logback的异步写日志几乎是标准的配置方式&#xff0c;本文从源码上看看异步写日志的整个流程 异步Appender日志 一般日志的配置如下所示 appender(“ASYNC-LOG”, AsyncAppender) { neverBlock true queueSize 10000 } 这…

前端需要知道的计算机网络知识----网络安全,自学网络安全,学习路线图必不可少,【282G】初级网络安全学习资源分享!

网络安全&#xff08;英语&#xff1a;network security&#xff09;包含网络设备安全、网络信息安全、网络软件安全。 黑客通过基于网络的入侵来达到窃取敏感信息的目的&#xff0c;也有人以基于网络的攻击见长&#xff0c;被人收买通过网络来攻击商业竞争对手企业&#xff0c…

CH07_封装

封装记录&#xff08;Encapsulate Record | 162&#xff09; 曾用名&#xff1a;以数据类代替记录&#xff08;Replace Record with Data Class&#xff09; organization {name: "Acme Gooseberries", country: "GB"};class Organization {constructor(…

HTML整站规划与规范

文章目录 命名规则命名命名书写 包含样式规范样式重置样式引入页面结构页面宽度页面高度与背景页面设计 网址图标 命名规则 命名 根据每块元素的主题、功能、页面上的位置命名&#xff0c;便于后期更改与维护。 另外&#xff1a;如果所有样式放在同一文件下&#xff0c;可以给…

计算如何剥出艺术品

背景&#xff1a; 今天给大家介绍一篇中国科学技术大学发表的论文《Computational Peeling Art Design》。论文要解决是&#xff1a;如何把球状三维物体的表面连续展开成一些艺术画面&#xff0c;要求是展开表面要占三维物体表面整个面积&#xff0c;展开表面要和艺术体形状尽可…

[CISCN 2022 初赛]online_crt

文章目录 涉及知识点代码审计解题过程 涉及知识点 CVE-2022-1292漏洞OpenSSLssrf 代码审计 app.py源码 import datetime import json import os import socket import uuid from cryptography import x509 from cryptography.hazmat.backends import default_backend from …

LeetCode(力扣)55. 跳跃游戏Python

LeetCode20. 有效的括号 题目链接代码 题目链接 https://leetcode.cn/problems/jump-game/ 代码 class Solution:def canJump(self, nums: List[int]) -> bool:if len(nums) < 1:return Truecover 0for i in range(len(nums)):if i < cover:cover max(cover, i …

【NVIDIA CUDA】2023 CUDA夏令营编程模型(四)

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

性能测试结果评估与展示

面向性能测试部门 对测试资产进行集中管理以及从项目或系统维度进行汇总展示是两种行之有效的管理手段。这些测试资产包括脚本、缺陷描述、测试记录、测试报告、项目需求等资料,通过对这些资料进行分类,当原有人员缺失的情况下,新接手的测试工程师能快速了解关键信息。 使…

Android RecyclerView BaseSectionQuickAdapter实现分组功能

详情网站&#xff1a;手把手教你使用BaseSectionQuickAdapter实现分组功能&#xff0c;史上最详细Adapter使用教程_basequickadapter 分组_杨阿程的博客-CSDN博客 //加入二个包implementation com.android.support:recyclerview-v7:26.0.0-beta1implementation com.github.Cym…