应用程自定义协议与序列化反序列化

news2024/11/16 7:53:12

        本篇将主要介绍在应用层中自定义的协议,自定义协议的同时还需要将我们的数据继续序列化和反序列化,所以本篇的重点为序列化、反序列化的原因以及如何自定义协议,接着探讨了关于为什么 tcp 协议可以支持全双工协议。还根据用户自定义协议结合 tcp socket 编写了一份三层结构(应用层、表示层、会话层)的网络计算器代码。

目录

自定义协议

序列化 / 反序列化

read、write、recv、send 和 tcp协议为什么支持全双工

网络版本计算器

自定义协议

        我们程序猿写的代码,解决的一个个实际问题,满足我们日常需求的网络程序,都是在应用层实现的。当我们想要使用一些能满足自己需求的协议的时候,这个时候我们就可以自定义协议。

        我们在前文中(Linux网络基础-CSDN博客)已经提到,协议通信双方的一种约定,是通信双方都认识的结构化数据,所以我们设定协议,设定出结构化数据,然后使用接口发送和接收数据即可。

        假设我们实现一个网络版本的计算器,只需要客户端发送对应的两个操作数以及操作符,然后由服务端计算数据之后将结构发回给客户端即可,所以现在存在两个方法:

        方案一:

        1. 客户端发送一个形如 “1 + 1” 的字符串 / 或者定义一个两个操作数一个操作符结构体

        2. 这个字符串中有两个操作数,都是整数 

        3. 两个整数之间会有一个字符都是运算符,运算符只能是 + - * / %

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

        ......

        方案二:

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

        2. 发送数据时将这个结构体按照一个规则转化为字符串,接收到数据的时候再按照相同的规则把字符串转换回结构体;

        3. 这个过程就叫做 “序列化” 和 “反序列化”

        但是对于方案一来说,对于不同操作系统,对于结构体定义时的内存对齐、字节对齐等等的都可能不同,大小可能都不一样,所以发送接收时很可能就会导致两端拿到的数据不一致,但是即使是跨平台出现的问题,我们也可以一个一个的解决,但即使我们解决这些问题,对我们的协议进行修改的时候,也会导致新的问题,所以严重不推介方案一(除非仅仅用于本地通信)。

序列化 / 反序列化

        对于序列化和反序列的可以形象的理解为下图:

        如上图,从发送方获取到结构体数据,然后将结构体数据序列化,经过网络发送给服务器端,在发送到服务器端的应用层的时候,将数据反序列化,然后向上层输出。

        但是对于 tcp 协议而言是面向字节流的,也就意味着每次发送、接收数据的时候,根本就不清楚自己发送出去的数据或者接收到的数据是一个完整的数据、部分的数据、还是多个数据,这些情况都有可能,那么 tcp 如何保证读到的数据是一个完成的数据呢?tcp 则需要对我们的报文进行分割,然后加上对用的报头,在接收方接收数据的时候,就会从报头中的信息来分析得到的数据是否是一个完整的数据。

read、write、recv、send 和 tcp协议为什么支持全双工

        在我们使用tcp协议,使用read、write、send、recv等系统调用发送数据的过程,其实是按照一下过程发送数据的:

        如上图所示,对应的一个 sockfd 打开了两个文件缓冲区,一个是接收缓冲区一个是发送缓冲区(一个 sockfd 就代表着一个连接),其中使用的 write 接口就是将数据拷贝进发送缓冲区,read 就是将数据从接收缓冲区中拷贝出来(write、read、send、recv本质就是拷贝函数)。

        所以,对于发送数据的本质就是从发送方的发送缓冲区把数据通过协议栈和网络拷贝给接收方的接收缓冲区,其中一个 sockfd 拥有两个文件缓冲区,也就是意味着主机可以边使用 write 写数据,也可以使用 read 读数据,读写不会冲突,这就是 tcp 支持全双工的本质原因

        tcp 协议(传输控制协议)可以决定当前的发送缓冲区发送什么数据,发送多少数据,发送数据出错了如何解决,对于接收数据同样如此,但是同时 tcp 所属的传输层是属于操作系统内核的,所以本质来讲,对于数据的拷贝、发送等,都还是在操作系统在干预

        同时对于以上的发送、接收操作,其实也是属于生产者消费模型。既然属于生产消费模型,那么对于我们的发送缓冲区和接收缓冲区本质就是临界资源,对于临界资源我们就需要将其进行保护以及同步,所以这就是为什么发送缓冲区满了 write 会阻塞,接收缓冲区空了 read 会阻塞

网络版本计算器

        接下来将模仿上述发送接收数据的方式写一个网络计算器,简单来说就是将两个操作数和一个操作符交给服务端,服务端计算完成之后发送回来。

        TcpClient.cc          客户端的代码

