[Linux理论基础1]----手写和使用json完成[序列化和反序列化]

news2024/9/29 13:17:09

文章目录

  • 前言
  • 一、应用层
  • 二、再谈"协议"
  • 三、 网络版计算器
    • 手写版本
    • 使用第三方库json实现
  • 完整代码
  • 总结


前言

  • 理解应用层的作用,初始HTTP协议;
  • 理解传输层的作用,深入理解TCP的各项特性和机制;
  • 对整个TCP/IP协议有系统的理解;
  • 对TCP/IP协议体系下的其他重要协议和技术有一定的了解;
  • 学会使用一些分析网络问题的工具和方法;

本章属于Linux下网络编程的理论基础.


正文开始!

一、应用层

我们程序猿写的一个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层.
在这里插入图片描述

二、再谈"协议"

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

三、 网络版计算器

例如,我们需要实现一个服务器版的加法器,我们需要客户端把要计算的两个加数发过去,然后由服务器进行计算,最后再把结果返回给客户端.
约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数,都是整形;
  • 两个数字之间会有一个字符是运算符,运算符只能是 + ;
  • 数字和运算符之间没有空格;

在这里插入图片描述

约定方案二:

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

手写版本

makefile

.PHONY:all
all:clientTcp serverTcpd
clientTcp:clientTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp
serverTcpd:serverTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp -lpthread
.PHONY:clean
clean:
	rm -rf clientTcp serverTcpd ServerTcp.log

clientTcp.cc

#include "util.hpp"
#include"Protocol.hpp"
#include "log.hpp"

using namespace std;

volatile bool quit =false;

static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " ip port" << endl;
    cerr << "Example\n\t" << proc << " 127.0.0.1 8080\n"
         << endl;
}

// 2.需要bind吗??--->需要,但是不需要显式的bind!
// 3.需要listen吗?不需要的!
// 4.需要accept吗?不需要的!
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    string serverIp = argv[1];
    uint16_t serverPort = stoi(argv[2]);

    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        cerr << "socket: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // 2.connect,发起连接请求,你想谁发起请求呢?当然是想服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    inet_aton(serverIp.c_str(), &server.sin_addr);
    server.sin_port = ntohs(serverPort);
    // 2.2发送请求,connect会自动帮我们进行bind!
    if (connect(sock, (const sockaddr *)&server, sizeof(server)) != 0)
    {
        cerr << "connect: " << strerror(errno) << endl;
        exit(CONN_ERR);
    }
    cout << "info connect success: " << sock << endl;
    //trimStr(message); //对多输入的空格进行清洗
    string message;
    while(!quit)
    {
        cout<<"请输入表达式>> "; // 1 + 1
        getline(cin,message);
        if(strcasecmp(message.c_str(),"quit")==0)
        {    
            quit=true;
            continue;
        }     
        Request req;
        if(!makeRequest(message,&req))
            continue;
        std::string package;
        req.serialize(&package);
        cout<<"debug->serialize-> \n"<<package<<endl;
        package = encode(package,package.size());
        cout<<"debug->encode-> \n"<<package<<endl;
        ssize_t s=write(sock,package.c_str(),package.size());
        if(s>0)
        {
            char buffer[BUFFER_SIZE];
            size_t s =read(sock,buffer,sizeof(buffer)-1);
            if(s > 0) buffer[s]=0;
            cout<<buffer<<endl;
            string echoPackage = buffer;
            Response resp;
            uint32_t len=0;
            cout<<"debug->get response-> "<<echoPackage<<endl;
            string tmp = decode(echoPackage,&len);
            if(len > 0)
            {
                echoPackage = tmp;
                cout<<"debug->decode-> "<<echoPackage<<endl;
                resp.deserialize(echoPackage);
                printf("[exitcode: %d] %d\n",resp._exitCode,resp._result);
            }
        }
        else if(s<=0)
        {
            break;
        }
        message.clear();
    }
    close(sock);
    return 0;
}

calServer.cc

#include "util.hpp"
#include "log.hpp"
#include "ThreadPool.hpp"
#include "Protocol.hpp"
#include "Task.hpp"
#include "daemonize.hpp"
using namespace std;

class ServerTcp;
struct ThreadData
{
    ThreadData(int sock, string clientIp, uint16_t clientPort, ServerTcp *ts)
        : _sock(sock), _clientIp(clientIp), _clientPort(clientPort), _this(ts)
    {
    }
    int _sock;
    string _clientIp;
    uint16_t _clientPort;
    ServerTcp *_this;
};
static Response calcaulator(const Request &req)
{
    Response resp;
    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(req._y == 0)
                resp._exitCode = 1; // 1:除0
            else
                resp._result = req._x / req._y;
        }
        break;
    case '%':
        {
            if(req._y == 0)
                resp._exitCode = 2; // 1:模0
            else
                resp._result = req._x % req._y;
        }
        break;
    default:
        resp._exitCode = 3; //非法操作符
        break;
    }
    return resp;
}

// 1. 全部手写
// 2.部分采用别人的方案 -- 序列化和反序列化的问题  -- xml,json,protobuf 
void netCal(int sock, const string &clientIp, uint16_t clientPort)
{
    assert(sock > 0);
    assert(!clientIp.empty());
    assert(clientPort > 1024);

    // 9\r\n100 + 200\r\n
    string inbuffer;
    while (true)
    {
        char buff[128];
        ssize_t s = read(sock, buff, sizeof(buff) - 1);
        if (s == 0)
        {
            logMessage(NOTICE, "client[%s:%d] close sock, service done", clientIp.c_str(), clientPort);
            break;
        }
        else if (s < 0)
        {
            logMessage(WARINING, "read client[%s:%d] error, errcode: %d, errormessage: %s",
                       clientIp.c_str(), clientPort, errno, strerror(errno));
            break;
        }
        cout<<buff<<endl;
        // read success
        buff[s] = 0;
        inbuffer += buff;
        // 1.检查inbuffer是不是已经具有了一个strPackage
        Request req;
        uint32_t packageLen = 0;
        string package = decode(inbuffer, &packageLen);
        if (packageLen == 0)
            continue; // 无法提取一个完整的报文,进行下一次读取
        // 2.证明已经获得一个完整的package
        if (req.deserialize(package))
        {
            req.debug();
            // 3.处理逻辑,输入一个req,得到一个resp
            Response resp = calcaulator(req); //resp是一个结构化的字段
            // 4.对resp进行序列化
            string respPackage;
            resp.serialize(&respPackage);
            // 5.对报文进行encode
            respPackage = encode(respPackage,respPackage.size());
            // 6.简单进行发送
            write(sock,respPackage.c_str(),respPackage.size());

        }
    }
}

