Linux网络基础二

news2024/12/23 15:30:42

一.应用层

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层。

二.再谈 "协议"

协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?(序列化和反序列化)

序列化:将结构体数据变成字符串的过程

为什么需要序列化:方便网络的发送和接受
反序列化:将字符串变回结构体数据的过程

为什么需要反序列化:方便上层应用程序正常使用数据

三.网络版计算器

客户端发送需计算的数据,服务器进行计算并返回结果.

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() {}
};

这个文件封装sock套接字有关接口,方便直接在代码中使用,就没有什么好解释的,,就是把套接字有关的接口进行了封装。

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

这个就是一个普通的日志文件,用来关注此时的运行状态。

TcpServer.hpp:

#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_;
    };
    class TcpServer
    {
    public:
        static void* ThreadRoutine(void* args)
        {
            pthread_detach(pthread_self());
            ThreadData* td=static_cast<ThreadData*>(args);
            td->server_->Excute(td->sock_);
            close(td->sock_);
            return nullptr;
        }
    public:
        TcpServer(const uint16_t port,const std::string ip="")
        {
            listensock_=sock_.Socket();
            sock_.Bind(listensock_,port,ip);
            sock_.Listen(listensock_);
        }
        //bind服务
        void BindService(func_t func)
        {
            func_.push_back(func);
        }
        //执行任务
        void Excute(int sock)
        {
            for(auto f: func_)
            {
                f(sock);
            }
        }
        void Start()
        {
            for(;;)
            {
                uint16_t clientport;
                std::string clientip;
                int sock=sock_.Accept(listensock_,&clientip,&clientport);
                if(sock==-1)
                    continue;
                pthread_t tid;
                ThreadData* td=new ThreadData(listensock_,this);
                pthread_create(&tid,nullptr,ThreadRoutine,td);
            }
        }
        ~TcpServer()
        {
            if(listensock_>=0)
            {
                close(listensock_);
            }
        }
    private:
        int listensock_;
        Sock sock_;
        std::vector<func_t> func_;
    };
}

 这个文件,首先把内容放在一个ns_tcpserver的命名空间里面。

TcpServer:

类成员:首先这个类的成员有三个,listensock_这个用来存储套接字,sock_是一个套接字有关的类,func_是一个用来存储任务的数组。

TcpServer:构造函数,用于创建套接字,bind对应的ip,port,并且进行listen。

~TcpServer:析构函数,用于关闭套接字

BindService:bind一个服务

Excute:执行一个对应的服务任务

Start:首先获取连接,然后创建一个线程去执行对应的任务

ThreadRoutine:线程的回调函数,第一步分离线程,然后执行对应的服务任务

ThreadData:

类成员:这个类有两个成员,sock_存储套接字,server是一个TcpServer的类

这个类的作用是用来方便线程执行对应的服务任务

CalServer.cc:

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

using namespace ns_tcpserver;
using namespace ns_protocol;

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, req.x_, req.y_, req.op_);
    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;
        // std::cout << "begin: inbuffer: " << inbuffer << std::endl;
        // 2. 协议解析,保证得到一个完整的报文
        std::string package = Decode(inbuffer);
        if (package.empty())
            continue;
        // std::cout << "end: inbuffer: " << inbuffer << std::endl;
        // std::cout << "packge: " << package << std::endl;
        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. 添加长度信息,形成一个完整的报文
        // "length\r\ncode result\r\n"
        // std::cout << "respString: " << respString << std::endl;
        respString = Encode(respString);
        // std::cout << "encode: respString: " << respString << std::endl;
        // 8. send这里我们暂时先这样写,多路转接的时候,我们再来谈发送的问题
        Send(sock, respString);
    }
}

// void handler(int signo)
// {
//     std::cout << "get a signo: " << signo << std::endl;
//     exit(0);
// }

