最新资讯

  • Linux:自定义协议+序列反序列化

Linux:自定义协议+序列反序列化

2025-05-04 21:00:44 1 阅读

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

一,再次理解协议

思考1: 我们究竟是如何将数据发送出去的?

-------->

(1)我们都知道TCP是双加工的,所以在内核中存在着发送缓冲区和接受缓冲区,而我们write是写入发送缓冲区的,read是从接受缓冲区里读的,所以我们会发现这两个函数其实就是拷贝函数!!write将数据从用户层拷贝到内核层就返回,read将数据从内核层拷贝到用户层就返回,意思就是我用户不管,反正我把数据都交给你OS了,发送过程中的可靠性由你来维护。(就像当年我们只需要将内容写到文件内核缓冲区,而由OS来决定什么时候,以什么方式刷新到磁盘上) 

(2)所以TCP协议是属于操作系统的网络模块部分,他之所以叫做传输控制协议,就是因为他需要确保数据传输过程中的可靠性,比方说我什么时候应该发给对方?要发多少?万一出错了怎么办?

 思考2:回忆管道和文件系统

------------->

(1)以往我们往管道文件里写了很多次的时候,可能我们一次就全部读上来了       

(2)而在文件系统中,我们的写很容易,可以分很多次写,各种类型比如整数/字符串…… 但是读的时候就很难去读,同时不同的文件的读取方式可能也不一样,比如按行读也仅仅只是读取文件的一种方式而已。

思考3:TCP是面向字节流的,你怎么保证你读上来的数据一定是一个"完整的报文"呢?

------------>

(1)  早期的时候我们可能会想到比方说我们约定必须要凑齐多少字节才往上读,但是这个其实只适用于一些固定类型的报文,比方说我们规定读的是int,而int对应的就是一个错误码或者是某一个固定的任务。但是如果是一些长度不固定的报文,比如说我们在聊天的时候发送的字符串长度都是不一样的,那么这个时候就很容易读到不完整的报文

(2)所以为了应对这种情况,我们就需要在报文直接加入一下分割符,或者是标识这个报文有多长,以确保能够读到完整的报文, 而协议要添加报头就会有很多新得问题出来-比如序列化和反序列化。

二,序列化和反序列化

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

-----------> 

(1)同一个结构体在不同的编译器下编译的大小不一定一样(结构体的内存对齐),其实Linux的协议就是传结构体做到的,但是他底层可以把所有方方面面的情况都考虑到了,但是我们用户去定的时候很难考虑得这么周全。

(2) 可以如果不用结构体的话,我们如果用字符串呢?举个例子,我们聊天的时候我发了一句哈哈,但是发送的时候还会带上昵称以及发送时间,所以我们肯定不能把这三个字符串分开发,因为这也服务端就不知道你这话是谁说的,所以我肯定希望把三个字符串打包成一个字符串发过去,然后服务的把消息广播给所有客户端的时候,会再把这个包根据一定的方法解析分成三个字符串然后再给你显现出来!------------->也就是说我们需要在类里面定义两种方法,一种是把类内的数据打包成一个字符串发送过去,另一种是解析的时候将这个字符串再拆分出来。 而这个过程就是序列化和反序列化

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

--------------->

约定方案一:

客户端发送一个形如"1+1"的字符串;

这个字符串中有两个操作数, 都是整形;

两个数字之间会有一个字符是运算符, 运算符只能是 + ;

数字和运算符之间没有空格;

...

约定方案二:

定义结构体来表示我们需要交互的信息;

发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转 化回结构体; 这个过程叫做 "序列化" 和”反序列化“

三,实现网络计算器 

3.1 日志文件

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define LogFile "log.txt"
class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    // void logmessage(int level, const char *format, ...)
    // {
    //     time_t t = time(nullptr);
    //     struct tm *ctime = localtime(&t);
    //     char leftbuffer[SIZE];
    //     snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
    //              ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
    //              ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

    //     // va_list s;
    //     // va_start(s, format);
    //     char rightbuffer[SIZE];
    //     vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
    //     // va_end(s);

    //     // 格式:默认部分+自定义部分
    //     char logtxt[SIZE * 2];
    //     snprintf(logtxt, sizeof(logtxt), "%s %s
", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }
    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);

//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }

//     va_end(s); //s = NULL
//     return sum;
// }
Log lg; // 命令对象 用来打印日志信息

3.2 Socket.hpp

我们可以写个套接字的小组件,这样未来我们就可以直接去使用

#pragma once
//写一个套接字的小组件 这样我们未来就可以直接用
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "Log.hpp"

enum {
    SocketError = 1,
    BindError =2,
    ListenError = 3,
    AcceptError = 4
};
const int defaultbacklog = 10;//监听队列默认长度
class Sock
{
public:
    Sock(){}
    ~Sock(){}
    void Socket()//创建套接字
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd<0)
        {
            lg(Fatal,"socket error,%s:%d",strerror(errno),errno);
            exit(SocketError);
        }
    }
    void Bind(uint16_t port)//绑定套接字
    {
        struct sockaddr_in addr;
        bzero(&addr, sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = INADDR_ANY;
        if(bind(_sockfd, (struct sockaddr*)&addr, sizeof(addr))<0)
        {
            lg(Fatal,"bind error,%s:%d",strerror(errno),errno);
            exit(BindError);
        }
    }
    void Listen(int backlog=defaultbacklog)//监听套接字
    {
        if(listen(_sockfd, backlog)<0)
        {
            lg(Fatal,"listen error,%s:%d",strerror(errno),errno);
            exit(ListenError);
        }
    }

    int Accept(std::string *clientip,uint16_t *clientport)//接受套接字并把客户端的ip和端口返回(输出型参数)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int connfd = accept(_sockfd, (struct sockaddr*)&client, &len);
        if(connfd<0)
        {
            lg(Error,"accept error,%s:%d",strerror(errno),errno);//如果接受失败 打印错误信息并退出程序
            exit(AcceptError);
        }
         char ipstr[64];
        inet_ntop(AF_INET, &client.sin_addr,ipstr, sizeof(ipstr));
        *clientip = ipstr;
        *clientport = ntohs(client.sin_port);
        lg(Info,"accept a client %s:%d",*clientip,*clientport);//表示接受成功了并打印客户端的ip和端口
        return connfd;
    }
    int GetSockfd()//获取套接字
    {
        return _sockfd;
    }
    void Close()//关闭套接字
    {
        close(_sockfd);
    }
    bool Connect(const std::string &ip,const uint16_t &port)//连接套接字
    {
        struct sockaddr_in addr;
        bzero(&addr, sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &addr.sin_addr) ;//将ip地址转换为网络字节序
        if(connect(_sockfd, (struct sockaddr*)&addr, sizeof(addr))<0)
        {
            lg(Warning,"connect error,%s:%d",strerror(errno),errno);
            return false;
        }
        return true;
    }

private:
    int _sockfd;
};

3.3 TcpServer.hpp

#pragma once
#include 
#include 
#include "Socket.hpp"