class ServerTcp
{
public:
    ServerTcp(uint16_t port, string ip = "")
        : _listenSock(-1), _port(port), _ip(ip), _tp(nullptr)
    {
        _quit = false;
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1.创建socket
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            logMessage(FATAL, "socket:%s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket:%s,%d", strerror(errno), _listenSock);
        // 2.bind绑定
        // 2.1填充服务器
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        _ip.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(_ip.c_str(), &local.sin_addr));
        // 2.2本地socket信息,写入_sock对应的内核区域
        if (bind(_listenSock, (const sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s", strerror(errno));
        // 3.监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(_listenSock, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s", strerror(errno));
        // 允许别人来连接你了

        // 4.加载线程池
        _tp = ThreadPool<Task>::getInstance();
    }
    void loop()
    {
        // signal(SIGCHLD,SIG_IGN);//只在Linux下有效
        _tp->start();
        logMessage(DEBUG, "thread pool start success,thread num: %d", _tp->ThreadNum());

        while (!_quit)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            // 4.1 _listenScok:监听&&获取新的连接--->sock
            // 4.2 serviceSock:给用户提供新的socket服务
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (_quit)
                break;
            if (serviceSock < 0)
            {
                // 获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.提供服务,小写转大写
            Task t(serviceSock, peerIp, peerPort, netCal);
            _tp->push(t);

            // logMessage(DEBUG,"server provide service start ...");
            // sleep(1);
        }
    }

    bool quitServer()
    {
        _quit = true;
    }

private:
    int _listenSock;
    uint16_t _port;
    string _ip;
    // 引入线程池
    ThreadPool<Task> *_tp;
    // 安全退出
    bool _quit;
};
static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " port ip" << endl;
    cerr << "Example\n\t" << proc << " 8080  127.0.0.1\n"
         << endl;
}

ServerTcp *svrp = nullptr;

void sighandler(int signo)
{
    if (signo == 3 && svrp != nullptr)
    {
        svrp->quitServer();
    }
    logMessage(DEBUG, "server quit save!");
}

// ./serverTcp local_port [local_ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = stoi(argv[1]);
    string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }
    signal(3, sighandler);
    ServerTcp svr(port, ip);
    svr.init();
    svrp = &svr;
    svr.loop();
    return 0;
}

protocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <cstring>
#include "util.hpp"
#include<jsoncpp/json/json.h>
using namespace std;
// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算机

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define OPS "+-*/%"

// encode,整个序列化之后的字符串进行添加长度
// strlen(四个字节)XXXXXXXX
// "strlen\r\n"XXXXXXXX\r\n --采用这种方案
// encode,整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{
    string encodein = to_string(len);
    encodein += CRLF;
    encodein += in;
    encodein += CRLF;
    return encodein;
}

// decode,整个序列化之后的字符串进行提取长度
// 1.必须具有完整的长度
// 2.必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则我们就是一个检测函数!
// 9\r\n100 + 200\r\n 9\r\n100 + 200\r\n
std::string decode(std::string &in, uint32_t *len)
{
    assert(len);
    // 1.确认是否是一个包含len的有效字符串
    *len = 0;
    size_t pos = in.find(CRLF);
    if (pos == string::npos)
        return "";
    // 2.提取长度
    string inLen = in.substr(0, pos);
    int intLen = stoi(inLen);
    // 3.确认有效载荷也是符合要求的
    int surplus = in.size() - 2 * CRLF_LEN - pos;
    if (surplus < intLen)
        return "";
    // 4.确认有完整的报文结构
    std::string package = in.substr(pos + CRLF_LEN, intLen);
    *len = intLen;
    // 5.将当前的报文完整的从in中全部移除掉!
    long removeLen = 2 * CRLF_LEN + inLen.size() + package.size();
    in.erase(0, removeLen);
    // 6.正常返回
    return package;
}

// 定制的请求 x op y
class Request
{
public:
    Request()
    {
    }
    ~Request()
    {
    }
    // 序列化 -- 结构化的数据 -> 字符串
    // 认为结构化字段中的内容已经被填充了
    void serialize(std::string *out)
    {
        *out = to_string(_x) + SPACE + _op + SPACE + to_string(_y);
    }

    // 反序列化 -- 字符串 -> 结构化的数据
    //  9\r\n100 + 200\r\n -> 100 + 200
    bool deserialize(std::string &in)
    {
        size_t spaceOne = in.find(SPACE);
        if (spaceOne == string::npos)
            return false;
        size_t spaceTwo = in.rfind(SPACE);
        if (spaceTwo == string::npos)
            return false;

        string dataOne = in.substr(0, spaceOne);
        string dataTwo = in.substr(spaceTwo + SPACE_LEN);
        string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - spaceOne - SPACE_LEN);
        if (oper.size() != 1)
            return false;

        // 转成内部成员
        _x = stoi(dataOne);
        _y = stoi(dataTwo);
        _op = oper[0];
        return true;
    }
    void debug()
    {
        cout << "#####################################" << endl;
        printf("_x=%d,_op=%c,_y=%d\n", _x, _op, _y);
        cout << "#####################################" << endl;
    }

public:
    // 需要计算的数据
    int _x;
    int _y;
    // 需要计算的种类
    char _op; //+ - * / %
};