// ./CalServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 一般经验:server在编写的时候,要有较为严谨性的判断逻辑
    // 一般服务器,都是要忽略SIGPIPE信号的,防止在运行中出现非法写入的问题!
    // signal(SIGPIPE, SIG_IGN);
    MyDaemon();
    std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));
    server->BindService(calculator);
    server->Start();
    // Request req(123, 456, '+');
    // std::string s = req.Serialize();
    // std::cout << s << std::endl;

    // Request temp;
    // temp.Deserialized(s);
    // std::cout << temp.x_ << std::endl;
    // std::cout << temp.op_ << std::endl;
    // std::cout << temp.y_ << std::endl;
    return 0;
}

 这个是服务端,首先来说一下主函数,首先调用MyDaemon(),然这个进程变成守护进程,这个在后面再仔细说,然后就是创建一个TcpServer对象,然后给他BindService一个服务,然后调用Start(),然后启动服务端。

calculator:这个函数就是对应服务,第一步首下就是要从对应客户端读取数据,然后进行协议解析,然后反序列化,再按正常逻辑计算结果即可,最后序列化和添加报文,然后发送出去即可。

为什么需要协议解析以及添加报文:因为tcp是字节流式的,收到的数据可能并不完整,就是并不是一个数据,有可能是半个,也有可能是一个半,因为send/write这类接口是把数据写到缓冲区,其他的事情都归Tcp管,因为TCP是字节流式的,所以发送次数和接收次数,没有关系,不像udp发多少次就接受多少次。

Makefile:

.PHONY:all
all:client server
client:CalClient.cc
	g++ -o $@ $^ -std=c++11
server:CalServer.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -rf client server

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.x_ << resp.op_ << resp.y_ << " = " << resp.result_ << " [success]" << std::endl;
                break;
            }
            if(!err.empty()) std::cerr << err << std::endl;
            // sleep(1);
            break;
        }
    }
    close(sockfd);
    return 0;
}

CalClient.cc这个是客户端:

main():第一步接收数据(端口号和IP),然后创捷套接字,并且获取连接,然后进入一个正常的死循环,先是从键盘获取数据,然后序列化添加报头,发送给服务器,然后再从服务器读取数据,进行协议解析和反序列化,最后回显到显示器即可。

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. 忽略信号,SIGPIPE,SIGCHLD
    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);
    }
}

Daemon.hpp:作用是让一个进程变成守护进程

接下来解释一下什么是守护进程:

守护进程:

1.全部都是在前台运行的

2..前台进程:和终端关联的进程,前台进程。

3.任何xshell登陆,只允许一个前台进程和多个后台进程

4.进程除了有自己的pid,ppid,还有一个组ID

5.在命令行中,同时用管道启动多个进程,多个进程是兄弟关系,父进程都是bash -→>可以用匿名管道来进行通信,而同时被创建的多个进程可以成为一个进程组的概念,组长一般是第一个进程

6.任何一次登陆,登陆的用户,需要有多个进程(组)来给这个用户提供服务的(bash),用户自己可以启动很多进程,或者进程组。我们把给用户提供服务的进程或者用户自己启动的所有的进程或者服务,整体都是要属于一个叫做会话的机制中的。

7.如何将自己变成自成会话呢? 调用setsid0

8. setsid要成功被调用,必须保证当前进程不是进程组的组长,怎么保证我不是组长呢?调用fork()

9.守护进程不能直接向显示器打印消息一旦打印,会被暂停、终止

10.如何杀死守护进程,使用9号信号杀死

 Protocol.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h>

namespace ns_protocol
{
#define MYSELF 0

#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP) // 不能是sizeof!