#include <iostream>
#include <ctime>
#include <unistd.h>
#include "Protocol.hpp"
#include "Socket.hpp"
#include "Log.hpp"

using namespace protocol_ns;
using namespace socket_ns;
using namespace log_ns;

int main(int argc, char* args[]) {
    if (argc != 3) {
        LOG(ERROR, "please input the -> ./client、ip and port\n");
        return 0;
    }
    // 获取 ip 和 port 以及 socket
    uint16_t server_port = std::stoi(args[2]);
    std::string server_ip = args[1];

    // 创建sockfd,以及将其连接起来
    ScokSPtr socket = std::make_shared<TcpSocket>();
    if (!socket->BuildCilentSocket(server_port, server_ip)) {
        LOG(FATAL, "connect fail\n");
        return 1;
    }

    srand(time(nullptr) ^ getpid());
    std::string operators("+-*/!^&");

    // 连接成功,现在开始通信
    while (true) {
        // 1. 先创建出需要解决的Request
        int x = rand() % 10, y = rand() % 10;
        usleep(1000 * x * y);
        char oper = operators[y % operators.size()];
        std::shared_ptr<Request> Req = Factory::BuildRequest();
        Req->SetValue(x, y, oper);

        std::string jsonstr;
        // 1. 将数据序列化
        jsonstr = Req->Serialize();

        // 2. 为数据添加报头
        jsonstr = Encode(jsonstr);

        // 3. 将数据发送出去
        ssize_t n = socket->Send(jsonstr);

        std::cout << "----------------------------------------------" << std::endl;
        // 将发送出去的jsonstr打印出来
        std::cout << "jsonstr\n" << jsonstr << std::endl;

        if (n == 0) {
            LOG(ERROR, "send message failed\n");
            exit(1);
        }


        while (true) {
            // 4. 现在开始接收消息
            std::shared_ptr<Responce> Resp = Factory::BuildResponce();
            std::string packagestreamqueue;
            socket->Recv(&packagestreamqueue);

            // 5. 将收到的信息解码
            std::string package = Decode(packagestreamqueue);
            if (package.empty()) continue; 
            // 将解码后的数据打印出来
            std::cout << "package\n" << package << std::endl;

            // 6. 反序列化
            Resp->Deserialize(package);

            // 7. 将数据打印出来
            Resp->PrintResult();

            break;
        }

        sleep(1);
    }

    return 0;
}

        TcpServer.cc             服务端的代码

#include "TcpServer.hpp"   // 通信管理,负责建立和断开通信 -> 会话层
#include "NetCal.hpp"      // 针对特定的计算协议 -> 应用层
#include "IOService.hpp"   // 负责数据个数转换 -> 表示层
#include <memory>

// 自己建立端口号
int main(int argc, char* argv[]) {
    if (argc != 2) {
        LOG(ERROR, "please input: ./server port\n");
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);
    
    NetCal calculator;
    calculate_t cal = std::bind(&NetCal::Calculator, &calculator, std::placeholders::_1);
    IOService io_forword;
    tcp_task_t task = std::bind(
        &IOService::IOForword, 
        &io_forword, cal, 
        std::placeholders::_1, 
        std::placeholders::_2
    );
    
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(task, port);
    tsvr->Init();
    tsvr->Start();

    return 0;
}

        Socket.hpp     使用模板方式方法封装的 socket 接口,因为创建接口和连接等操作都是一定格式的,所以将其设置为模板(参考:UDP/TCP --- Socket编程-CSDN博客)

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <memory>
#include <cstring>
#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>
#include "Log.hpp"
#include "InetAddr.hpp"

namespace socket_ns {
    using namespace log_ns;

    enum {
        SOCKET_ERROR = 1,
        BIND_ERROR,
        LISTEN_ERROR
    };

    const int gblcklog = 8;

    class TcpSocket;
    class Socket;
    using ScokSPtr = std::shared_ptr<Socket>;

    // 模板方法模式
    class Socket {
    public:
        Socket() {}
        ~Socket() {}

        virtual int CreateSocketOrDie() = 0;
        virtual void CreateBindOrDie(uint16_t port) = 0;
        virtual void CreateListenOrDie(int blcklog = gblcklog) = 0;
        virtual int CreateAccepte(InetAdrr* addr) = 0;
        virtual bool CreateConnector(uint16_t server_port, std::string server_ip) = 0;
    
        virtual ssize_t Recv(std::string* out) = 0;
        virtual ssize_t Send(std::string& in) = 0;
        virtual int GetSockfd() = 0;
    public:
        void BuildListenSocket(uint16_t port, int blcklog = gblcklog) {
            // 分别是创建sockfd,然后将其绑定,然后listen
            CreateSocketOrDie();
            CreateBindOrDie(port);
            CreateListenOrDie(blcklog);
        }