// 定制的响应2
class Response
{
public:
    Response() : _exitCode(0), _result(0)
    {
    }
    ~Response()
    {
    }
    // 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的
    //  "_exitCode _result"
    void serialize(std::string *out)
    {
        string ec = to_string(_exitCode);
        string res = to_string(_result);
        *out = ec + SPACE + res;
    }
    // 反序列化
    bool deserialize(const std::string &in)
    {
        size_t pos = in.find(SPACE);
        if (pos == string::npos)
            return false;
        string codeStr = in.substr(0, pos);
        string resStr = in.substr(pos + SPACE_LEN);
        // 将反序列化的结果写入到内部成员中,形成结构化数据.
        _exitCode = stoi(codeStr);
        _result = stoi(resStr);
        return true;
    }
    void debug()
    {
        cout << "#####################################" << endl;
        printf("_exitCode=%d,_result=%d\n", _exitCode, _result);
        cout << "#####################################" << endl;
    }

public:
    // 退出状态,0标识运算结果合法,非0标识运算结果是非法的,!0是几就表示是什么原因错了!
    int _exitCode;

    int _result;
};

bool makeRequest(string &str, Request *req)
{
    char strtmp[BUFFER_SIZE];
    snprintf(strtmp, sizeof strtmp, "%s", str.c_str());
    char *left = strtok(strtmp, OPS);
    if (!left)
        return false;
    char *right = strtok(nullptr, OPS);
    if (!right)
        return false;

    char mid = str[strlen(left)];
    req->_x = stoi(left);
    req->_y = stoi(right);
    req->_op = mid;
    return true;
}

在这里插入图片描述

使用第三方库json实现

相比于手写版本的代码,使用库的版本只进行修改了makefile和protocol.hpp两个文件!

云服务器中安装json库

sudo yum install -y jsoncpp-devel

和线程库一样,json也属于第三方库,所以在makefile编译中需要带-ljsoncpp选项,否则编译就会失败!

makefile

.PHONY:all
all:clientTcp serverTcpd
Method=#-DMY_SELF	#进行命令行宏定义,如果定义则编译手写版本,否则编译第三方库版本
clientTcp:clientTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp
serverTcpd:serverTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp -lpthread
.PHONY:clean
clean:
	rm -rf clientTcp serverTcpd ServerTcp.log

protocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <cstring>
#include "util.hpp"
#include<jsoncpp/json/json.h>
using namespace std;
// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算机

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define OPS "+-*/%"

// encode,整个序列化之后的字符串进行添加长度
// strlen(四个字节)XXXXXXXX
// "strlen\r\n"XXXXXXXX\r\n --采用这种方案
// encode,整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{
    string encodein = to_string(len);
    encodein += CRLF;
    encodein += in;
    encodein += CRLF;
    return encodein;
}

// decode,整个序列化之后的字符串进行提取长度
// 1.必须具有完整的长度
// 2.必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则我们就是一个检测函数!
// 9\r\n100 + 200\r\n 9\r\n100 + 200\r\n
std::string decode(std::string &in, uint32_t *len)
{
    assert(len);
    // 1.确认是否是一个包含len的有效字符串
    *len = 0;
    size_t pos = in.find(CRLF);
    if (pos == string::npos)
        return "";
    // 2.提取长度
    string inLen = in.substr(0, pos);
    int intLen = stoi(inLen);
    // 3.确认有效载荷也是符合要求的
    int surplus = in.size() - 2 * CRLF_LEN - pos;
    if (surplus < intLen)
        return "";
    // 4.确认有完整的报文结构
    std::string package = in.substr(pos + CRLF_LEN, intLen);
    *len = intLen;
    // 5.将当前的报文完整的从in中全部移除掉!
    long removeLen = 2 * CRLF_LEN + inLen.size() + package.size();
    in.erase(0, removeLen);
    // 6.正常返回
    return package;
}

// 定制的请求 x op y
class Request
{
public:
    Request()
    {
    }
    ~Request()
    {
    }
    // 序列化 -- 结构化的数据 -> 字符串
    // 认为结构化字段中的内容已经被填充了
    void serialize(std::string *out)
    {
#ifdef MY_SELF
        *out = to_string(_x) + SPACE + _op + SPACE + to_string(_y);
#else
        //json
        // 1.Value对象,万能对象,
        // 2.json是基于KV
        // 3.json有两套操作方法
        // 4.序列化的时候,会将所有的数据内容,转化称为字符串
        Json::Value root;
        root["x"]=_x;
        root["y"]=_y;
        root["op"]=_op;

        // Json::FastWriter fw;
        Json::StyledWriter fw;
        *out = fw.write(root);
#endif
    }

    // 反序列化 -- 字符串 -> 结构化的数据
    //  9\r\n100 + 200\r\n -> 100 + 200
    bool deserialize(std::string &in)
    {
#ifdef MY_SELF
        size_t spaceOne = in.find(SPACE);
        if (spaceOne == string::npos)
            return false;
        size_t spaceTwo = in.rfind(SPACE);
        if (spaceTwo == string::npos)
            return false;

        string dataOne = in.substr(0, spaceOne);
        string dataTwo = in.substr(spaceTwo + SPACE_LEN);
        string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - spaceOne - SPACE_LEN);
        if (oper.size() != 1)
            return false;

        // 转成内部成员
        _x = stoi(dataOne);
        _y = stoi(dataTwo);
        _op = oper[0];
        return true;

#else
        //json
        Json::Value root;
        Json::Reader rd;
        rd.parse(in,root);
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _op = root["op"].asInt();
        return true;
#endif
    }
    void debug()
    {
        cout << "#####################################" << endl;
        printf("_x=%d,_op=%c,_y=%d\n", _x, _op, _y);
        cout << "#####################################" << endl;
    }

public:
    // 需要计算的数据
    int _x;
    int _y;
    // 需要计算的种类
    char _op; //+ - * / %
};