using func_t = std::function; // 回调函数

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t callback) : _port(port), _callback(callback), _isrunning(false)
    {
    }
    ~TcpServer() {}

    void InitServer()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen(10);
        lg(Info, "Server is running on port %d", _port); // 看看服务器在哪个端口上运行
    }
    void Start() // 启动服务器
    {
        _isrunning = true;
        signal(SIGCHLD, SIG_IGN); // 忽略子进程结束信号 因为子进程结束时会产生SIGCHLD信号
        signal(SIGPIPE, SIG_IGN); // 忽略管道错误信号 因为当网络连接中某个套接字被关闭或者重启时,该套接字已经发送缓冲区中的数据都发送完毕了,但是它仍然可以接收数据 此时该套接字就会产生SIGPIPE信号
        while (_isrunning)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = _listensock.Accept(&clientip, &clientport); // 接受客户端连接并返回套接字描述符
            if (sockfd < 0)
                continue;       // 连接失败就继续尝试重连
            pid_t pid = fork(); // 创建子进程 帮助我们处理每个客户端的请求
            if (pid < 0)        // 出错
            {
                lg(Error, "Fork error");
                continue;
            }
            if (pid == 0) // 子进程
            {
                _listensock.Close();          // 关闭监听套接字 防止accept阻塞
                std::string inbuffer_stream; // 用来获取客户端发来的所有数据
                while (true)
                {
                    char buffer[1024];
                    ssize_t n = read(sockfd, buffer, sizeof(buffer)); // 读取客户端数据
                    if (n == 0)                                       // 客户端断开了
                    {
                        lg(Info, "Client %s:%d disconnected", clientip.c_str(), clientport);
                        break;
                    }
                    if (n < 0) // 读取出错
                    {
                        lg(Error, "Read error");
                        break;
                    }
                    // 拼接所有数据
                    buffer[n]=0;
                    inbuffer_stream+=buffer;
                    lg(Debug, "debug:
%s", inbuffer_stream.c_str());// 调试看看整个流的信息
                    //有可能一个流里面有多个报文,所以我们要循环去处理
                    while(1)
                    {
                        std::string info =_callback(inbuffer_stream);// 调用回调函数获取服务端需要的数据
                        //看看剩余的报文信息
                        if(info.empty()) break;//说明读不到完整报文了
                        lg(Debug, "debug:
%s", inbuffer_stream.c_str());// 调试看看整个流的信息
                        lg(Debug, "debug,response:
%s",info.c_str());// 调试看看发给客户端数据
                        write(sockfd, info.c_str(), info.size()); // 发送数据给客户端
                    }
                }
                exit(0); // 子进程结束
            }
            close(sockfd); // 关闭套接字
        }
    }

private:
    uint16_t _port;   // 端口号
    Sock _listensock; // 监听套接字
    func_t _callback; // 回调函数 用来提供服务
    // ip模式是0
    bool _isrunning; // 是否运行
};

1、tcpserver只负责网络通信  读到了那些数据,但是关于数据的解析 全部交给callback回调函数去处理   这样就是将网络通信和协议解析进行了解耦

2、因为我们并不确定读到的是否是一个完整报文,所以我们要将读到的内容加到inbuffer-stream里 

3.4 Protocol.hpp

协议其实就是我们双方约定好的结构化字段 

     为了确保读到完整的报文,在前面加个有关报文长度的字段是是一种方案,在报文的后面加个分割符其实也是一个方案    

     甚至我们可以在前面增加  protocol select字段,表示我们选择的不同的协议类型! 

 "len"/n"x op y"/n  就是我们约定request的协议,  里面要提供序列化和反序列化的方法

”len“/n"result code"/n 就是我们约定的respose的协议  里面要提供序列化和反序列化的方法

#pragma once

#include 
#include 

// #define MySelf 1

const std::string blank_space_sep = " ";
const std::string protocol_sep = "
";

std::string Encode(std::string &content)
{
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;

    return package;
}

// "len"
"x op y"
XXXXXX
// "protocolnumber"
"len"
"x op y"
XXXXXX
bool Decode(std::string &package, std::string *content)
{
    std::size_t pos = package.find(protocol_sep);
    if(pos == std::string::npos) return false;
    std::string len_str = package.substr(0, pos);
    std::size_t len = std::stoi(len_str);
    // package = len_str + content_str + 2
    std::size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len) return false;

    *content = package.substr(pos+1, len);
    // earse 移除报文 package.erase(0, total_len);
    package.erase(0, total_len);

    return true;
}


// json, protobuf
class Request
{
public:
    Request(int data1, int data2, char oper) : _x(data1), _y(data2),_op(oper)
    {
    }
    Request()
    {}
public:
    bool Serialize(std::string *out)
    {
        // 构建报文的有效载荷
        // struct => string, "x op y"
        std::string s = std::to_string(_x);
        s += blank_space_sep;
        s += _op;
        s += blank_space_sep;
        s += std::to_string(_y);
        *out = s;
        return true;
    }
    bool Deserialize(const std::string &in) // "x op y"
    {
        std::size_t left = in.find(blank_space_sep);
        if (left == std::string::npos)
            return false;
        std::string part_x = in.substr(0, left);

        std::size_t right = in.rfind(blank_space_sep);
        if (right == std::string::npos)
            return false;
        std::string part_y = in.substr(right + 1);

        if (left + 2 != right)
            return false;
        _op = in[left + 1];
        _x = std::stoi(part_x);
        _y = std::stoi(part_y);
        return true;
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成:  " << _x << _op << _y << "=?" << std::endl;
    }
public:
    // x op y
    int _x;
    int _y;
    char _op; // + - * / %
};

class Response
{
public:
    Response(int res, int c) : _result(res), _code(c)
    {
    }

    Response()
    {}
public:
    bool Serialize(std::string *out)
    {
        // "result code"
        // 构建报文的有效载荷
        std::string s = std::to_string(_result);
        s += blank_space_sep;
        s += std::to_string(_code);
        *out = s;
        return true;
    }
    bool Deserialize(const std::string &in) // "result code"
    {
        std::size_t pos = in.find(blank_space_sep);
        if (pos == std::string::npos)
            return false;
        std::string part_left = in.substr(0, pos);
        std::string part_right = in.substr(pos+1);

        _result = std::stoi(part_left);
        _code = std::stoi(part_right);

        return true;

    }
    void DebugPrint()
    {
        std::cout << "结果响应完成, result: " << _result << ", code: "<< _code << std::endl;
    }
public:
    int _result;
    int _code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

不仅需要有序列化和反序列化的方法,还要有添加报头和解析报头(要有很多检查 将一个有效的报文提取出来)的方法

 3.5 Servercal.hpp

// 计算器服务

#include 
#include "Protocol.hpp" //协议头文件 必须遵守

enum
{
    Div_Zero = 1,
    Mod_Zero = 2,
    Other_Oper = 3
};


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

Response Calhelper(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 (req._y == 0) resp._code = Div_Zero;
       else resp._result = req._x / req._y;
       break;
   case '%':
       if (req._y == 0) resp._code = Mod_Zero;
       else resp._result = req._x % req._y;
       break;
   default:
       resp._code=Other_Oper;
       break;
   }
 return resp;
}