        bool BuildCilentSocket(uint16_t server_port, std::string server_ip) {
            // 分别是创建sockfd,然后绑定,然后connnect
            CreateSocketOrDie();
            return CreateConnector(server_port, server_ip);
        }
    };


    class TcpSocket : public Socket {
    public:
        TcpSocket() {}

        TcpSocket(int sockfd)
            : _sockfd(sockfd)
        {}

        // 创建 sockfd
        int CreateSocketOrDie() override {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0) {
                LOG(FATAL, "create sockfd fail\n");
                exit(SOCKET_ERROR);
            }
            LOG(INFO, "get listensockfd success, sockfd: %d\n", _sockfd);
            return _sockfd;
        }


        // 绑定
        void CreateBindOrDie(uint16_t port) override {
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(port);
            local.sin_addr.s_addr = INADDR_ANY;

            int bind_n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
            if (bind_n < 0) {
                LOG(FATAL, "bind listensockfd fail, the reason: %s\n", strerror(errno));
                exit(BIND_ERROR);
            }
            LOG(INFO, "bind success\n");
        }

        // 绑定之后listen
        void CreateListenOrDie(int blcklog = gblcklog) override {
            int n = listen(_sockfd, blcklog);
            if (n < 0) {
                LOG(FATAL, "listen socket fail\n");
                exit(LISTEN_ERROR);
            }
            LOG(INFO, "listen sucess\n");            
        }

        int CreateAccepte(InetAdrr* addr) override {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_sockfd, (struct sockaddr*)&peer, &len);
            *addr = peer;
            
            return sockfd;
        }

        bool CreateConnector(uint16_t server_port, std::string server_ip) override {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            socklen_t len = sizeof(server);
            server.sin_family = AF_INET;
            server.sin_port = htons(server_port);
            // server.sin_addr.s_addr = inet_addr(server_ip.c_str());
            inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);

            int n = connect(_sockfd, (struct sockaddr*)&server, sizeof(server));
            if (n < 0) {
                return false;                
            }
            return true;
        }

        ssize_t Recv(std::string* out) override {
            // 收消息,将收到的信息
            char buff[4096];
            ssize_t n = recv(_sockfd, buff, sizeof(buff), 0);
            if (n <= 0) return n;
            buff[n] = 0;
            *out += buff;
            return n;
        }

        ssize_t Send(std::string& in) override {
            ssize_t n = send(_sockfd, in.c_str(), in.size(), 0);
            return n;
        }

        int GetSockfd() override {
            return _sockfd;
        }

        ~TcpSocket() {
            if (_sockfd < 0) close(_sockfd);
        }
    private:
        int _sockfd;
    };
}

        TcpServer.hpp                服务端的头文件,其中主要实现的是调用逻辑

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#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>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "Socket.hpp"

using namespace log_ns;
using namespace socket_ns;

enum {
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR
};

const int glistensockfd = -1;
const int gblcklog = 8;

using tcp_task_t = std::function<void(ScokSPtr, InetAdrr&)>;

class TcpServer {
private:
    
    // 创建一个内部类
    struct ThreadData {
        ScokSPtr _tcp_socket;
        InetAdrr _addr;
        TcpServer* _tcp_point;

        
        ThreadData(const ScokSPtr& tcpsocket, const InetAdrr& addr, TcpServer* tcp)
            : _tcp_socket(tcpsocket),
              _addr(addr),
              _tcp_point(tcp)
        {}
    };

    static void* runServer(void* args) {
        // 将线程分离,就不用阻塞的join线程
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        // LOG(INFO, "the sockfd: %d\n", td->_sockfd);

        td->_tcp_point->_task(td->_tcp_socket, td->_addr);
        // close(); // 将其转化为tcpsocket变量则不需要显示的close了,因为已经析构了
        delete td;
        return nullptr;
    }

public:
    // TcpServer(){}
    TcpServer(tcp_task_t task, uint16_t port)
        : _task(task),
          _port(port),
          _isrunning(false),
          _tcp_socket(std::make_shared<TcpSocket>())
    {}

    void Init() {
        _tcp_socket->BuildListenSocket(_port);
    }

    void Start() {
        _isrunning = true;
        while (_isrunning) {

            InetAdrr addr;
            int sockfd = _tcp_socket->CreateAccepte(&addr);
            if (sockfd < 0) {
                LOG(ERROR, "%s get sockfd fail, the reason is %s\n", addr.AddrString().c_str(), strerror(errno));
                continue;
            }
            LOG(INFO, "get sockfd success, sockfd: %d\n", sockfd);

            // 为accept建立一个tcpsocket变量
            ScokSPtr tcp_accept = std::make_shared<TcpSocket>(sockfd);

            // 2. 多线程
            pthread_t tid;
            ThreadData* data = new ThreadData(tcp_accept, addr, this);
            pthread_create(&tid, nullptr, runServer, (void*)data);
        }

        _isrunning = false;
    }