// 定制的响应2
class Response
{
public:
    Response() : _exitCode(0), _result(0)
    {
    }
    ~Response()
    {
    }
    // 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的
    //  "_exitCode _result"
    void serialize(std::string *out)
    {
#ifdef MY_SELF
        string ec = to_string(_exitCode);
        string res = to_string(_result);

        *out = ec + SPACE + res;
#else
        //json
        Json::Value root;
        root["code"]=_exitCode;
        root["res"]=_result;
        //Json::FastWriter fw;
        Json::StyledWriter fw;
        *out = fw.write(root);
#endif
    }
    // 反序列化
    bool deserialize(const std::string &in)
    {
#ifdef MY_SELF
        size_t pos = in.find(SPACE);
        if (pos == string::npos)
            return false;
        string codeStr = in.substr(0, pos);
        string resStr = in.substr(pos + SPACE_LEN);
        // 将反序列化的结果写入到内部成员中,形成结构化数据.
        _exitCode = stoi(codeStr);
        _result = stoi(resStr);
        return true;
#else
        //json
        Json::Value root;
        Json::Reader rd;
        rd.parse(in,root);
        _exitCode = root["code"].asInt();
        _result = root["res"].asInt();
        return true;
#endif
    }
    void debug()
    {
        cout << "#####################################" << endl;
        printf("_exitCode=%d,_result=%d\n", _exitCode, _result);
        cout << "#####################################" << endl;
    }

public:
    // 退出状态,0标识运算结果合法,非0标识运算结果是非法的,!0是几就表示是什么原因错了!
    int _exitCode;

    int _result;
};

bool makeRequest(string &str, Request *req)
{
    char strtmp[BUFFER_SIZE];
    snprintf(strtmp, sizeof strtmp, "%s", str.c_str());
    char *left = strtok(strtmp, OPS);
    if (!left)
        return false;
    char *right = strtok(nullptr, OPS);
    if (!right)
        return false;

    char mid = str[strlen(left)];
    req->_x = stoi(left);
    req->_y = stoi(right);
    req->_op = mid;
    return true;
}

在这里插入图片描述

在这里我们没有进行删除手写版本的序列化和反序列化,通过命令行进行宏定义和条件编译来进行版本的切换.

完整代码

lock.hpp

#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&_lock, nullptr);
    }

    void lock()
    {
        pthread_mutex_lock(&_lock);
    }
    void unlock()
    {
        pthread_mutex_unlock(&_lock);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&_lock);
    }

private:
    pthread_mutex_t _lock;
};

class LockGuard
{
public:
    LockGuard(Mutex* mutex)
        : _mutex(mutex)
    {
        _mutex->lock();
        std::cout<<"加锁成功..."<<std::endl;
    }
    ~LockGuard()
    {
        _mutex->unlock();
        std::cout<<"解锁成功..."<<std::endl;
    }

private:
    Mutex* _mutex;
};

log.hpp

#pragma once
#include<cstdio>
#include<ctime>
#include<cstdarg>
#include<cassert>
#include<cstdlib>
#include<cstring>
#include<cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define DEBUG     0
#define NOTICE    1
#define WARINING  2
#define FATAL     3

const char* log_level[]={"DEBUG","NOTICE","WARINING","FATAL"};

#define LOGFIFE "ServerTcp.log"

class Log
{
public:
    Log():_logFd((-1))
    {}
    ~Log()
    {
        if(_logFd!=-1)
        {
            fsync(_logFd);//将操作系统中的数据尽快刷盘
            close(_logFd);
        }

    }
    void enable()
    {
        _logFd=open(LOGFIFE,O_WRONLY|O_APPEND|O_CREAT,0666);
        assert(_logFd!=-1);
        dup2(_logFd,0);
        dup2(_logFd,1);
        dup2(_logFd,2);
    }
private:
    int _logFd;
};




//logMessage(DEBUG,"%d",10);
void logMessage(int level,const char* format,...)
{
    assert(level>=DEBUG);
    assert(level<=FATAL);
    char logInfo[1024];
    char* name=getenv("USER");
    va_list ap; //ap--->char*
    va_start(ap,format);

    vsnprintf(logInfo,sizeof(logInfo)-1,format,ap);

    va_end(ap); //ap=NULL

    FILE* out=(level==FATAL)?stderr:stdout;
    fprintf(out,"%s | %u | %s | %s\n",\
    log_level[level],(unsigned int)time(nullptr),\
    name==nullptr?"unknow":name,logInfo);
    fflush(out);//将C缓冲区中的数据刷新到OS

}

Task.hpp

#pragma once

#include <iostream>
#include <string>
#include<functional>
#include"log.hpp"
#include<pthread.h>
class Task
{
public:

    //等价于
    // typedef std::function<void(int,std::string,uint16_t)> callback_t;
    using callback_t=std::function<void (int, std::string, uint16_t)>;

private:
    int _sock;//给用户提供任务IO服务的sock
    std::string _ip;//client ip
    uint16_t _port;//client port
    callback_t _func;//回调方法
public:
    Task():_sock(-1),_port(-1)
    {}
    Task(int sock,std::string ip,uint16_t port,callback_t func)
    :_sock(sock),_ip(ip),_port(port),_func(func)
    {}
    void operator()()
    {
        logMessage(DEBUG,"线程ID[%p]->处理%s:%d的请求 开始啦....",\
            pthread_self(),_ip.c_str(),_port);

        _func(_sock,_ip,_port);
        
        logMessage(DEBUG,"线程ID[%p]->处理%s:%d的请求 结束啦....",\
            pthread_self(),_ip.c_str(),_port);
    }

    ~Task()
    {}
};

ThreadPool.hpp

#pragma once

#include <iostream>
#include <queue>
#include <cassert>
#include <cstdlib>
#include <unistd.h>
#include <pthread.h>
#include<sys/prctl.h>
#include "Task.hpp"
#include "lock.hpp"
using namespace std;

const int gThreadNum = 15;