//设计一个回调方法 来帮助我们计算
std::string Cal(std::string package) //回调方法  到时传过去
{
    std::string content;//返回的内容
    bool r=Decode(package,&content);
    if(!r)  return "";//报文不完整
    //我们要将这个报文反序列化拿到数据 然后计算成respond 然后再序列化发给客户端
    Request req;
    r=req.Deserialize(content); //"10 + 20 " ->
    if(!r) return ""; //解析失败
    content=""; //清空再利用
    Response resp=Calhelper(req);
    resp.Serialize(&content);
    content=Encode(content);//把报头加上去
    return content;
}

};

 在这里写一个回调方法,如果解析失败的话就返回空串

 3.6 ServerCal.cc

#include"ServerCal.hpp"
#include"TcpServer.hpp"

static void Usage(const std::string &proc)
{
    std::cout << "
Usage: " << proc << " port
" << std::endl; 
}

int main(int argc, char *argv[])//./servercal 8080
{
   if(argc != 2)
   {
       Usage(argv[0]);
       exit(0);
   }
   uint16_t port = atoi(argv[1]);
   ServerCal cal;//用来做计算请求的对象
   TcpServer *tsvp = new TcpServer(port,std::bind(&ServerCal::Cal, &cal, std::placeholders::_1));//bind
   tsvp->InitServer();
 // Daemon();
    daemon(0, 0);
   tsvp->Start();

   
   return 0;
}

3.7 ClientCal.cc

#include 
#include 
#include 
#include 
#include 
#include "Socket.hpp"
#include "Protocol.hpp" //客户端也得知道协议

static void Usage(const std::string &proc)
{
    std::cout << "
Usage: " << proc << " serverip serverport
"
              << std::endl;
}

//./client 127.0.0.1 8080
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return -1;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    Sock sockfd;
    sockfd.Socket();
    bool r=sockfd.Connect(serverip, serverport);//尝试和服务端连接
    if(!r)
    {
        std::cout<<"连接失败"< 0)
        {
            buffer[n] = 0;
            inbuffer_stream += buffer; // "len"
"result code"

            std::cout << inbuffer_stream << std::endl;
            std::string content;
            bool r = Decode(inbuffer_stream, &content); // "result code"
            assert(r);

            Response resp;
            r = resp.Deserialize(content);
            assert(r);

            resp.DebugPrint();
        }

        std::cout << "=================================================" << std::endl;
        sleep(1);

        cnt++;
    }


    sockfd.Close();
    return 0;
}

3.8 Daemon.hpp

#pragma once

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

const std::string nullfile = "/dev/null";

void Daemon(const std::string &cwd = "")
{
    // 1. 忽略其他异常信号
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2. 将自己变成独立的会话
    if (fork() > 0)
        exit(0);
    setsid();

    // 3. 更改当前调用进程的工作目录
    if (!cwd.empty())
        chdir(cwd.c_str());

    // 4. 标准输入,标准输出,标准错误重定向至/dev/null
    int fd = open(nullfile.c_str(), O_RDWR);
    if(fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        close(fd);
    }
}

3.9 json简单使用

其实我们有更好的两个工具能帮我们完成序列化和反序列化,一个是json 一个是productor  

关于json的下载

安装这个json,其实就是将头文件和源文件安装在制定的路径下

头文件:

库文件:

使用第三方库必须要指定链接  

测试代码: 

#include 
#include 
#include 

// {a:120, b:"123"}
int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    //Json::FastWriter w;
    Json::StyledWriter w;
    std::string res = w.write(root);

    std::cout << res << std::endl;

    sleep(3);

    Json::Value v;
    Json::Reader r;
    r.parse(res, v);

    int x = v["x"].asInt();
    int y = v["y"].asInt();
    char op = v["op"].asInt();
    std::string desc = v["desc"].asString();
    Json::Value temp = v["test"];
    std::cout << x << std::endl;
    std::cout << y << std::endl;
    std::cout << op << std::endl;
    std::cout << desc << std::endl;



    return 0;
}

(1)头文件必须要指明路径

(2)Json是万能类 Value代表对象

(3)FastWrite是快速写  StyleWrite是风格写

(4)asInt表示解析成int类型

JsonCpp 使用指导-菜鸟笔记

 3.10 Protocol.hpp改进版

#pragma once

#include 
#include 
#include
// #define MySelf 1

const std::string blank_space_sep = " ";
const std::string protocol_sep = "
";

std::string Encode(std::string &content)
{
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;

    return package;
}

// "len"
"x op y"
XXXXXX
// "protocolnumber"
"len"
"x op y"
XXXXXX
bool Decode(std::string &package, std::string *content)
{
    std::size_t pos = package.find(protocol_sep);
    if(pos == std::string::npos) return false;
    std::string len_str = package.substr(0, pos);
    std::size_t len = std::stoi(len_str);
    // package = len_str + content_str + 2
    std::size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len) return false;

    *content = package.substr(pos+1, len);
    // earse 移除报文 package.erase(0, total_len);
    package.erase(0, total_len);

    return true;
}


// json, protobuf
class Request
{
public:
    Request(int data1, int data2, char oper) : _x(data1), _y(data2),_op(oper)
    {
    }
    Request()
    {}
public:
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // 构建报文的有效载荷
        // struct => string, "x op y"
        std::string s = std::to_string(_x);
        s += blank_space_sep;
        s += _op;
        s += blank_space_sep;
        s += std::to_string(_y);
        *out = s;
        return true;
#else
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["op"] = _op;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "x op y"
    {
#ifdef MySelf
        std::size_t left = in.find(blank_space_sep);
        if (left == std::string::npos)
            return false;
        std::string part_x = in.substr(0, left);

        std::size_t right = in.rfind(blank_space_sep);
        if (right == std::string::npos)
            return false;
        std::string part_y = in.substr(right + 1);

        if (left + 2 != right)
            return false;
        _op = in[left + 1];
        _x = std::stoi(part_x);
        _y = std::stoi(part_y);
        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _op = root["op"].asInt();
        return true;
#endif
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成:  " << _x << _op << _y << "=?" << std::endl;
    }
public:
    // x op y
    int _x;
    int _y;
    char _op; // + - * / %
};

class Response
{
public:
    Response(int res, int c) : _result(res), _code(c)
    {
    }