    ~TcpServer() {
        
    }
private:
    uint16_t _port;
    // int _listensocked;
    bool _isrunning;

    tcp_task_t _task;

    ScokSPtr _tcp_socket;
};

        Thread.hpp              线程头文件,便于创建多线程执行流

#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <cstring>
#include <cerrno>
#include "Log.hpp"

// using func_t = std::function<void(const std::string& name, pthread_mutex_t* lock)>;
using func_t = std::function<void(const std::string& name)>;
using namespace log_ns;
// typedef void*(*func_t)(void*);

const pthread_t ctid = -1;

class Thread {
private:
    void excute() {
        // std::cout << _name << " begin to run" << std::endl;
        // LOG(INFO, "%s begin to run\n", _name.c_str());
        _isrunning = true;
        _func(_name);
        _isrunning = false;
    }

    static void* ThreadRoutine(void* args) {
        Thread* self = static_cast<Thread*>(args);
        self->excute();
        return nullptr;
    }
public:
    Thread(func_t func, const std::string& name) 
        : _func(func),
          _isrunning(false),
          _tid(ctid),
          _name(name)
    {}

    ~Thread() {}

    void Start() {
        // 创建之后就开始运行了
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, (void*)this);
        if (n != 0) {
            std::cout << "thread create failed!!!" << std::endl;
            exit(1);
        }
    }

    void Stop() {
        // 将线程暂停,使用
        if (_isrunning == false) return;
        // std::cout << _name << " stop " << std::endl;

        int n = ::pthread_cancel(_tid);
        if (n != 0)  {
            std::cout << "thread stop failed" << std::endl;
        }
        _isrunning = false;
    }

    void Join() {
        // 线程等待,
        if (_isrunning) return;
        int n = pthread_join(_tid, nullptr);
        if (n != 0) {
            std::cout << "thread wait failed!!!" << strerror(errno) << std::endl;
        }
        // std::cout << _name << " join " << std::endl;

    }

    std::string Status() {
        if (_isrunning) return "running";
        else return "sleep";
    }
private:
    pthread_t _tid;
    func_t _func;
    bool _isrunning;
    std::string _name;
};

        Protocol.hpp                    我们设计的协议,其中主要包含两个结构体 Responce 和 Request,同时还在结构体中设计了将其序列化和反序列化的接口,使用的是 Json 库,需要在提前下载,源库中没有,使用如下命令:

ubuntu:sudo apt-get install libjsoncpp-dev
centos: sudo yum install jsoncpp-devel
#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <memory>
#include <jsoncpp/json/json.h>
#include "Log.hpp"

namespace protocol_ns {
    using namespace log_ns;

    // 设计的协议的报头和报文的格式
    // "len"\r\n"{json}"\r\n
    const std::string sep = "\r\n";

    // 给我们的报文加上报头
    std::string Encode(const std::string& jsonstring) {
        int len = jsonstring.size();
        if (len == 0) return "";
        return std::to_string(len) + sep + jsonstring + sep;
    }

    // 给我们的报文解密
    std::string Decode(std::string& jsonstring) {
        auto pos = jsonstring.find(sep);
        if (pos == std::string::npos) return "";
        // 现在开始截取json串
        std::string lenstr = jsonstring.substr(0, pos);
        int len = std::stoi(lenstr);
        if (jsonstring.size() < 2 * sep.size() + len + lenstr.size()) return "";

        size_t nextpos = jsonstring.find(sep, pos + sep.size());
        if (nextpos == std::string::npos) return "";
        std::string json = jsonstring.substr(pos + sep.size(), len);
        // 现在将jsonstring 给删除一部分
        jsonstring.erase(0, 2 * sep.size() + len + lenstr.size());
        return json;
    }

    class Request {
    public:
        Request() {}
        Request(int x, int y, char oper)
            : _x(x),
              _y(y),
              _oper(oper)
        {}

        // 序列化、反序列化
        std::string Serialize() {
            Json::Value root;
            root["x"] = _x;
            root["y"] = _y;
            root["oper"] = _oper;
            
            Json::FastWriter writer;
            std::string s = writer.write(root);

            return s;
        }

        void Deserialize(const std::string& info) {
            Json::Reader reader;
            Json::Value root;

            bool parsingSuccessful = reader.parse(info, root);
            if (!parsingSuccessful) {
                LOG(FATAL, "fail to parse json\n");
                return;
            }

            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
        }

        int Get_X() {
            return _x;
        }

        int Get_Y() {
            return _y;
        }

        char Get_oper() {
            return _oper;
        }
        
        void SetValue(int x, int y, char oper) {
            _x = x;
            _y = y;
            _oper = oper;
        }