//设计为懒汉模式
template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum)
        : _threadNum(threadNum), _isStart(false)
    {
        assert(_threadNum > 0);
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T> &) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance)//仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex);//进入代码块,加锁,退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }
        return instance;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
    int ThreadNum()
    {
        return _threadNum;
    }

    //类内成员,成员函数都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        while (true)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            T t = tp->pop();
            tp->unlockQueue();
            t();//让指定的线程处理这个任务
            
        }
    }
    void start()
    {
        assert(!_isStart);
        for (int i = 0; i < _threadNum; i++)
        {
            pthread_t tmp;
            pthread_create(&tmp, nullptr, threadRoutine, this);
        }
        _isStart = true;
    }
    void push(const T &in)
    {
        lockQueue();
        _taskQueue.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }

private:
    void lockQueue() { pthread_mutex_lock(&_mutex); }
    void unlockQueue() { pthread_mutex_unlock(&_mutex); }
    bool haveTask() { return !_taskQueue.empty(); }
    void waitForTask() { pthread_cond_wait(&_cond, &_mutex); }
    void choiceThreadForHandler() { pthread_cond_signal(&_cond); }
    T pop()
    {
        T tmp = _taskQueue.front();
        _taskQueue.pop();
        return tmp;
    }

private:
    bool _isStart;
    int _threadNum;
    queue<T> _taskQueue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
    static ThreadPool<T> *instance;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

util.hpp

#include <iostream>
#include <string>
#include<cstring>
#include<stdio.h>
#include<cstdlib>
#include <ctype.h>

#include<signal.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h> 
#include<pthread.h>

#define SOCKET_ERR   1
#define BIND_ERR     2
#define LISTEN_ERR   3
#define USAGE_ERR    4
#define CONN_ERR     5

#define BUFFER_SIZE 1024

makefile

.PHONY:all
all:clientTcp serverTcpd
Method=#-DMY_SELF
clientTcp:clientTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp
serverTcpd:serverTcp.cc
	g++ -o $@ $^ $(Method) -std=c++11 -ljsoncpp -lpthread
.PHONY:clean
clean:
	rm -rf clientTcp serverTcpd ServerTcp.log

protocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cassert>
#include <cstring>
#include "util.hpp"
#include<jsoncpp/json/json.h>
using namespace std;
// 我们要在这里进行我们自己的协议定制!
// 网络版本的计算机

#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define OPS "+-*/%"

// encode,整个序列化之后的字符串进行添加长度
// strlen(四个字节)XXXXXXXX
// "strlen\r\n"XXXXXXXX\r\n --采用这种方案
// encode,整个序列化之后的字符串进行添加长度
std::string encode(const std::string &in, uint32_t len)
{
    string encodein = to_string(len);
    encodein += CRLF;
    encodein += in;
    encodein += CRLF;
    return encodein;
}

// decode,整个序列化之后的字符串进行提取长度
// 1.必须具有完整的长度
// 2.必须具有和len相符合的有效载荷
// 我们才返回有效载荷和len
// 否则我们就是一个检测函数!
// 9\r\n100 + 200\r\n 9\r\n100 + 200\r\n
std::string decode(std::string &in, uint32_t *len)
{
    assert(len);
    // 1.确认是否是一个包含len的有效字符串
    *len = 0;
    size_t pos = in.find(CRLF);
    if (pos == string::npos)
        return "";
    // 2.提取长度
    string inLen = in.substr(0, pos);
    int intLen = stoi(inLen);
    // 3.确认有效载荷也是符合要求的
    int surplus = in.size() - 2 * CRLF_LEN - pos;
    if (surplus < intLen)
        return "";
    // 4.确认有完整的报文结构
    std::string package = in.substr(pos + CRLF_LEN, intLen);
    *len = intLen;
    // 5.将当前的报文完整的从in中全部移除掉!
    long removeLen = 2 * CRLF_LEN + inLen.size() + package.size();
    in.erase(0, removeLen);
    // 6.正常返回
    return package;
}

// 定制的请求 x op y
class Request
{
public:
    Request()
    {
    }
    ~Request()
    {
    }
    // 序列化 -- 结构化的数据 -> 字符串
    // 认为结构化字段中的内容已经被填充了
    void serialize(std::string *out)
    {
#ifdef MY_SELF
        *out = to_string(_x) + SPACE + _op + SPACE + to_string(_y);
#else
        //json
        // 1.Value对象,万能对象,
        // 2.json是基于KV
        // 3.json有两套操作方法
        // 4.序列化的时候,会将所有的数据内容,转化称为字符串
        Json::Value root;
        root["x"]=_x;
        root["y"]=_y;
        root["op"]=_op;

        Json::FastWriter fw;
        // Json::StyledWriter fw;
        *out = fw.write(root);
#endif
    }

    // 反序列化 -- 字符串 -> 结构化的数据
    //  9\r\n100 + 200\r\n -> 100 + 200
    bool deserialize(std::string &in)
    {
#ifdef MY_SELF
        size_t spaceOne = in.find(SPACE);
        if (spaceOne == string::npos)
            return false;
        size_t spaceTwo = in.rfind(SPACE);
        if (spaceTwo == string::npos)
            return false;

        string dataOne = in.substr(0, spaceOne);
        string dataTwo = in.substr(spaceTwo + SPACE_LEN);
        string oper = in.substr(spaceOne + SPACE_LEN, spaceTwo - spaceOne - SPACE_LEN);
        if (oper.size() != 1)
            return false;

        // 转成内部成员
        _x = stoi(dataOne);
        _y = stoi(dataTwo);
        _op = oper[0];
        return true;

#else
        //json
        Json::Value root;
        Json::Reader rd;
        rd.parse(in,root);
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _op = root["op"].asInt();
        return true;
#endif
    }
    void debug()
    {
        cout << "#####################################" << endl;
        printf("_x=%d,_op=%c,_y=%d\n", _x, _op, _y);
        cout << "#####################################" << endl;
    }

public:
    // 需要计算的数据
    int _x;
    int _y;
    // 需要计算的种类
    char _op; //+ - * / %
};