    class Request
    {
    public:
        // 1. 自主实现 "length\r\nx_ op_ y_\r\n"
        // 2. 使用现成的方案
        std::string Serialize()
        {
#ifdef MYSELF
            std::string str;
            str = std::to_string(x_);
            str += SPACE;
            str += op_; // TODO
            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
        }
        // "x_ op_ y_"
        // "1234 + 5678"
        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);
            x_ = root["x"].asInt();
            y_ = root["y"].asInt();
            op_ = root["op"].asInt();
            return true;
#endif
        }

    public:
        Request()
        {
        }
        Request(int x, int y, char op) : x_(x), y_(y), op_(op)
        {
        }
        ~Request() {}

    public:
        // 约定
        // x_ op y_ ? y_ op x_?
        int x_;   // 是什么?
        int y_;   // 是什么?
        char op_; // '+' '-' '*' '/' '%'
    };

    class Response
    {
    public:
        // "code_ result_"
        std::string Serialize()
        {
#ifdef MYSELF
            std::string s;
            s = std::to_string(code_);
            s += SPACE;
            s += std::to_string(result_);

            return s;
#else
            Json::Value root;
            root["code"] = code_;
            root["result"] = result_;
            root["xx"] = x_;
            root["yy"] = y_;
            root["zz"] = op_;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        // "111 100"
        bool Deserialized(const std::string &s)
        {
#ifdef MYSELF
            std::size_t pos = s.find(SPACE);
            if (pos == std::string::npos)
                return false;
            code_ = atoi(s.substr(0, pos).c_str());
            result_ = atoi(s.substr(pos + SPACE_LEN).c_str());
            return true;
#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(s, root);
            code_ = root["code"].asInt();
            result_ = root["result"].asInt();
            x_ =  root["xx"].asInt();
            y_ =  root["yy"].asInt();
            op_ =  root["zz"].asInt();
            return true;
#endif
        }

    public:
        Response()
        {
        }
        Response(int result, int code, int x, int y, char op) 
        : result_(result), code_(code), x_(x), y_(y), op_(op)
        {
        }
        ~Response() {}

    public:
        // 约定!
        // result_? code_? code_ 0? 1?2?3?
        int result_; // 计算结果
        int code_;   // 计算结果的状态码

        int x_;
        int y_;
        char op_;
    };

    // 临时方案
    // 调整方案2: 我们期望,你必须给我返回一个完整的报文
    bool Recv(int sock, std::string *out)
    {
        // UDP是面向数据报:
        // TCP 面向字节流的:
        // recv : 你怎么保证,你读到的inbuffer,是一个完整完善的请求呢?不能保证
        // "1234 + 5678" : 1234 +
        // "1234 + 5678" : 1234 + 5678 123+99
        // "1234 "
        // 必须是:"1234 + 5678"
        // 单纯的recv是无法解决这个问题的,需要对协议进一步定制!
        char buffer[1024];
        ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0); // 9\r\n123+789\r\n
        if (s > 0)
        {
            buffer[s] = 0;
            *out += buffer;
        }
        else if (s == 0)
        {
            // std::cout << "client quit" << std::endl;
            return false;
        }
        else
        {
            // std::cout << "recv error" << std::endl;
            return false;
        }
        return true;
    }

    void Send(int sock, const std::string str)
    {
        // std::cout << "sent in" << std::endl;
        int n = send(sock, str.c_str(), str.size(), 0);
        if (n < 0)
            std::cout << "send error" << std::endl;
    }
    // "length\r\nx_ op_ y_\r\n..." // 10\r\nabc
    // "x_ op_ y_\r\n length\r\nXXX\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 "";
        }
    }
    // "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;
    }

}

protocal.hpp:这个文件里面主要包含了序列化和反序列化以及协议解析和添加报头

首先把内容封装在ns_protocal这个命名空间里面

Requst和Response这两个类分别是存储初始数据和结果,然后在里面各写了一份序列化和反序列化

Recv:就是把读取数据封装了一下

Send:就是把发送数据封装了

Decode:协议解析

Encode:添加报头

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

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

相关文章

VBA替换中文文献引用出现的et al.和and