        ~Request() {}
    private:
        int _x;
        int _y;
        char _oper;
    };

    class Responce {
    public:
        Responce()
            : _result(0),
              _code(0),
              _desc("success") 
        {}

        std::string Serialize() {
            Json::Value root;
            root["result"] = _result;
            root["code"] = _code;
            root["desc"] = _desc;

            Json::FastWriter writer;
            std::string s = writer.write(root);

            return s;
        }

        void Deserialize(const std::string& info) {
            Json::Reader reader;
            Json::Value root;

            bool parsingSuccessful = reader.parse(info, root);
            if (!parsingSuccessful) {
                LOG(FATAL, "fail to parse json\n");
                return;
            }

            _result = root["result"].asInt();
            _code = root["code"].asInt();
            _desc = root["desc"].asString();
        }

        int& Get_result() {
            return _result;
        }

        int& Get_code() {
            return _code;
        }

        std::string& Get_desc() {
            return _desc;
        }

        void PrintResult() {
            std::cout << "result: " << _result << ", code: " << _code << ", desc:" << _desc << std::endl;

        }

        ~Responce() {}

    private:
        int _result;
        int _code; // 0:success  1:div zero  2.illegal
        std::string _desc;
    };

    class Factory {
    public:
        static std::shared_ptr<Request> BuildRequest() {
            return std::make_shared<Request>();
        }

        static std::shared_ptr<Responce> BuildResponce() {
            return std::make_shared<Responce>();
        }
    };
}


        NetCal.hpp                      设计的网络计算器头文件,负责处理发送过来的数据,然后计算出来发送回去。

#pragma once
#include <iostream>
#include <memory>
#include <functional>
#include <string>
#include "Protocol.hpp"

using namespace protocol_ns;
using calculate_t = std::function<std::shared_ptr<Responce>(const std::shared_ptr<Request>& req)>;

// 需要一个Req和一个Resp
class NetCal {
public:
    NetCal() {}

    std::shared_ptr<Responce> Calculator(const std::shared_ptr<Request>& req) {
        std::shared_ptr<Responce> Resp = std::make_shared<Responce>();
        int x = req->Get_X(), y = req->Get_Y();
        char oper = req->Get_oper();

        int& result = Resp->Get_result();
        int& code = Resp->Get_code();
        std::string& desc = Resp->Get_desc();

        switch (oper) {
            case '+':
                result = x + y;
                code = 0;
                desc = "success";
                break;
            case '-':
                result = x - y;
                code = 0;
                desc = "success";
                break;
            case '*':
                result = x * y;
                code = 0;
                desc = "success";
                break;  
            case '/':
                if (y == 0) {
                    code = 1;
                    desc = "div zero";
                } else {
                    code = 0;
                    desc = "success";
                    result = x / y;
                }
                break;
            case '%':
                if (y == 0) {
                    code = 2;
                    desc = "mod zero";
                } else {
                    code = 0;
                    desc = "success";
                    result = x % y;
                }
                break;     
            default:
                code = 2;
                desc = "illegal operation";
                break;     
        }
        return Resp;
    }

    ~NetCal() {}
private:

};

        Log.hpp               日志文件,记录我们的信息(参考:日志Log程序(C++))

#pragma once
#include <iostream>
#include <string>
#include <cstdarg>
#include <cstring>
#include <fstream>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>


namespace log_ns {
    enum { DEBUG = 1, INFO, WARNING, ERROR, FATAL };

    // 定义日子真正需要记录的信息
    struct LogMessage {
        std::string _level;
        int _id;
        std::string _filename;
        int _filenumber;
        std::string _curtime;
        std::string _log_message;
    };

    #define SCREEN_TYPE 1
    #define FILE_TYPE   2

    const std::string defaultlogfile = "./log.txt";

    pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;

    class Log {
    private:
        std::string LevelToString(int level) {
            switch(level) {
                case DEBUG:
                    return "DEBUG";
                case INFO:
                    return "INFO";
                case WARNING:
                    return "WARNING";
                case ERROR:
                    return "ERROR";
                case FATAL:
                    return "FATAL";
                default:
                    return "UNKNOWN";
            }
        }

        std::string CurTime() {
            // 获取当前的时间戳
            time_t curtime = time(nullptr);
            // 将当前时间戳转换成结构体
            struct tm* now = localtime(&curtime);
            char buff[128];
            snprintf(buff, sizeof(buff), "%d-%02d-%02d %02d:%02d:%02d", 
                now->tm_year + 1900,
                now->tm_mon + 1,
                now->tm_mday,
                now->tm_hour,
                now->tm_min,
                now->tm_sec
            );
            return buff;
        }