// 定制的响应2
class Response
{
public:
    Response() : _exitCode(0), _result(0)
    {
    }
    ~Response()
    {
    }
    // 序列化 -- 不仅仅是在网络中应用,本地也是可以直接使用的
    //  "_exitCode _result"
    void serialize(std::string *out)
    {
#ifdef MY_SELF
        string ec = to_string(_exitCode);
        string res = to_string(_result);

        *out = ec + SPACE + res;
#else
        //json
        Json::Value root;
        root["code"]=_exitCode;
        root["res"]=_result;
        Json::FastWriter fw;
        // Json::StyledWriter fw;
        *out = fw.write(root);
#endif
    }
    // 反序列化
    bool deserialize(const std::string &in)
    {
#ifdef MY_SELF
        size_t pos = in.find(SPACE);
        if (pos == string::npos)
            return false;
        string codeStr = in.substr(0, pos);
        string resStr = in.substr(pos + SPACE_LEN);
        // 将反序列化的结果写入到内部成员中,形成结构化数据.
        _exitCode = stoi(codeStr);
        _result = stoi(resStr);
        return true;
#else
        //json
        Json::Value root;
        Json::Reader rd;
        rd.parse(in,root);
        _exitCode = root["code"].asInt();
        _result = root["res"].asInt();
        return true;
#endif
    }
    void debug()
    {
        cout << "#####################################" << endl;
        printf("_exitCode=%d,_result=%d\n", _exitCode, _result);
        cout << "#####################################" << endl;
    }

public:
    // 退出状态,0标识运算结果合法,非0标识运算结果是非法的,!0是几就表示是什么原因错了!
    int _exitCode;

    int _result;
};

bool makeRequest(string &str, Request *req)
{
    char strtmp[BUFFER_SIZE];
    snprintf(strtmp, sizeof strtmp, "%s", str.c_str());
    char *left = strtok(strtmp, OPS);
    if (!left)
        return false;
    char *right = strtok(nullptr, OPS);
    if (!right)
        return false;

    char mid = str[strlen(left)];
    req->_x = stoi(left);
    req->_y = stoi(right);
    req->_op = mid;
    return true;
}

clientTcp.cc

#include "util.hpp"
#include"Protocol.hpp"
#include "log.hpp"

using namespace std;

volatile bool quit =false;

static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " ip port" << endl;
    cerr << "Example\n\t" << proc << " 127.0.0.1 8080\n"
         << endl;
}

// 2.需要bind吗??--->需要,但是不需要显式的bind!
// 3.需要listen吗?不需要的!
// 4.需要accept吗?不需要的!
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    string serverIp = argv[1];
    uint16_t serverPort = stoi(argv[2]);

    // 1.创建套接字
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        cerr << "socket: " << strerror(errno) << endl;
        exit(SOCKET_ERR);
    }

    // 2.connect,发起连接请求,你想谁发起请求呢?当然是想服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    inet_aton(serverIp.c_str(), &server.sin_addr);
    server.sin_port = ntohs(serverPort);
    // 2.2发送请求,connect会自动帮我们进行bind!
    if (connect(sock, (const sockaddr *)&server, sizeof(server)) != 0)
    {
        cerr << "connect: " << strerror(errno) << endl;
        exit(CONN_ERR);
    }
    cout << "info connect success: " << sock << endl;
    //trimStr(message); //对多输入的空格进行清洗
    string message;
    while(!quit)
    {
        cout<<"请输入表达式>> "; // 1 + 1
        getline(cin,message);
        if(strcasecmp(message.c_str(),"quit")==0)
        {    
            quit=true;
            continue;
        }     
        Request req;
        if(!makeRequest(message,&req))
            continue;
        std::string package;
        req.serialize(&package);
        cout<<"debug->serialize-> \n"<<package<<endl;
        package = encode(package,package.size());
        cout<<"debug->encode-> \n"<<package<<endl;
        ssize_t s=write(sock,package.c_str(),package.size());
        if(s>0)
        {
            char buffer[BUFFER_SIZE];
            size_t s =read(sock,buffer,sizeof(buffer)-1);
            if(s > 0) buffer[s]=0;
            cout<<buffer<<endl;
            string echoPackage = buffer;
            Response resp;
            uint32_t len=0;
            cout<<"debug->get response-> "<<echoPackage<<endl;
            string tmp = decode(echoPackage,&len);
            if(len > 0)
            {
                echoPackage = tmp;
                cout<<"debug->decode-> "<<echoPackage<<endl;
                resp.deserialize(echoPackage);
                printf("[exitcode: %d] %d\n",resp._exitCode,resp._result);
            }
        }
        else if(s<=0)
        {
            break;
        }
        message.clear();
    }

    close(sock);
    return 0;
}

calServer.cc

#include "util.hpp"
#include "log.hpp"
#include "ThreadPool.hpp"
#include "Protocol.hpp"
#include "Task.hpp"
#include "daemonize.hpp"
using namespace std;

class ServerTcp;
struct ThreadData
{
    ThreadData(int sock, string clientIp, uint16_t clientPort, ServerTcp *ts)
        : _sock(sock), _clientIp(clientIp), _clientPort(clientPort), _this(ts)
    {
    }
    int _sock;
    string _clientIp;
    uint16_t _clientPort;
    ServerTcp *_this;
};
static Response calcaulator(const Request &req)
{
    Response resp;
    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(req._y == 0)
                resp._exitCode = 1; // 1:除0
            else
                resp._result = req._x / req._y;
        }
        break;
    case '%':
        {
            if(req._y == 0)
                resp._exitCode = 2; // 1:模0
            else
                resp._result = req._x % req._y;
        }
        break;
    default:
        resp._exitCode = 3; //非法操作符
        break;
    }
    return resp;
}