问题描述&#xff1a;Endnote是常用的文献管理工具&#xff0c;并提供国标模板Chinese Std GBT7714 (numeric).ens&#xff0c;但Endnote在中英文混排上略欠考虑。Chinese Std GBT7714使用序号的形式&#xff08;******1&#xff09;对文献进行引用&#xff0c;但有时我们需要以…

python毕业设计之django+vue医院医疗救助系统

&#xff08;1&#xff09; 信息发布 当有基金的申请审批通过时&#xff0c;慈善机构信息维护部门应与慈善机构进行对接&#xff0c;保证信息的真实性&#xff0c;信息发布之后患者可以进行相应的基金申请。 &#xff08;2&#xff09; 基金管理 此项功能是保证基金信息的动态刷…

HR员工管理的三重境界:管事、管人、管心

在一个公司里&#xff0c;员工来来往往是常态&#xff0c;虽说我们不能替他们决定&#xff0c;但是一定是与公司的管理者有一定的关系。马云曾经说过&#xff1a;“一个员工离职&#xff0c;不外乎两种原因&#xff0c;一是钱没给到位&#xff1b;二是心里委屈了”。一句话就是…

笔记:计算机网络体系结构(OSI七层模型、TCP/IP五层协议)

计算机网络体系结构 计算机网络是一个复杂的、具有综合性技术的系统&#xff0c;它由计算机系统、通信处理机、通信线路和通信设备、操作系统以及网络协议等组成。为了更好地描述计算机网络结构&#xff0c;使计算机网络系统有条不紊地处理工作&#xff0c;需要定义一种较好的…

2023.04.30 学习周报

文章目录 摘要文献阅读1.题目2.摘要3.介绍4.本文贡献5.数据处理6.模型6.1 look - up操作6.2 LSTM6.3 周期模拟及额外因素 7.实验7.1 数据集7.2 基线7.3 实验表现 8.结论 ISOMAP1.基本思想2.欧氏距离3.折线近似曲线4.计算折线长度5.Floyd-Warshall算法6.ISOMAP算法7.总结 数学建…

Educoder/头歌JAVA——Java Web:基于JSP的网上商城

目录 一、商品列表 本关任务 具体要求 结果输出 实现代码 二、商品详情 本关任务 JDBC查询方法封装 商品相关信息介绍 具体要求 结果输出 实现代码 三、商品搜索 编程要求 测试说明 实现代码 四、购物车列表 本关任务 JDBC查询方法封装 购物车相关信息介绍…

IPsec中IKE与ISAKMP过程分析(主模式-消息4)

IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息1&#xff09;_搞搞搞高傲的博客-CSDN博客 IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息2&#xff09;_搞搞搞高傲的博客-CSDN博客 IPsec中IKE与ISAKMP过程分析&#xff08;主模式-消息3&#xff09;_搞搞搞高傲的博客…

Jenkins+Docker+gitee 持续部署spring boot 应用教程

目录 参考安装jenkinsci拉取镜像创建目录安装maven启动镜像配置输入密码安装推荐的插件 创建用户密码配置环境变量安装gitee和dingtalk插件创建SpringBoot并创建Dockfile配置gitee的webhook配置构建shell脚本配置监听gitee webhook触发构建 配置dingtalk钉钉通知 参考 Jenkins…

归纳截图小结

文章目录 web服务器、缓存和PHP加速对比没有做软连接http状态码cookie是什么图形管理界面nginx的访问路径LANM架构redisd服务整体框架免密连接kubeletk8s架构图kubeadm、kubectl、kubelet作用k8s集群token失效时&#xff0c;重新创建tokenk8s网络通信OSI总结pod的理解k8s核心知…

springboot+vue高校实验室预约管理系统

开发环境 开发语言&#xff1a;Java 后端框架&#xff1a;springbootweb 前端框架&#xff1a;vue.js 服务器&#xff1a;tomcat 数据库&#xff1a;mysql 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 校实验…

K8S集群原理(IT枫斗者)