    Response()
    {}
public:
    bool Serialize(std::string *out)
    {
  #ifdef MySelf
        // "result code"
        // 构建报文的有效载荷
        std::string s = std::to_string(_result);
        s += blank_space_sep;
        s += std::to_string(_code);
        *out = s;
        return true;
#else
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "result code"
    {
#ifdef MySelf
        std::size_t pos = in.find(blank_space_sep);
        if (pos == std::string::npos)
            return false;
        std::string part_left = in.substr(0, pos);
        std::string part_right = in.substr(pos+1);

        _result = std::stoi(part_left);
        _code = std::stoi(part_right);

        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        _result = root["result"].asInt();
        _code = root["code"].asInt();
        return true;
#endif
    }
    void DebugPrint()
    {
        std::cout << "结果响应完成, result: " << _result << ", code: "<< _code << std::endl;
    }
public:
    int _result;
    int _code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

 3.11 Makefile

.PHONY:all
all:servercal clientcal

Flag=#-DMySelf=1
Lib=-ljsoncpp

servercal:ServerCal.cc
	g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cc
	g++ -o $@ $^ -std=c++11 -g $(Lib) $(Flag)


.PHONY:clean
clean:
	rm -f clientcal servercal

四,再谈七层协议 

 会话层: 由服务端解决获取新链接,维护整个链接的使用情况,创建子进程来对外提供服务,相当于没访问一次服务我就会创建一个新的会话,然后去处理新的连接,不需要就关掉

表示层:相当于协议的序列化和反序列化

应用层:处理数据,不同的数据需要有不同的协议

为什么要压成一层呢??因为以上都是在用户层去实现的,因为方法由很多但是谁也说服不了谁,所以无法统一把他搞到OS模块而必须由用户根据不同的场景去定制

每次服务都要自己去定自定义协议吗??其实是不需要的,有人想就会有人去做,所以应用层的协议大部分不需要自己写,可以直接用就可以了,因为他全都考虑到了。 一般公司产品比较严格不会让你使用第三方库。

本文地址:https://www.vps345.com/8090.html

搜索文章

Tags

PV计算 带宽计算 流量带宽 服务器带宽 上行带宽 上行速率 什么是上行带宽? CC攻击 攻击怎么办 流量攻击 DDOS攻击 服务器被攻击怎么办 源IP 服务器 linux 运维 游戏 云计算 javascript 前端 chrome edge ubuntu 阿里云 网络 网络安全 网络协议 llama 算法 opencv 自然语言处理 神经网络 语言模型 php 人工智能 ssh python MCP tcp/ip macos adb centos openvpn server openvpn配置教程 centos安装openvpn 数据库 oracle 关系型 安全 分布式 asm docker 机器学习 java 经验分享 mysql android deepseek Ollama 模型联网 API CherryStudio json RTSP xop RTP RTSPServer 推流 视频 Dify 容器 debian PVE 进程 操作系统 进程控制 Ubuntu 科技 ai 个人开发 tomcat nginx 负载均衡 windows 开发环境 C# MQTTS 双向认证 emqx harmonyos 华为 开发语言 typescript 计算机网络 vscode 笔记 物联网 mcu iot 信息与通信 filezilla 无法连接服务器 连接被服务器拒绝 vsftpd 331/530 智能路由器 外网访问 内网穿透 端口映射 jellyfin nas word图片自动上传 word一键转存 复制word图片 复制word图文 复制word公式 粘贴word图文 粘贴word公式 ue5 vr c++ 嵌入式 linux驱动开发 arm开发 嵌入式硬件 ip命令 新增网卡 新增IP 启动网卡 产品经理 agi microsoft vim 数据结构 学习 c语言 学习方法 并查集 leetcode spring boot HarmonyOS Next Docker Hub docker pull 镜像源 daemon.json Linux k8s kubernetes numpy YOLO efficientVIT YOLOv8替换主干网络 TOLOv8 unix rust http DevEco Studio 网络药理学 生信 生物信息学 gromacs 分子动力学模拟 MD 动力学模拟 虚拟机 VMware vue3 HTML audio 控件组件 vue3 audio音乐播放器 Audio标签自定义样式默认 vue3播放音频文件音效音乐 自定义audio播放器样式 播放暂停调整声音大小下载文件 jenkins 运维开发 云原生 github 创意 社区 c# llm transformer Flask FastAPI Waitress Gunicorn uWSGI Uvicorn git elasticsearch 英语 xcode ide Qwen2.5-coder 离线部署 鸿蒙 powerpoint 自动化 pycharm 互信 pip conda 高级IO epoll dify Dell R750XS ui 华为云 华为od java-ee MacOS录屏软件 面试 性能优化 jdk intellij-idea 架构 etcd 数据安全 RBAC 宝塔面板访问不了 宝塔面板网站访问不了 宝塔面板怎么配置网站能访问 宝塔面板配置ip访问 宝塔面板配置域名访问教程 宝塔面板配置教程 gitee GaN HEMT 氮化镓 单粒子烧毁 辐射损伤 辐照效应 vue.js audio vue音乐播放器 vue播放音频文件 Audio音频播放器自定义样式 播放暂停进度条音量调节快进快退 自定义audio覆盖默认样式 思科 进程信号 开源 实时音视频 实时互动 protobuf 序列化和反序列化 安装 ssl mac 游戏程序 ios EtherCAT转Modbus ECT转Modbus协议 EtherCAT转485网关 ECT转Modbus串口网关 EtherCAT转485协议 ECT转Modbus网关 fastapi mcp mcp-proxy mcp-inspector fastapi-mcp agent sse 前端框架 深度学习 目标检测 计算机视觉 spring LDAP udp 域名服务 DHCP 符号链接 配置 aws googlecloud virtualenv vSphere vCenter 软件定义数据中心 sddc mongodb 权限 腾讯云 HCIE 数通 电脑 rabbitmq maven intellij idea postgresql rpc 微服务 springcloud VMware安装mocOS macOS系统安装 飞牛nas fnos react.js 前端面试题 node.js 持续部署 jmeter 软件测试 微信 微信分享 Image wxopensdk gateway Clion Nova ResharperC++引擎 Centos7 远程开发 Ubuntu共享文件夹 共享目录 Linux共享文件夹 ollama下载加速 大模型 编辑器 websocket dubbo 多线程 html5 firefox sqlserver kamailio sip VoIP 大数据 大数据平台 指令 mq rocketmq kafka 系统开发 binder 车载系统 framework 源码环境 后端 pillow https rust腐蚀 Linux PID WSL win11 无法解析服务器的名称或地址 设置代理 实用教程 iperf3 带宽测试 小程序 微信小程序域名配置 微信小程序服务器域名 微信小程序合法域名 小程序配置业务域名 微信小程序需要域名吗 微信小程序添加域名 vnc fd 文件描述符 AI编程 AI写作 程序员创富 集成学习 集成测试 NFS ffmpeg 音视频 命名管道 客户端与服务端通信 WebUI DeepSeek V3 DeepSeek AI大模型 ux 迁移指南 程序人生 golang log4j apache minicom 串口调试工具 测试工具 远程 命令 执行 sshpass 操作 pytorch FTP 服务器 DeepSeek-R1 API接口 stm32 qt web安全 中间件 iis 多线程服务器 Linux网络编程 单片机 live555 rtsp rtp 工业4.0 远程控制 远程看看 远程协助 客户端 远程工作 RAGFLOW C 环境变量 进程地址空间 Cline 自动化编程 gcc centos 7 chatgpt AI 爬虫 数据集 硬件工程 向日葵 selenium Hyper-V WinRM TrustedHosts fstab DigitalOcean GPU服务器购买 GPU服务器哪里有 GPU服务器 matplotlib ros2 moveit 机器人运动 Kali Linux 黑客 渗透测试 信息收集 eureka prometheus 监控k8s 监控kubernetes 1024程序员节 mount挂载磁盘 wrong fs type LVM挂载磁盘 Centos7.9 压测 ECS wireshark 显示过滤器 ICMP Wireshark安装 alias unalias 别名 bash CPU 内存 主板 电源 网卡 rime bug VMware安装Ubuntu Ubuntu安装k8s 安装教程 GPU环境配置 Ubuntu22 CUDA PyTorch Anaconda安装 windows 服务器安装 僵尸进程 .netcore 源码剖析 rtsp实现步骤 流媒体开发 ESXi hive Hive环境搭建 hive3环境 Hive远程模式 Samba NAS 大模型技术 本地部署大模型 WSL2 go 代理模式 监控k8s集群 集群内prometheus flash-attention 报错 无人机 list 模拟实现 cpu 实时 使用 5G 3GPP 卫星通信 聚类 linux环境变量 ollama C语言 openwrt ipython threejs 3D flutter 虚拟局域网 ai小智 语音助手 ai小智配网 ai小智教程 智能硬件 esp32语音助手 diy语音助手 redis selete ip Ubuntu Server Ubuntu 22.04.5 监控 自动化运维 政务 分布式系统 监控运维 Prometheus Grafana gnu YOLOv8 NPU Atlas800 A300I pro asi_bench 系统架构 设计模式 软件工程 环境配置 mybatis AIGC 统信UOS 麒麟 bonding 链路聚合 机器人 低代码 课程设计 unity ping++ ddos stm32项目 数据挖掘 网络用户购物行为分析可视化平台 大数据毕业设计 gpu算力 基础环境 llama3 Chatglm 开源大模型 docker搭建pg docker搭建pgsql pg授权 postgresql使用 postgresql搭建 zotero WebDAV 同步失败 安防软件 linux上传下载 温湿度数据上传到服务器 Arduino HTTP dell服务器 docker搭建nacos详解 docker部署nacos docker安装nacos 腾讯云搭建nacos centos7搭建nacos gitlab 隐藏文件 隐藏目录 文件系统 管理器 通配符 路径解析 ssh漏洞 ssh9.9p2 CVE-2025-23419 flask 深度求索 私域 知识库 vmware 卡死 ansible playbook 剧本 云桌面 微软 AD域控 证书服务器 博客 sublime text docker compose svn sql KingBase UOS 统信操作系统 yum oceanbase rc.local 开机自启 systemd uni-app visual studio code tcpdump 代码调试 ipdb bcompare Beyond Compare kylin 智能手机 Termux JAVA Java spring cloud Agent WSL2 上安装 Ubuntu YOLOv12 rag ragflow ragflow 源码启动 Portainer搭建 Portainer使用 Portainer使用详解 Portainer详解 Portainer portainer MQTT协议 消息服务器 代码 视觉检测 ue4 着色器 虚幻 目标跟踪 OpenVINO 推理应用 VMware创建虚拟机 postman mock mock server 模拟服务器 mock服务器 Postman内置变量 Postman随机数据 kali 共享文件夹 QT 5.12.12 QT开发环境 Ubuntu18.04 开发 cuda cudnn anaconda Windsurf Reactor C++ .net linux安装配置 OpenManus 数据分析 中兴光猫 换光猫 网络桥接 自己换光猫 串口服务器 CLion 远程连接 IDE webstorm jar gradle fpga开发 docker-compose 鸿蒙系统 缓存 firewalld 强制清理 强制删除 mac废纸篓 KylinV10 麒麟操作系统 Vmware 计算机外设 软件需求 ESP32 camera Arduino 电子信息 xml iBMC UltraISO 豆瓣 追剧助手 迅雷 express p2p 银河麒麟 kylin v10 麒麟 v10 ShenTong 国产化 okhttp CORS 跨域 雨云 NPS 自动化任务管理 开机自启动 dns prompt easyui langchain uniapp vue 程序员 大模型入门 mamba powerbi 信息可视化 css nlp 致远OA OA服务器 服务器磁盘扩容 虚拟显示器 树莓派 VNC 多层架构 解耦 cmos 硬件 arm dba 能力提升 面试宝典 技术 IT信息化 大模型微调 ros yum源切换 更换国内yum源 webrtc 重启 排查 系统重启 日志 原因 线程 图像处理 3d matlab 分析解读 边缘计算 代理 AutoDL ruoyi 毕设 DeepSeek r1 Open WebUI 业界资讯 鲲鹏 硬件架构 计算机 模拟退火算法 媒体 信号 code-server MQTT mosquitto 消息队列 android studio HarmonyOS NEXT 原生鸿蒙 DNS django sqlite 华为认证 网络工程师 交换机 MS Materials openssl 密码学 mariadb 微信小程序 miniapp 真机调试 调试 debug 断点 网络API请求调试方法 AISphereButler 通信工程 毕业 远程桌面 shell AI-native Docker Desktop 免费域名 域名解析 MacMini Mac 迷你主机 mini Apple AP配网 AK配网 小程序AP配网和AK配网教程 WIFI设备配网小程序UDP开 ci/cd echarts 网页设计 宠物 毕业设计 免费学习 宠物领养 宠物平台 策略模式 单例模式 小艺 Pura X excel 安全架构 安全威胁分析 本地部署AI大模型 elk Java Applet URL操作 服务器建立 Socket编程 网络文件读取 devops Deepseek 大模型教程 remote-ssh perf linux内核 国产操作系统 ukui 麒麟kylinos openeuler 统信 虚拟机安装 Linux的权限 Doris搭建 docker搭建Doris Doris搭建过程 linux搭建Doris Doris搭建详细步骤 Doris部署 pdf 办公自动化 自动化生成 pdf教程 框架搭建 大语言模型 bat 服务器繁忙 springboot web 云服务器 VPS pyqt arcgis Ubuntu DeepSeek DeepSeek Ubuntu DeepSeek 本地部署 DeepSeek 知识库 DeepSeek 私有化知识库 本地部署 DeepSeek DeepSeek 私有化部署 zabbix 蓝耘科技 元生代平台工作流 ComfyUI 其他 安卓模拟器 VR手套 数据手套 动捕手套 动捕数据手套 RustDesk自建服务器 rustdesk服务器 docker rustdesk web3.py big data npm opensearch helm pyautogui 服务器主板 AI芯片 职场和发展 飞书 MI300x 孤岛惊魂4 Dell HPE 联想 浪潮 WebRTC gpt zip unzip CentOS Stream CentOS 软链接 硬链接 输入法 恒源云 av1 电视盒子 机顶盒ROM 魔百盒刷机 系统安全 SSH 服务 SSH Server OpenSSH Server RTMP 应用层 jupyter 数学建模 网络结构图 本地环回 bind IPMITOOL BMC 硬件管理 minio neo4j 数据仓库 数据库开发 数据库架构 database opcua opcda KEPServer安装 LLM CrewAI oneapi edge浏览器 open webui 自动驾驶 kvm qemu libvirt asp.net大文件上传 asp.net大文件上传源码 ASP.NET断点续传 asp.net上传文件夹 asp.net上传大文件 .net core断点续传 .net mvc断点续传 游戏服务器 TrinityCore 魔兽世界 QQ bot Docker keepalived 推荐算法 adobe 传统数据库升级 银行 LLMs sonoma 自动更新 Python 网络编程 聊天服务器 套接字 TCP Socket Linux无人智慧超市 LInux多线程服务器 QT项目 LInux项目 单片机项目 蓝桥杯 群晖 可信计算技术 矩阵 springsecurity6 oauth2 授权服务器 token sas 服务器管理 宝塔面板 配置教程 服务器安装 网站管理 chrome devtools chromedriver cd 目录切换 文心一言 HarmonyOS ArcTS 登录 ArcUI GridItem arkUI 飞牛 asp.net大文件上传下载 文件分享 Xinference RAGFlow VSCode 移动云 云服务 服务器数据恢复 数据恢复 存储数据恢复 raid5数据恢复 磁盘阵列数据恢复 Redis Desktop visualstudio 银河麒麟操作系统 apt 国内源 ROS2 Cursor nfs 服务器部署ai模型 virtualbox tensorflow 部署 SSL 域名 rsyslog Anolis nginx安装 环境安装 linux插件下载 信号处理 ubuntu24 vivado24 UOS1070e 三级等保 服务器审计日志备份 xpath定位元素 k8s集群资源管理 云原生开发 rclone AList webdav fnOS Trae AI 原生集成开发环境 Trae AI 网站搭建 serv00 Ubuntu22.04 虚拟化 开发人员主页 驱动开发 嵌入式实习 OpenHarmony 微信开放平台 微信公众平台 微信公众号配置 远程登录 telnet URL lsb_release /etc/issue /proc/version uname -r 查看ubuntu版本 RoboVLM 通用机器人策略 VLA设计哲学 vlm fot robot 视觉语言动作模型 具身智能 本地部署 api banner Linux的基础指令 Kylin-Server WLAN 图形渲染 EasyConnect rancher html ecmascript nextjs react reactjs pygame 小游戏 五子棋 黑客技术 sdkman 嵌入式系统开发 流式接口 大文件分片上传断点续传及进度条 如何批量上传超大文件并显示进度 axios大文件切片上传详细教 node服务器合并切片 vue3大文件上传报错提示错误 大文件秒传跨域报错cors 网工 c 大模型面经 大模型学习 搜索引擎 毕昇JDK ssrf 失效的访问控制 n8n dity make safari 系统 上传视频至服务器代码 vue3批量上传多个视频并预览 如何实现将本地视频上传到网页 element plu视频上传 ant design vue vue3本地上传视频及预览移除 历史版本 下载 nvidia etl 软件构建 实习 hadoop 相机 searxng xrdp string模拟实现 深拷贝 浅拷贝 经典的string类问题 三个swap WebVM mysql离线安装 ubuntu22.04 mysql8.0 半虚拟化 硬件虚拟化 Hypervisor SSL证书 流水线 脚本式流水线 源码 交互 ip协议 混合开发 JDK odoo 服务器动作 Server action 雨云服务器 考研 计算生物学 生物信息 基因组 京东云 c/c++ 串口 环境迁移 崖山数据库 YashanDB firewall 命令行 基础入门 编程 pgpool Ubuntu 24.04.1 轻量级服务器 redhat 金仓数据库 2025 征文 数据库平替用金仓 端口测试 田俊楠 nftables 防火墙 centos-root /dev/mapper yum clean all df -h / du -sh 相差8小时 UTC 时间 netty 代码托管服务 outlook r语言 数据可视化 用户缓冲区 IIS .net core Hosting Bundle .NET Framework vs2022 直播推流 chrome 浏览器下载 chrome 下载安装 谷歌浏览器下载 区块链 支付 微信支付 开放平台 docker命令大全 Typore 技能大赛 W5500 OLED u8g2 TCP服务器 SenseVoice 状态管理的 UDP 服务器 Arduino RTOS ubuntu20.04 ros1 Noetic 20.04 apt 安装 openEuler docker run 数据卷挂载 交互模式 gitea risc-v MySql Jellyfin tcp cocoapods cnn 邮件APP 免费软件 网络攻击模型 wsl 版本 rustdesk lb 协议 联想开天P90Z装win10 Invalid Host allowedHosts 强化学习 yolov8 bootstrap Kali 渗透 软考 飞牛NAS 飞牛OS MacBook Pro gpt-3 SysBench 基准测试 昇腾 npu 服务器时间 eNSP 网络规划 VLAN 企业网络 Docker Compose 云电竞 云电脑 todesk OD机试真题 华为OD机试真题 服务器能耗统计 显卡驱动 宕机切换 服务器宕机 AnythingLLM AnythingLLM安装 企业微信 Linux24.04 deepin 像素流送api 像素流送UE4 像素流送卡顿 像素流送并发支持 压力测试 交叉编译 DIFY 序列化反序列化 MCP server C/S windows日志 链表 主从复制 TRAE trea idea export import save load 迁移镜像 Minecraft 智能音箱 智能家居 DOIT 四博智联 Cookie nac 802.1 portal k8s资源监控 annotations自动化 自动化监控 监控service 监控jvm 实战案例 freebsd can 线程池 XCC Lenovo ruby 繁忙 解决办法 替代网站 汇总推荐 AI推理 游戏引擎 thingsboard iftop 网络流量监控 cfssl yolov5 iDRAC R720xd ocr make命令 makefile文件 拓扑图 镜像 健康医疗 互联网医院 双系统 GRUB引导 Linux技巧 skynet IM即时通讯 剪切板对通 HTML FORMAT springboot远程调试 java项目远程debug docker远程debug java项目远程调试 springboot远程 saltstack Playwright 自动化测试 XFS xfs文件系统损坏 I_O error ceph 键盘 embedding 测试用例 功能测试 虚拟现实 ssh远程登录 磁盘监控 安卓 P2P HDLC 服务器配置 ROS 私有化 程序 性能分析 FunASR ASR LORA NLP file server http server web server RAID RAID技术 磁盘 存储 muduo X11 Xming uv AI代码编辑器 Alexnet wps Linux awk awk函数 awk结构 awk内置变量 awk参数 awk脚本 awk详解 rdp 实验 图形化界面 腾讯云大模型知识引擎 Vmamba 王者荣耀 yaml Ultralytics 可视化 Wi-Fi 多进程 jetty undertow less CH340 串口驱动 CH341 uart 485 conda配置 conda镜像源 rnn 小番茄C盘清理 便捷易用C盘清理工具 小番茄C盘清理的优势尽显何处? 教你深度体验小番茄C盘清理 C盘变红?!不知所措? C盘瘦身后电脑会发生什么变化? RAG 检索增强生成 文档解析 大模型垂直应用 Google pay Apple pay KVM 设备 GPU PCI-Express OpenSSH jina 深度优先 图论 并集查找 换根法 树上倍增 模拟器 教程 Docker引擎已经停止 Docker无法使用 WSL进度一直是0 镜像加速地址 SSH 金融 seatunnel x64 SIGSEGV SSE xmm0 稳定性 看门狗 Erlang OTP gen_server 热代码交换 事务语义 MNN Qwen 医疗APP开发 app开发 composer 游戏机 hugo Netty 即时通信 NIO 产测工具框架 IMX6ULL 管理框架 SWAT 配置文件 服务管理 网络共享 gaussdb vpn k8s二次开发 集群管理 DeepSeek行业应用 Heroku 网站部署 DBeaver sqlite3 micropython esp32 mqtt 思科模拟器 Cisco nuxt3 AI作画 ArkTs ArkUI IIS服务器 IIS性能 日志监控 next.js 部署next.js 聊天室 进程优先级 调度队列 进程切换 银河麒麟服务器操作系统 系统激活 宝塔 算力 Mac内存不够用怎么办 lio-sam SLAM HiCar CarLife+ CarPlay QT RK3588 hibernate Node-Red 编程工具 流编程 fonts-noto-cjk windwos防火墙 defender防火墙 win防火墙白名单 防火墙白名单效果 防火墙只允许指定应用上网 防火墙允许指定上网其它禁止 ubuntu24.04.1 单元测试 NLP模型 社交电子 自学笔记 小米 澎湃OS Android 数据库系统 高效远程协作 TrustViewer体验 跨设备操作便利 智能远程控制 灵办AI vscode 1.86 工作流 workflow grafana 直流充电桩 充电桩 IPMI 换源 Debian 读写锁 SEO chfs ubuntu 16.04 上传视频文件到服务器 uniApp本地上传视频并预览 uniapp移动端h5网页 uniapp微信小程序上传视频 uniapp app端视频上传 uniapp uview组件库 漏洞 kind spark fast 同步 备份 建站 curl wget 网络穿透 火绒安全 数据管理 数据治理 数据编织 数据虚拟化 Nuxt.js Xterminal wsl2 IPv4 子网掩码 公网IP 私有IP idm SSH 密钥生成 SSH 公钥 私钥 生成 Ark-TS语言 trae 弹性计算 裸金属服务器 弹性裸金属服务器 linux 命令 sed 命令 GCC crosstool-ng unity3d su sudo jvm deekseek rpa 音乐库 备选 网站 调用 示例 AD域 视频编解码 word 反向代理 python2 ubuntu24.04 话题通信 服务通信 Linux find grep 钉钉 查询数据库服务IP地址 SQL Server 语音识别 本地化部署 开源软件 语法 智能电视 EtherNet/IP串口网关 EIP转RS485 EIP转Modbus EtherNet/IP网关协议 EIP转RS485网关 EIP串口服务器 抓包工具 wpf 内核 自定义客户端 SAS 硅基流动 ChatBox 离线部署dify 技术共享 eclipse 热榜 flink 极限编程 qt5 客户端开发 知识图谱 华为机试 安装MySQL Qwen2.5-VL vllm Reactor反应堆 卷积神经网络 java-rocketmq 做raid 装系统 armbian u-boot LInux 李心怡 WebServer 回显服务器 UDP的API使用 perl 项目部署到linux服务器 项目部署过程 ftp 升级 CVE-2024-7347 MDK 嵌入式开发工具 论文笔记 react native VM搭建win2012 win2012应急响应靶机搭建 攻击者获取服务器权限 上传wakaung病毒 应急响应并溯源 挖矿病毒处置 应急响应综合性靶场 g++ g++13 docker部署Python 企业网络规划 华为eNSP llama.cpp 7z web3 win服务器架设 windows server 运维监控 vscode1.86 1.86版本 ssh远程连接 open Euler dde LLM Web APP Streamlit GameFramework HybridCLR Unity编辑器扩展 自动化工具 AI员工 onlyoffice IMM nvm whistle 网络爬虫 sysctl.conf vm.nr_hugepages 单一职责原则 Linux 维护模式 xshell termius iterm2 DenseNet 服务器扩容没有扩容成功 状态模式 授时服务 北斗授时 opengl 电视剧收视率分析与可视化平台 HistoryServer Spark YARN jobhistory Headless Linux 服务网格 istio 增强现实 沉浸式体验 应用场景 技术实现 案例分析 AR 视频平台 录像 视频转发 性能测试 视频流 VS Code ebpf uprobe 僵尸世界大战 游戏服务器搭建 java-rabbitmq kotlin iphone zookeeper 影刀 #影刀RPA# 虚幻引擎 DocFlow 合成模型 扩散模型 图像生成 hexo Windows ai工具 v10 软件 ldap 小智AI服务端 xiaozhi TTS Sealos 论文阅读 AD 域管理 GIS 遥感 WebGIS 怎么卸载MySQL MySQL怎么卸载干净 MySQL卸载重新安装教程 MySQL5.7卸载 Linux卸载MySQL8.0 如何卸载MySQL教程 MySQL卸载与安装 nosql 大大通 第三代半导体 碳化硅 鸿蒙开发 移动开发 figma Helm k8s集群 架构与原理 捆绑 链接 谷歌浏览器 youtube google gmail burp suite 抓包 axure 富文本编辑器 visual studio 多个客户端访问 IO多路复用 TCP相关API 内网环境 deep learning Ubuntu 24 常用命令 Ubuntu 24 Ubuntu vi 异常处理 h.264 黑苹果 软件卸载 系统清理 烟花代码 烟花 元旦 宝塔面板无法访问 开机黑屏 sequoiaDB cpp-httplib Mermaid 可视化图表 Web服务器 多线程下载工具 PYTHON 网络建设与运维 网络搭建 神州数码 神州数码云平台 云平台 copilot 我的世界服务器搭建 minecraft GoogLeNet prometheus数据采集 prometheus数据模型 prometheus特点 网卡的名称修改 eth0 ens33 浪潮信息 AI服务器 PPI String Cytoscape CytoHubba vue-i18n 国际化多语言 vue2中英文切换详细教程 如何动态加载i18n语言包 把语言json放到服务器调用 前端调用api获取语言配置文件 SRS 流媒体 直播 Unity Dedicated Server Host Client 无头主机 Deepseek-R1 私有化部署 推理模型 regedit 开机启动 在线office 常用命令 文本命令 目录命令 python3.11 dash 正则表达式 webgl 抗锯齿 玩机技巧 软件分享 软件图标 远程过程调用 Windows环境 sentinel es 高效日志打印 串口通信日志 服务器日志 系统状态监控日志 异常记录日志 PX4 midjourney 问题解决 swoole FTP服务器 无桌面 ubuntu 18.04 Carla 智能驾驶 top Linux top top命令详解 top命令重点 top常用参数 佛山戴尔服务器维修 佛山三水服务器维修 TrueLicense MVS 海康威视相机 浏览器开发 AI浏览器 加解密 Yakit yaklang dock 加速 Python基础 Python教程 Python技巧 超融合 干货分享 黑客工具 密码爆破 移动魔百盒 proxy模式 USB转串口 我的世界 我的世界联机 数码 性能调优 安全代理 本地知识库部署 DeepSeek R1 模型 harmonyOS面试题 triton 模型分析 线性代数 电商平台 samba ecm bpm C++软件实战问题排查经验分享 0xfeeefeee 0xcdcdcdcd 动态库加载失败 程序启动失败 程序运行权限 标准用户权限与管理员权限 kernel Claude ranger MySQL8.0 网页服务器 web服务器 Nginx 磁盘清理 执法记录仪 智能安全帽 smarteye tailscale derp derper 中转 cursor 音乐服务器 Navidrome 音流 人工智能生成内容 deepseek r1 北亚数据恢复 oracle数据恢复 容器技术 多路转接 IO openstack Xen 匿名管道 CDN notepad 沙盒 H3C mcp服务器 client close 前后端分离 tar 服务器无法访问 ip地址无法访问 无法访问宝塔面板 宝塔面板打不开 USB网络共享 服务器安全 网络安全策略 防御服务器攻击 安全威胁和解决方案 程序员博客保护 数据保护 安全最佳实践 navicat 项目部署 代理服务器 uni-file-picker 拍摄从相册选择 uni.uploadFile H5上传图片 微信小程序上传图片 浏览器自动化 阿里云ECS 个人博客 Attention rtsp服务器 rtsp server android rtsp服务 安卓rtsp服务器 移动端rtsp服务 大牛直播SDK Linux环境 milvus 智慧农业 开源鸿蒙 团队开发 Spring Security 远程服务 deployment daemonset statefulset cronjob grub 版本升级 扩容 ISO镜像作为本地源 游戏开发 Linux权限 权限命令 特殊权限 阻塞队列 生产者消费者模型 服务器崩坏原因 vu大文件秒传跨域报错cors frp 磁盘镜像 服务器镜像 服务器实时复制 实时文件备份 kerberos MAC SecureCRT 备份SQL Server数据库 数据库备份 傲梅企业备份网络版 大模型部署 pppoe radius IO模型 cron crontab日志 tidb GLIBC UEFI Legacy MBR GPT U盘安装操作系统 查看显卡进程 fuser ArtTS HTTP 服务器控制 ESP32 DeepSeek K8S k8s管理系统 dns是什么 如何设置电脑dns dns应该如何设置 银河麒麟桌面操作系统 Kylin OS xss 在线预览 xlsx xls文件 在浏览器直接打开解析xls表格 前端实现vue3打开excel 文件地址url或接口文档流二进 国产数据库 瀚高数据库 数据迁移 下载安装 seleium db glibc TCP协议 端口聚合 windows11 搭建个人相关服务器 MacOS 嵌入式Linux IPC 达梦 DM8 AI agent 机柜 1U 2U EMUI 回退 降级 vasp安装 Logstash 日志采集 分布式训练 信创 信创终端 中科方德 对比 工具 meld DiffMerge 多端开发 智慧分发 应用生态 鸿蒙OS 国标28181 视频监控 监控接入 语音广播 流程 SIP SDP System V共享内存 进程通信 wordpress 无法访问wordpess后台 打开网站页面错乱 linux宝塔面板 wordpress更换服务器 Radius ELF加载 qt项目 qt项目实战 qt教程 云耀服务器 计算虚拟化 弹性裸金属 docker desktop image 银河麒麟高级服务器 外接硬盘 Kylin 物联网开发 根服务器 clickhouse 生活 burpsuite 安全工具 mac安全工具 burp安装教程 渗透工具 React Next.js 开源框架 软负载 EMQX 通信协议 元服务 应用上架 IDEA junit CosyVoice IMX317 MIPI H265 VCU 显示管理器 lightdm gdm maxkb ARG 大模型应用 端口 查看 ss aarch64 编译安装 HPC 内网服务器 内网代理 内网通信 MAVROS 四旋翼无人机 欧标 OCPP docker部署翻译组件 docker部署deepl docker搭建deepl java对接deepl 翻译组件使用 需求分析 规格说明书 lua hosts fork wait waitpid exit DevOps 软件交付 数据驱动 数字证书 签署证书 网易邮箱大师 AzureDataStudio 解决方案 风扇控制软件 wsgiref Web 服务器网关接口 镜像下载 oracle fusion oracle中间件 ardunio BLE 多产物 网络文件系统 yum换源 服务器正确解析请求体 autodl lvm 磁盘挂载 磁盘分区 桌面环境 高效I/O macOS Mac软件 服务器部署 本地拉取打包 大屏端 CNNs 图像分类 shell脚本免交互 expect linux免交互 MobaXterm NAT转发 NAT Server Web应用服务器 scapy Zoertier 内网组网 css3 ABAP 存储维护 NetApp存储 EMC存储 搜狗输入法 中文输入法 scikit-learn compose UDP 流量运营 网络管理 2024 2024年上半年 下午真题 答案 西门子PLC 通讯 输入系统 粘包问题 Unity插件 iventoy VmWare OpenEuler mysql安装报错 windows拒绝安装 跨平台 CPU 使用率 系统监控工具 linux 命令 带外管理 大模型推理 Qualcomm WoS QNN AppBuilder SVN Server tortoise svn HAProxy cmake 华为证书 HarmonyOS认证 华为证书考试 laravel 图片增强 增强数据 AI Agent 字节智能运维 接口优化 mm-wiki搭建 linux搭建mm-wiki mm-wiki搭建与使用 mm-wiki使用 mm-wiki详解 联机 僵尸毁灭工程 游戏联机 开服 HP Anyware 笔灵AI AI工具 ECT转485串口服务器 ECT转Modbus485协议 ECT转Modbus串口服务器 deepseek-r1 大模型本地部署 k8s部署 MySQL8.0 高可用集群(1主2从) 内网渗透 靶机渗透 Docker快速入门 显示器 VPN wireguard sudo原理 su切换 蓝桥杯C++组 csrutil mac恢复模式进入方法 恢复模式 VMware Tools vmware tools安装 vmwaretools安装步骤 vmwaretools安装失败 vmware tool安装步骤 vm tools安装步骤 vm tools安装后不能拖 vmware tools安装步骤 负载测试 iNode Macos ShapeFile GeoJSON xfce ArkTS 移动端开发 js 错误代码2603 无网络连接 2603 deepseak 豆包 KIMI 腾讯元宝 lighttpd安装 Ubuntu配置 Windows安装 服务器优化 蓝牙 零售 免密 公钥 私钥 小智 弹性服务器 stable diffusion 联网 easyconnect WINCC 用户管理 Pyppeteer Chatbox Tabs组件 TabContent TabBar TabsController 导航页签栏 滚动导航栏 Apache Beam 批流统一 案例展示 数据分区 容错机制 netlink libnl3 动态规划 底层实现 代码规范 vite zerotier NFC 近场通讯 智能门锁 Isaac Sim 虚拟仿真 es6 qt6.3 g726 nohup后台启动 信息安全 GeneCards OMIM TTD qps 高并发 iTerm2 终端 文件传输 archlinux kde plasma nacos EVE-NG 进程间通信 VGG网络 卷积层 池化层 服务器ssl异常解决 配置原理 Linux Vim 工具分享 ubuntu安装 linux入门小白 yashandb 桌面快捷方式 pyicu gunicorn 充电桩平台 充电桩开源平台 锁屏不生效 ftp服务 文件上传 环境搭建 Maven glm4 录音麦克风权限判断检测 录音功能 录音文件mp3播放 小程序实现录音及播放功能 RecorderManager 解决录音报错播放没声音问题