        void Flush(const LogMessage& lg) {
            // 打印日志的时候可能存在线程安全,使用锁lock住
            pthread_mutex_lock(&log_lock);
            switch(_type) {
                case SCREEN_TYPE:
                    FlushToScreen(lg);
                    break;
                case FILE_TYPE:
                    FlushToFile(lg);
                    break;
            }
            pthread_mutex_unlock(&log_lock);
        }

        void FlushToFile(const LogMessage& lg) {
            std::ofstream out;
            out.open(_logfile, std::ios::app); // 文件的操作使用追加
            if (!out.is_open()) return;
            
            char buff[2024];
            snprintf(buff ,sizeof(buff), "[%s][%d][%s][%d][%s] %s",
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curtime.c_str(),
                lg._log_message.c_str()
            );            

            out.write(buff, strlen(buff));

            out.close();
        }

        void FlushToScreen(const LogMessage& lg) {
            printf("[%s][%d][%s][%d][%s] %s",
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curtime.c_str(),
                lg._log_message.c_str()
            );
        }

    public:
        Log(std::string logfile = defaultlogfile)
            : _type(SCREEN_TYPE),
              _logfile(logfile)
        {}

        void Enable(int type) {
            _type = type;
        }

        void LoadMessage(std::string filename, int filenumber, int level, const char* format, ...) {
            LogMessage lg;
            lg._level = LevelToString(level);
            lg._filename = filename;
            lg._filenumber = filenumber;
            // 获取当前时间
            lg._curtime = CurTime();
            // std::cout << lg._curtime << std::endl;
            lg._id = getpid();

            // 获取可变参数
            va_list ap;
            va_start(ap, format);
            char buff[2048];
            vsnprintf(buff, sizeof(buff), format, ap);
            va_end(ap);
            lg._log_message = buff;
            // std::cout << lg._log_message;
            Flush(lg);
        }

        void ClearOurFile() {
            std::ofstream out;
            out.open(_logfile);
            out.close();
        }

        ~Log() {}
    private:
        int _type;
        std::string _logfile;
    };

    Log lg;

// LOG 宏
#define LOG(level, format, ...)                                           \
    do                                                                    \
    {                                                                     \
        lg.LoadMessage(__FILE__, __LINE__, level, format, ##__VA_ARGS__); \
    } while (0)

#define EnableToScreen()        \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)

#define EnableToFile()        \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)

// 清理文件
#define ClearFile()        \
    do                     \
    {                      \
        lg.ClearOurFile(); \
    } while (0)
}

        IOService.hpp                  负责接收数据,然后将数据处理,然后转发回结果

#pragma once
#include <iostream>
#include <string>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Socket.hpp"
#include "Protocol.hpp"
#include "NetCal.hpp"

using namespace log_ns;
using namespace socket_ns;
using namespace protocol_ns;

// 提供io服务,需要将对应的信息反序列化,然后执行,接着加上报头,然后继续序列化
// 然后将数据发送出去
class IOService {
public:
    IOService() {}

    void IOForword(calculate_t cal, ScokSPtr socket, InetAdrr& who) {
        // 将读和写包装起来
        while (true) {
            // 开始读和写
            std::string packages_stream;
            // LOG(INFO, "the sockfd: %d\n", sockfd);
            int n = socket->Recv(&packages_stream);
            if (n <= 0) {
                LOG(WARNING, "client %s quit or read error\n", who.AddrString().c_str());
                break;
            } else {
                // 在这里处理信息
                // 1. 先将数据解码
                std::string jsonstr = Decode(packages_stream);
                if (jsonstr.empty()) continue;

                std::cout << "----------------------------------" << std::endl;
                // 将解码的数据打印出来
                std::cout << "jsonstr\n" << jsonstr << std::endl;

                // 2. 然后将数据反序列化
                std::shared_ptr<Request> Req = Factory::BuildRequest();
                Req->Deserialize(jsonstr);

                // 3. 开始运行处理数据
                std::shared_ptr<Responce> Resp = cal(Req);

                // 4. 运行处理完数据之后将数据序列化
                std::string anti_jsonstr = Resp->Serialize();
                // 将序列化后的数据打印出来
                std::cout << "anti_json\n" << anti_jsonstr << std::endl;

                // 5. 给序列化的数据加上报头
                std::string package = Encode(anti_jsonstr);

                // 6. 将数据发送出去
                socket->Send(package);
            }
        }
    }

    ~IOService() {}
};

        InetAddr.hpp         记录客户端或者服务端的 ip 和 port

#pragma once
#include <iostream>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

class InetAdrr {
    void ToHost(const struct sockaddr_in& addr) {
        // inet_ntoa 函数不是线程安全的函数,推荐使用 inet_ntop 函数
        // _ip = inet_ntoa(addr.sin_addr);
        char ip_buff[32];

        // 该函数是网络序列转主机序列 :network to process
        inet_ntop(AF_INET, &addr.sin_addr, ip_buff, sizeof(ip_buff));
        // 若想要将主机序列转换成网络序列使用函数 :
        // inet_pton(AF_INET, _ip.c_str(), (void*)&addr.sin_addr.s_addr); 
        _ip = ip_buff;
        _port = ntohs(addr.sin_port);
    }
public:
    InetAdrr() {}