// 1. 全部手写
// 2.部分采用别人的方案 -- 序列化和反序列化的问题  -- xml,json,protobuf 
void netCal(int sock, const string &clientIp, uint16_t clientPort)
{
    assert(sock > 0);
    assert(!clientIp.empty());
    assert(clientPort > 1024);

    // 9\r\n100 + 200\r\n
    string inbuffer;
    while (true)
    {
        char buff[128];
        ssize_t s = read(sock, buff, sizeof(buff) - 1);
        if (s == 0)
        {
            logMessage(NOTICE, "client[%s:%d] close sock, service done", clientIp.c_str(), clientPort);
            break;
        }
        else if (s < 0)
        {
            logMessage(WARINING, "read client[%s:%d] error, errcode: %d, errormessage: %s",
                       clientIp.c_str(), clientPort, errno, strerror(errno));
            break;
        }
        cout<<buff<<endl;
        // read success
        buff[s] = 0;
        inbuffer += buff;
        // 1.检查inbuffer是不是已经具有了一个strPackage
        Request req;
        uint32_t packageLen = 0;
        string package = decode(inbuffer, &packageLen);
        if (packageLen == 0)
            continue; // 无法提取一个完整的报文,进行下一次读取
        // 2.证明已经获得一个完整的package
        if (req.deserialize(package))
        {
            req.debug();
            // 3.处理逻辑,输入一个req,得到一个resp
            Response resp = calcaulator(req); //resp是一个结构化的字段
            // 4.对resp进行序列化
            string respPackage;
            resp.serialize(&respPackage);
            // 5.对报文进行encode
            respPackage = encode(respPackage,respPackage.size());
            // 6.简单进行发送
            write(sock,respPackage.c_str(),respPackage.size());

        }
    }
}