K8S集群原理&#xff08;IT枫斗者&#xff09; 看图说K8S 先从一张大图来观看一下K8S是如何运作的&#xff0c;再具体去细化K8S的概念、组件以及网络模型。从上图&#xff0c;我们可以看到K8S组件和逻辑及其复杂&#xff0c;但是这并不可怕&#xff0c;我们从宏观上先了解K8S…

【设计模式】责任链模式的设计与示例

前言 责任链模式是一种行为设计模式&#xff0c;执行上它允许请求沿着一条处理链路依次向下传递&#xff0c;每个处理节点都能对当前状态的请求进行处理&#xff0c;满足一定条件后传递给下一个处理节点&#xff0c;亦或者直接结束这一次处理流程。 在现实生产环境中&#xf…

工作流框架研究

工作流框架研究 主流开源框架介绍OsWorkFlowJBPMActivitiFlowableCamundaCamunda 和Flowable对比功能上对比性能上对比 总结 主流开源框架介绍 OsWorkFlow 对于比较简单的流程&#xff0c;OsWorkFlow会是一个比较好的选择&#xff0c;对于复杂的流程就不推荐了&#xff0c;Os…

小 C 的数学(math)

祝大家劳动节快乐&#xff01;&#xff01;小手动起来 言归正传┏ (゜ω゜)☞ 题目描述 小 C 想要成为一名 OIer&#xff0c;于是他提前学习数学&#xff0c;为 OI 做好铺垫。这一天&#xff0c;他的数学老师给了一道题&#xff1a;给定正整数 a&#xff0c;以及给定一个区间 …

Python基础之列表元组

1.列表 序列是Python中最基本的数据结构。序列中的每个元素都分配一个数字 去锁定它的位置&#xff0c;或索引&#xff0c;第一个索引是0&#xff0c;第二个索引是1&#xff0c;依此类推。Python有6个序列的内置类型&#xff0c;但最常见的是列表和元组。序列都可以进行的操作…

HTB-Forge

HTB-Forge 信息收集80端口 立足user -> root 信息收集 80端口 试试上传图片看看有什么限制。 jpg上传成功&#xff0c;并且会给一个随机的文件名存储图片&#xff0c;过了一阵子图片就会被清除。 上传phpinfo后访问界面出现报错。 看来没有执行上传的PHP代码&#xff0…

第43天-DP-第九章 ● 139.单词拆分 ● 关于多重背包,你该了解这些! ● 背包问题总结篇!

文章目录 1. 单词拆分2.多重背包3. 背包总结 1. 单词拆分 s class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {unordered_set<string> wordSet(wordDict.begin(), wordDict.end());// 1. dp[i] 为true代表 可以拆分为一个或者多…

PySyft框架

openmined社区开源的pysyft框架可以提供安全的联邦学习&#xff0c;有助于解决基于“不可见数据”的统计分析与建模开发。在PySyft中&#xff0c;syft是重要的张量&#xff0c;通过建立SyftTensor抽象类来表现张量链的运算或数据状态转换。如图5-7所示&#xff0c;张量链的结构…

3.3 泰勒公式例题分析

例1 写出函数f(x)带有拉格朗日余项的n阶麦克劳林公式 我的答案&#xff1a; 一、信息 1.f(x)的表达式 2.目标求这个f(x)的n阶麦克劳林公式 二、分析 条件1&#xff1a;告诉我f(x)的表达式为我后续带入公式做准备 条件2&#xff1a;告诉我用什么公式和此次求解的方向 三…

【ONE·C++ || 二叉搜索树】

总言 二叉树进阶&#xff1a;主要介绍二叉搜索树相关内容。 文章目录 总言1、基本介绍1.1、什么是二叉搜索树 2、相关实现2.1、基本框架2.1.1、如何构建二叉树单节点2.1.2、如何定义一个二叉搜索树 2.2、非递归实现&#xff1a;插入、查找、删除2.2.1、二叉搜索树插入&#xf…