    InetAdrr(const struct sockaddr_in& addr) : _addr(addr)
    {
        ToHost(_addr);
    }

    InetAdrr& operator=(const struct sockaddr_in& addr) {
        _addr = addr;
        return *this;
    }

    std::string Ip() const {
        return _ip;
    }

    bool operator==(const InetAdrr& addr) {
        return (_port == addr._port && _ip == addr._ip);
    }

    struct sockaddr_in Addr() const {
        return _addr;
    }

    std::string AddrString() const {
        return _ip + ":" + std::to_string(_port);
    }

    uint16_t Port() const {
        return _port;
    }

    ~InetAdrr() {}
private:
    uint16_t _port;
    std::string _ip;
    struct sockaddr_in _addr;
};

        makefile

.PHONY:all
all:server client

server:TcpServer.cc
	g++ -o $@ $^ -std=c++14 -lpthread -ljsoncpp
client:TcpClient.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp

.PHONY:clean
clean:
	rm -f server client

        测试结果:

        对于以上代码主要实现的是 IOS 七层协议中的表示层、会话层、应用层,其中应用层实现的代码为:NetCal.hpp、表示层实现的代码为:IOService.hpp、会话层实现的代码为 TcpServer.hpp。

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

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

相关文章

⼆⼿⻋交易系统前景分析

二手车交易系统开发小程序在当前市场中具有显著的优势和潜力。以下是对二手车交易系统小程序功能的综合分析&#xff1a; 车辆信息展示&#xff1a;小程序应提供详细的车辆信息展示&#xff0c;包括车辆的图片、品牌、型号、年份、里程数、价格等关键信息&#xff0c;方便用户…

Python爬虫—常用的网络爬虫工具推荐

以下列举几个常用的网络爬虫工具 1. 八爪鱼&#xff08;Bazhuayu&#xff09; 简介&#xff1a; 八爪鱼是一款面向非技术用户的桌面端爬虫软件&#xff0c;以其可视化操作和强大的模板库而受到青睐。它支持从各种网站上抓取数据&#xff0c;包括文本、图片、文档等&#xff…

特殊类设计和类型转换

前言 这一篇博客我们讲特殊类设计和类型转换 1. 特殊类设计 1.1 请设计一个类&#xff0c;不能被拷贝 这个比较简单 第一种方法就是将赋值和拷贝构造只定义不声明然后设置为私有就可以了 第二种方法就是直接令它为delete 1.2 请设计一个类&#xff0c;只能在堆上创建对象 …

自学成才:通过自学成为软件开发者——之入行成为软件开发者

一些优秀的程序员&#xff0c;可能以前从事的是其他职业&#xff0c;他们大都发现工作中的很多固定化的流程内容&#xff0c;如果可以实现自动化&#xff0c;不仅效率能够得到提高和保证&#xff0c;提高自己的生成力&#xff0c;同时自己也会从中释放出来&#xff0c;有更多的…

Go使用MongoDB应用指南

Go使用MongoDB应用指南 MongoDB 是一种高性能、开源、文档型的 NoSQL 数据库&#xff0c;广泛应用于 Web 应用、大数据以及云计算领域。Go 语言则以其快速、开发效率高、代码可维护性强著称。本指南将详细介绍如何在 Go 语言中使用 MongoDB 进行数据库操作&#xff0c;包括连接…

鸿蒙HarmonyOS开发知识:命令行工具Command Line Tools

该命令行工具集合了HarmonyOS应用开发所用到的系列工具&#xff0c;包括代码检查codelinter、三方库的包管理ohpm、命令行解析hstack、编译构建hvigorw。 命令行工具获取 请前往下载中心获取并下载命令行工具Command Line Tools。 配置环境变量 Windows 将解压后command-l…

英语四六级有多重要你不知道

卷出天际 IT业内卷严重大家都知道 因此也就打击了很多想入行的新人 到底什么是核心竞争力 放在十年前 稍微会Spring, CRUD 就能达到入门的台阶 那也是培训机构最繁荣的一段时期 而今顶峰已经过去 IT业从含金量上 已经大不如前 在野蛮发展期 如果不太挑的话 大专也是…

云轴科技ZStack AIOS平台智塔亮相FDS金融领袖峰会

人工智能&#xff08;AI&#xff09;正以前所未有的速度渗透到金融系统&#xff0c;推动着金融服务的创新和变革。这种深度融合不仅可以提高金融服务的效率和准确性&#xff0c;未来还可催生全新的金融产品和服务模式。尤其是生成式人工智能&#xff08;GenAI&#xff09;的出现…

算法的学习笔记—复杂链表的复制(牛客JZ35)

&#x1f600;前言 在许多实际应用中&#xff0c;我们会遇到复杂链表的复制问题。复杂链表不同于一般的单链表&#xff0c;不仅每个节点有指向下一个节点的指针&#xff0c;还有一个特殊的指针 random&#xff0c;可以指向链表中的任意节点或 null。如何高效地复制这样一个复杂…

CACTER直播预告:聚焦EDLP邮件数据防泄露实战重点

在信息高速流通的今天&#xff0c;邮件作为商务沟通的桥梁&#xff0c;不仅承载着日常沟通&#xff0c;更是企业机密和知识产权的重要载体。然而&#xff0c;邮件系统的开放性也使其成为网络攻击的主要目标。数据泄露不仅会导致商业损失&#xff0c;还可能对企业声誉造成不可逆…

【请安全下载】黑神话:悟空 单机游戏 它是如何保证安全的 怎样防破解的?安全措施:D加密,反外挂,代码加密,资源保护

单机 《黑神话&#xff1a;悟空》是一款单机游戏&#xff0c;由游戏科学开发&#xff0c;并于2024年8月20日全球同步上线。游戏以其独特的暗黑国风、深度的故事背景以及精致的游戏画面&#xff0c;重塑了西游题材&#xff0c;为玩家呈现了一个前所未有的悟空传奇。 黑神话&…

[Linux]在Ubuntu中安装samba并且正确配置(详细)

一、我们为什么需要samba服务 samba是一种实现windows和linux包括macos文件共享的套件。它能让我们像访问自己的磁盘一样去访问别的系统的文件。可以看得出来这种一种快速并且高效的文件传输协议。看到这里&#xff0c;大家可能会有些疑问。向linux传输文件&#xff0c;我们可以…

常用网络测试工具以及解决tcp协议带来得问题

一、解决粘包问题 1.1、tcp的特点 面向字节流特点&#xff0c;会造成可能数据与数据发送到一块&#xff0c;成为粘包&#xff0c;数据之间不区分 1.2、拆包 因为缓冲区的大小&#xff0c;一次性发送的数据会进行拆分&#xff08;大小不符合的时候&#xff09; 就和水一样一…

vue3使用i18n实现国际化

安装vue-i18n npm install vue-i18n创建一个ts文件用于存储各种翻译 globalLang.ts的内容如下&#xff1a; export default {"cn": {},"en": {},"de": {},"es": {},"fr": {},"id": {},"it": {},&quo…

HDMI画面发白

这个问题困扰我很久了&#xff0c;今天在抖音上看到了解决方案! https://v.douyin.com/Ceie2g2s/ 量化范围&#xff1a;有限范围改成全范围。

Tomcat安装部署

简介 Tomcat 是由 Apache 开发的一个 Servlet 容器&#xff0c;实现了对 Servlet 和 JSP 的支持&#xff0c;并提供了作为Web服务器的一些特有功能&#xff0c;如Tomcat管理和控制平台、安全域管理和Tomcat阀等。 简单来说&#xff0c;Tomcat是一个WEB应用程序的托管平台&…

关于elementui table组件 —— 竖向表格

前端模拟数据方式&#xff1a; html代码&#x1f447;&#xff1a; <template><el-table :data"tableData" style"width: 60%;margin-top:20px" stripe :show-header"false" border :row-style"rowStyle"><el-table…

培训第三十五天(容器的基础命令使用)

1、创建一个容器并同时执行echo命令 # 快速启动一个容器执行特定的一次性命令并查看输出结果&#xff0c;输出结果后容器直接退出[rootdocker ~]# docker run -it --namea0 centos:latest echo "abc"abc[rootdocker ~]# docker psCONTAINER ID IMAGE COMMAND …

FreeRTOS 快速入门(六)之互斥量

目录 一、互斥量1、基本概念2、运作机制3、死锁现象4、递归互斥量 二、优先级反转和优先级继承问题1、优先级反转问题2、优先级继承问题 三、互斥量函数1、互斥量1、创建 2、获取互斥量3、释放互斥量4、删除互斥量 一、互斥量 1、基本概念 互斥量又称互斥信号量&#xff08;本…

Vue.js学习笔记(七)使用sortablejs或el-table-draggable拖拽ElementUI的el-table表格组件

文章目录 前言一、el-table-draggable是什么&#xff1f;二、使用步骤1.安装使用2.sortablejs 总结 前言 记录 el-table-draggable 插件使用方法。 一、el-table-draggable是什么&#xff1f; el-table-draggable的存在就是为了让vue-draggable支持element-ui中的el-table组件…