class ServerTcp
{
public:
    ServerTcp(uint16_t port, string ip = "")
        : _listenSock(-1), _port(port), _ip(ip), _tp(nullptr)
    {
        _quit = false;
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1.创建socket
        _listenSock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSock < 0)
        {
            logMessage(FATAL, "socket:%s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket:%s,%d", strerror(errno), _listenSock);
        // 2.bind绑定
        // 2.1填充服务器
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        _ip.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(_ip.c_str(), &local.sin_addr));
        // 2.2本地socket信息,写入_sock对应的内核区域
        if (bind(_listenSock, (const sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s", strerror(errno));
        // 3.监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(_listenSock, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s", strerror(errno));
        // 允许别人来连接你了

        // 4.加载线程池
        _tp = ThreadPool<Task>::getInstance();
    }
    void loop()
    {
        // signal(SIGCHLD,SIG_IGN);//只在Linux下有效
        _tp->start();
        logMessage(DEBUG, "thread pool start success,thread num: %d", _tp->ThreadNum());

        while (!_quit)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4.获取连接,accept的返回值是一个新的socket fd??
            // 4.1 _listenScok:监听&&获取新的连接--->sock
            // 4.2 serviceSock:给用户提供新的socket服务
            int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
            if (_quit)
                break;
            if (serviceSock < 0)
            {
                // 获取连接失败
                logMessage(WARINING, "accept: &s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            string peerIp = inet_ntoa(peer.sin_addr);
            logMessage(DEBUG, "accept: %s | %s[%d],socker fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5.提供服务,小写转大写
            Task t(serviceSock, peerIp, peerPort, netCal);
            _tp->push(t);

            // logMessage(DEBUG,"server provide service start ...");
            // sleep(1);
        }
    }

    bool quitServer()
    {
        _quit = true;
    }

private:
    int _listenSock;
    uint16_t _port;
    string _ip;
    // 引入线程池
    ThreadPool<Task> *_tp;
    // 安全退出
    bool _quit;
};
static void Usage(string proc)
{
    cerr << "Usage\n\t" << proc << " port ip" << endl;
    cerr << "Example\n\t" << proc << " 8080  127.0.0.1\n"
         << endl;
}

ServerTcp *svrp = nullptr;

void sighandler(int signo)
{
    if (signo == 3 && svrp != nullptr)
    {
        svrp->quitServer();
    }
    logMessage(DEBUG, "server quit save!");
}

// ./serverTcp local_port [local_ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = stoi(argv[1]);
    string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }
    signal(3, sighandler);
    ServerTcp svr(port, ip);
    svr.init();
    svrp = &svr;
    svr.loop();
    return 0;
}

无论我们采用手写版本还是第三方库版本,又或者是其他的方案,只要保证,一段发送时构造的数据,在另一端能够正确的进行解析,这个方案就是可行的! 这种约定,就是应用层协议!


总结

(本小节完!)

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

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

相关文章

JPG格式如何转为PDF格式?快来学习如何转换

图片是我们经常用到的一种便携式文件&#xff0c;像我们日常的照片或者是一些学习资料、工作资料都是图片形式的&#xff0c;我们经常会把这些图片发送给其他人&#xff0c;这时候就需要想一个简单的办法把图片一次性发送过去&#xff0c;所以我们可以将图片转换为PDF文件&…

暨 广告、推荐、搜索 三大顶级复杂业务之 “广告业务系统详叙”

文章目录暨 广告、推荐、搜索 三大顶级复杂业务之 “广告业务系统详叙”广告系统的核心功能ADX 架构流程概述典型 ADX 架构图概述消息中心抱歉&#xff0c;有段日子没码字了&#xff0c;后面会尽量补出来分享给大家。这段时间整理了关于 “广告业务” 相关的思考&#xff0c;作…

OSPF笔记(五):OSPF虚链路--普通区域远离骨干区域

一、OSPF 虚链路 1.1 虚链路邻居关系&#xff1a; hello包只发送一次&#xff0c;没有dead时间 虚链路配置邻居指的是RID&#xff0c;非接口IP 1.2 虚链路解决的问题&#xff1a; 普通区域远离骨干区域0的问题 普通区域连接两个骨干区域0问题 &#xff08;1&#xff09;…

SpringSecurity授权功能快速上手

3. 授权 3.0 权限系统的作用 例如一个学校图书馆的管理系统&#xff0c;如果是普通学生登录就能看到借书还书相关的功能&#xff0c;不可能让他看到并且去使用添加书籍信息&#xff0c;删除书籍信息等功能。但是如果是一个图书馆管理员的账号登录了&#xff0c;应该就能看到并…

最新款发布 | 德州仪器(TI)60G单芯片毫米波雷达芯片 -xWRL6432

本文编辑&#xff1a;调皮哥的小助理 概述 最近&#xff0c;德州仪器(TI)推出了单芯片低功耗 57GHz 至 64GHz 工业(汽车)毫米波雷达传感器IWRL6432&#xff0c;具有 7GHz 的连续带宽&#xff0c;可实现更高分辨率。除了UWB雷达之外&#xff0c;IWRL6432目前是毫米波雷达带宽最…

漏洞挖掘-不安全的HTTP方法

前言&#xff1a; 年关将至&#xff0c;这可能是年前最后一篇文章了。已经有一段时间没有更新文章了&#xff0c;因为最近也没有学到什么新的知识&#xff0c;也就没什么可写的&#xff0c;又不想灌水。最近关注的好兄弟们多了很多&#xff0c;在这里也是十分感谢大家的支持&am…

Make RepVGG Greater Again | 中文翻译

性能和推理速度之间的权衡对于实际应用至关重要。而重参化可以让模型获得了更好的性能平衡&#xff0c;这也促使它正在成为现代卷积神经网络中越来越流行的架构。尽管如此&#xff0c;当需要INT8推断时&#xff0c;其量化性能通常太差&#xff0c;无法部署&#xff08;例如Imag…

SQL BETWEEN 操作符

BETWEEN 操作符用于选取介于两个值之间的数据范围内的值。 SQL BETWEEN 操作符 BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。 SQL BETWEEN 语法 SELECT column1, column2, ... FROM table_name WHERE column BETWEEN value1 AND va…

力扣714题 买卖股票的最佳时机含手续费

class Solution {public int maxProfit(int[] prices, int fee) {// 买第一天股票所需要的全部费用(买入)int buy prices[0] fee; // 利润总和int sum 0;for(int p:prices){// 如果买后些天的股票所需的全部费用比第一天的少,就买后边这天的(买入)if(p fee < buy){buy …

【Python】python深拷贝和浅拷贝(一)

【Python】python深拷贝和浅拷贝&#xff08;一&#xff09; 定义 直接赋值&#xff1a;其实就是对象的引用。浅拷贝&#xff1a;拷贝父对象&#xff0c;不会拷贝对象的内部的子对象。深拷贝&#xff1a; copy 模块的 deepcopy 方法&#xff0c;完全拷贝了父对象及其子对象。…

SpringBoot过滤器与拦截器

为什么要有过滤器和拦截器&#xff1f; 在实际开发过程中&#xff0c;经常会碰见一些比如系统启动初始化信息、统计在线人数、在线用户数、过滤敏高词汇、访问权限控制(URL级别)等业务需求。这些对于业务来说一般上是无关的&#xff0c;业务方是无需关注的&#xff0c;业务只需…

Ubuntu20.04安装ROS Noetic

一、实验环境准备 1.使用系统:Ubuntu20.04&#xff08;安装不做赘述&#xff0c;可看我另外一篇博客Ubuntu20.04安装&#xff09;&#xff0c;可到Ubuntu官网下载https://ubuntu.com/download/desktop 2.配置网络&#xff0c;使其可通互联网 二、在Ubuntu20.04上搭建ROS机器人…

树上差分-LCA

树上差分算法分析&#xff1a;练习例题差分的基本思想详情见博客&#xff08;一维、二维差分&#xff09;&#xff1a; https://blog.csdn.net/weixin_45629285/article/details/111146240 算法分析&#xff1a; 面向的对象可以是树上的结点&#xff0c;也可以是树上的边 结点…

springmvc 文件上传请求转换为MultipartFile的过程

前言: 最近在研究文件上传的问题,所以就写下这个博客,让大家都知道从流转换为MutipartFile的过程,不然你就知道在方法中使用,而不知道是怎么样处理的,是不行的 从DiaspatherServlet说起: 别问为啥,去了解tomcat和servlet的关系,我后面会 写这篇博客的 servlet的生命周期 ini…

[ 数据结构 ] 查找算法--------线性、二分、插值、斐波那契查找

0 前言 查找算法有4种: 线性查找 二分查找/折半查找 插值查找 斐波那契查找 1 线性查找 思路:线性遍历数组元素,与目标值比较,相同则返回下标 /**** param arr 给定数组* param value 目标元素值* return 返回目标元素的下标,没找到返回-1*/public static int search(…

63.Python 调用类的属性和方法

63.调用类的属性和方法 文章目录63.调用类的属性和方法1. 调用属性的语法2.调用类的方法3.总结1. 调用属性的语法 我们根据类创建了一张奥迪A6的小汽车。根据汽车流水线呢&#xff0c;汽车生产完之后&#xff0c;需要移交给检查部门检查车辆的外观、颜色(属性)等是否与图纸一致…

JavaEE高阶---SpringBoot 统一功能处理

一&#xff1a;什么是SpringBoot 统⼀功能处理 SpringBoot统一功能处理是AOP的实战环节。我们主要学习三方面内容&#xff1a; 统一用户登录权限验证&#xff1b;统一数据格式返回&#xff1b;统一异常处理。 二&#xff1a;统一用户登录权限验证 Spring 中提供了具体的实现…

Scala 集合常用函数

文章目录集合常用函数一、基本属性和常用操作1、常用方法2、案例示例二、衍生集合1、衍生集合常用方法操作2、案例示例三、集合简单计算函数1、常用计算操作2、案例示例四、集合计算高级函数1、语义说明2、案例示例集合常用函数 一、基本属性和常用操作 1、常用方法 (1) 获取…

Release库与Debug库混用导致释放堆内存时产生异常的详细分析

目录 1、问题描述 2、使用Windbg启动Debug版本的exe程序进行分析 3、进一步分析 4、问题复盘 5、为什么Debug库与Release库混用可能会出异常&#xff1f; 6、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;ht…

DM8:dexpdimp-逻辑导出--逻辑导入

DM8:dexp&dimp-逻辑导出--逻辑导入1 dexp逻辑导出dmp文件1.1 全库导出命令附加的参数信息1.2 导出用户所拥有权限的数据库对象-命令附加的参数信息1.3 导出用户所拥有权限的数据库对象-命令附加的参数信息2 dimp--逻辑导入dmp文件2.1 全库导入dmp数据文件-命令附加的参数信…