国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【網(wǎng)絡】協(xié)議定制+序列化/反序列化

這篇具有很好參考價值的文章主要介紹了【網(wǎng)絡】協(xié)議定制+序列化/反序列化。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

為什么要序列化?

如果光看定義很難理解序列化的意義,那么我們可以從另一個角度來推導出什么是序列化, 那么究竟序列化的目的是什么?

其實序列化最終的目的是為了對象可以跨平臺存儲,和進行網(wǎng)絡傳輸。而我們進行跨平臺存儲和網(wǎng)絡傳輸?shù)姆绞骄褪荌O,而我們的IO支持的數(shù)據(jù)格式就是字節(jié)數(shù)組。

因為我們單方面的只把對象轉成字節(jié)數(shù)組還不行,因為沒有規(guī)則的字節(jié)數(shù)組我們是沒辦法把對象的本來面目還原回來的,所以我們必須在把對象轉成字節(jié)數(shù)組的時候就制定一種規(guī)則(序列化),那么我們從IO流里面讀出數(shù)據(jù)的時候再以這種規(guī)則把對象還原回來(反序列化)。

如果我們要把一棟房子從一個地方運輸?shù)搅硪粋€地方去,序列化就是我把房子拆成一個個的磚塊放到車子里,然后留下一張房子原來結構的圖紙,反序列化就是我們把房子運輸?shù)搅四康牡匾院?,根?jù)圖紙把一塊塊磚頭還原成房子原來面目的過程。

是需要進行“跨平臺存儲”和”網(wǎng)絡傳輸”的數(shù)據(jù),都需要進行序列化。本質上存儲和網(wǎng)絡傳輸 都需要經(jīng)過 把一個對象狀態(tài)保存成一種跨平臺識別的字節(jié)格式,然后其他的平臺才可以通過字節(jié)信息解析還原對象信息。

序列化只是一種拆裝組裝對象的規(guī)則,那么這種規(guī)則肯定也可能有多種多樣,比如現(xiàn)在常見的序列化方式有:

JDK(不支持跨語言)、JSON、XML、Hessian、Kryo(不支持跨語言)、Thrift、Protostuff、FST(不支持跨語言)

在進行網(wǎng)絡傳輸之前,我們要將數(shù)據(jù)轉換成字符串的形式,以字節(jié)流的方式發(fā)送信息。雙方在通信時如果發(fā)送端一直發(fā)送數(shù)據(jù),但是接收端并沒有及時的接收數(shù)據(jù),那這一對的數(shù)據(jù)就會被堆積下來。等接收端能開始接收數(shù)據(jù)時該如何保證報文和報文的邊界的呢?保證讀到的時一個報文而不是半個或者1.5個報文的呢?

常見的解決方法:

1. 定? 長:? ? ?即可以規(guī)定一個報文的大小為1024字節(jié),讀取時嚴格的按照定下的長度讀取

2.特殊符號:在報文的結束端加上特殊符號“ + ” " -?"等特殊符號以此區(qū)分不同的報文

3.自描述方式:規(guī)定報文的頭四個字節(jié)存儲的是報文的有效長度,未來在讀取報文的時候首先讀取這頭四個字節(jié),根據(jù)這四個字節(jié)存儲的長度然后截取這個報文。

協(xié)議的實現(xiàn)

接下來將采用序列化和反序列化+自描述的方式實現(xiàn)一款簡易版網(wǎng)絡計算器。網(wǎng)絡計算器在發(fā)起請求的過程中的格式為:a運算符b ,我們首先創(chuàng)建一個對象將兩個運算的數(shù)和一個運算符存儲起來,因為tcp是面向字節(jié)流的所以在網(wǎng)絡通信前,首先需要將這個對象轉換為字符串的形式,以字節(jié)流的方式發(fā)送數(shù)據(jù)到服務端。上文所講為了方便服務端讀取報文,所以這里采用的是自描述的方式發(fā)送數(shù)據(jù),即在報文的有效載荷前加上一個存儲報文有效長度的報頭,報頭再與報文之間用特殊的字符隔開,未來再服務端進行讀取時就找這個特殊的分隔符,根據(jù)分隔符確定報頭的位置,然后根據(jù)報頭中存儲的有效的長度就可以完整的讀取到一條客戶端的請求。

【網(wǎng)絡】協(xié)議定制+序列化/反序列化

?客戶端發(fā)起請求就是serialize+enLength(序列化+自描述),服務端在接收到這個報文后首先去掉報頭得到正文,將正文反序列化得到一個包含正文元素的對象(請求對象),此時再創(chuàng)建一個要返回結果的對象(響應對象),將這請求對象和相應對象傳入到計算函數(shù)中,計算函數(shù)將計算的結果和計算的退出碼保存到相應對象中,再將相應對象序列化+添加報頭組成一個大的“字符串”通過網(wǎng)絡發(fā)送到客戶端,客戶端接收后先去報頭再反序列化構建出一個對象,這個對象中就保存著計算的結果和計算的退出碼。

【網(wǎng)絡】協(xié)議定制+序列化/反序列化

客戶端從網(wǎng)絡接收到數(shù)據(jù)后先去報頭得到正文,反序列化得到對象,對象中所存儲的值就有這次運算的退出碼和運算結果。

【網(wǎng)絡】協(xié)議定制+序列化/反序列化

?協(xié)議的定制中包括:1分隔符的種類2.添加報頭3.去報頭3.數(shù)據(jù)從緩沖區(qū)的接收4.請求對象的序列化和反序列化 5.相應對象的序列化和反序列化6.運行結果的退出碼

代碼如下:?

#pragma once

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

#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()

enum
{
    OK = 0,
    DIV_ZERO,
    MOD_ZERO,
    OP_ERROR
};

// "x op y" -> "content_len"\r\n"x op y"\r\n
// "exitcode result" -> "content_len"\r\n"exitcode result"\r\n
std::string enLength(const std::string &text)
{
    std::string send_string = std::to_string(text.size());
    send_string += LINE_SEP;
    send_string += text;
    send_string += LINE_SEP;

    return send_string;
}

// "content_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == std::string::npos)
        return false;
    std::string text_len_string = package.substr(0, pos);
    int text_len = std::stoi(text_len_string);
    *text = package.substr(pos + LINE_SEP_LEN, text_len);
    return true;
}

// 沒有人規(guī)定我們網(wǎng)絡通信的時候,只能有一種協(xié)議??!
// 我們怎么讓系統(tǒng)知道我們用的是哪一種協(xié)議呢??
// "content_len"\r\n"協(xié)議編號"\r\n"x op y"\r\n

class Request
{
public:
    Request() : x(0), y(0), op(0)
    {
    }
    Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
    {
    }
    // 1. 自己寫
    // 2. 用現(xiàn)成的
    bool serialize(std::string *out)
    {
#ifdef MYSELF
        *out = "";
        // 結構化 -> "x op y";
        std::string x_string = std::to_string(x);
        std::string y_string = std::to_string(y);

        *out = x_string;
        *out += SEP;
        *out += op;
        *out += SEP;
        *out += y_string;
#else
        Json::Value root;
        root["first"] = x;
        root["second"] = y;
        root["oper"] = op;

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

    // "x op yyyy";
    bool deserialize(const std::string &in)
    {
#ifdef MYSELF
        // "x op y" -> 結構化
        auto left = in.find(SEP);
        auto right = in.rfind(SEP);
        if (left == std::string::npos || right == std::string::npos)
            return false;
        if (left == right)
            return false;
        if (right - (left + SEP_LEN) != 1)
            return false;

        std::string x_string = in.substr(0, left); // [0, 2) [start, end) , start, end - start
        std::string y_string = in.substr(right + SEP_LEN);

        if (x_string.empty())
            return false;
        if (y_string.empty())
            return false;
        x = std::stoi(x_string);
        y = std::stoi(y_string);
        op = in[left + SEP_LEN];
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);

        x = root["first"].asInt();
        y = root["second"].asInt();
        op = root["oper"].asInt();
#endif
        return true;
    }

public:
    // "x op y"
    int x;
    int y;
    char op;
};

class Response
{
public:
    Response() : exitcode(0), result(0)
    {
    }
    Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
    {
    }
    bool serialize(std::string *out)
    {
#ifdef MYSELF
        *out = "";
        std::string ec_string = std::to_string(exitcode);
        std::string res_string = std::to_string(result);

        *out = ec_string;
        *out += SEP;
        *out += res_string;
#else
        Json::Value root;
        root["exitcode"] = exitcode;
        root["result"] = result;

        Json::FastWriter writer;
        *out = writer.write(root);
#endif
        return true;
    }
    bool deserialize(const std::string &in)
    {
#ifdef MYSELF
        // "exitcode result"
        auto mid = in.find(SEP);
        if (mid == std::string::npos)
            return false;
        std::string ec_string = in.substr(0, mid);
        std::string res_string = in.substr(mid + SEP_LEN);
        if (ec_string.empty() || res_string.empty())
            return false;

        exitcode = std::stoi(ec_string);
        result = std::stoi(res_string);
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);
        
        exitcode = root["exitcode"].asInt();
        result = root["result"].asInt();
#endif
        return true;
    }

public:
    int exitcode; // 0:計算成功,!0表示計算失敗,具體是多少,定好標準
    int result;   // 計算結果
};

// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{
    char buffer[1024];
    while (true)
    {
        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer += buffer;
            // 分析處理
            auto pos = inbuffer.find(LINE_SEP);
            if (pos == std::string::npos)
                continue;
            std::string text_len_string = inbuffer.substr(0, pos);
            int text_len = std::stoi(text_len_string);
            int total_len = text_len_string.size() + 2 * LINE_SEP_LEN + text_len;
            // text_len_string + "\r\n" + text + "\r\n" <= inbuffer.size();
            std::cout << "處理前#inbuffer: \n" << inbuffer << std::endl;

            if (inbuffer.size() < total_len)
            {
                std::cout << "你輸入的消息,沒有嚴格遵守我們的協(xié)議,正在等待后續(xù)的內容, continue" << std::endl;
                continue;
            }

            // 至少有一個完整的報文
            *text = inbuffer.substr(0, total_len);
            inbuffer.erase(0, total_len);

            std::cout << "處理后#inbuffer:\n " << inbuffer << std::endl;

            break;
        }
        else
            return false;
    }
    return true;
}

服務端將字符串通過去報頭和反序列化的操作就將客戶端的請求構建出來一個請求對象,服務端再創(chuàng)建一個響應對象,這兩個對象在計算函數(shù)的內部將計算的結果和計算的退出碼一起存儲到響應對象中,至此完成了計算任務,計算函數(shù)如下:

bool cal(const Request &req, Response &resp)
{
  
    resp.exitcode = OK;
    resp.result = OK;

    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 = DIV_ZERO;
        else
            resp.result = req.x / req.y;
    }
    break;
    case '%':
    {
        if (req.y == 0)
            resp.exitcode = MOD_ZERO;
        else
            resp.result = req.x % req.y;
    }
    break;
    default:
        resp.exitcode = OP_ERROR;
        break;
    }

    return true;
}

客戶端啟動后,就會彈出讓用戶輸入計算的式子,這個式子是從鍵盤接收的,首先將接收的式子存到一個零時緩沖區(qū)內,方便我們對輸入的式子切割并完成對請求對象的構建,調用切割的函數(shù)如下:

 Request ParseLine(const std::string &line)
    {
        
        int status = 0; 
        int i = 0;
        int cnt = line.size();
        std::string left, right;
        char op;
        while (i < cnt)
        {
            switch (status)
            {
            case 0:
            {
                if(!isdigit(line[i]))
                {
                    op = line[i];
                    status = 1;
                }
                else left.push_back(line[i++]);
            }
            break;
            case 1:
                i++;
                status = 2;
                break;
            case 2:
                right.push_back(line[i++]);
                break;
            }
        }
        std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
        return Request(std::stoi(left), std::stoi(right), op);
    }

這是一個名為ParseLine的C++函數(shù),它接受一個名為line的std::string參數(shù),并返回一個Request對象。ParseLine函數(shù)首先初始化一些變量,包括status、i、cnt、left、right和op。status跟蹤正在讀取字符串的哪個部分,i跟蹤正在讀取的字符的索引,cnt是輸入字符串的大小,left和right分別保存左操作數(shù)和右操作數(shù),op保存運算符。接下來,ParseLine使用while循環(huán)遍歷輸入字符串中的每個字符。在while循環(huán)內,有一個switch語句,根據(jù)當前狀態(tài)處理不同情況。在第0種情況下,函數(shù)使用isdigit()檢查索引i處的當前字符是否為數(shù)字。如果它不是數(shù)字,則將op設置為當前字符,將狀態(tài)更新為1,并繼續(xù)到下一個字符。否則,它將當前字符附加到left字符串中并增加i。在第1種情況下,它增加i并將狀態(tài)更改為2。在第2種情況下,它將當前字符附加到right字符串中并增加i。while循環(huán)結束后,函數(shù)使用std::cout打印left、right和op的值,并使用解析后的left、right和op值構造一個Request對象并返回它。這樣就完成了請求對象的構建。

對于序列化和反序列化這個操作的過程可以自己完成,也可以使用Json協(xié)議,使用別人定制好的成熟的方法,關于Json第三方庫的安裝,可以使用命令下載Json:

sudo yum install -y jsoncpp-deve

至于要使用自己的方法還是Json協(xié)議,這里可以使用條件編譯的方式,將兩者方法都寫進代碼中

代碼在上方的協(xié)議文件中有體現(xiàn)。

數(shù)據(jù)前面的字符串就是為反序列化建立的索引,先說序列化:Value是Json中的一個萬能對象,用它可以序列化不同格式的數(shù)據(jù),創(chuàng)建Value對象root,root[“x”, _x]就是在root中插入了一個鍵值對,把所有的數(shù)插入到root后,創(chuàng)建FastWrite對象,以root為參數(shù)調用其write方法,用string類型對象str接收write的返回值,str中保存的就是{“op”:43,“x”:1,“y”:1}這樣的字符串,是將數(shù)據(jù)序列化后的結果。

至于Json的反序列化:我們創(chuàng)建Reader類型對象rd與Value類型對象root,調用rd的parse方法,將root和序列化后的字符串str作為parse的參數(shù),parse方法會將反序列化后的結果寫入root,此時我們就能根據(jù)當時創(chuàng)建root對象時,為數(shù)據(jù)建立的索引來還原數(shù)據(jù),比如root[“x”].asInt(),將root中以"x"為key的value值以int的格式返回,這樣我們就能將數(shù)據(jù)還原到我們的結構體中

關于條件編譯,我們可以在makefile文件中,以命令行的方式,在編譯源文件時創(chuàng)建宏,具體是-D 宏的名字

實驗結果演示

?全部代碼:

calClient.cc:

#include "calClient.hpp"
#include <memory>

using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " serverip serverport\n\n";
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<CalClient> tcli(new CalClient(serverip, serverport));
    tcli->initClient();
    tcli->start();
    return 0;
}

calClient.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "protocol.hpp"

#define NUM 1024

class CalClient
{
public:
    CalClient(const std::string &serverip, const uint16_t &serverport)
        : _sock(-1), _serverip(serverip), _serverport(serverport)
    {
    }
    void initClient()
    {
        
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            std::cerr << "socket create error" << std::endl;
            exit(2);
        }
        
    }
    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
        {
            std::cerr << "socket connect error" << std::endl;
        }
        else
        {
            std::string line;
            std::string inbuffer;
            while (true)
            {
                std::cout << "mycal>>> ";
                std::getline(std::cin, line);  
                Request req = ParseLine(line); 
                std::string content;
                req.serialize(&content);
                std::string send_string = enLength(content);
                send(_sock, send_string.c_str(), send_string.size(), 0); 

                std::string package, text;
              
                if (!recvPackage(_sock, inbuffer, &package))
                    continue;
                if (!deLength(package, &text))
                    continue;
              
                Response resp;
                resp.deserialize(text);
                std::cout << "exitCode: " << resp.exitcode << std::endl;
                std::cout << "result: " << resp.result << std::endl;
            }
        }
    }
    Request ParseLine(const std::string &line)
    {
        
        int status = 0; 
        int i = 0;
        int cnt = line.size();
        std::string left, right;
        char op;
        while (i < cnt)
        {
            switch (status)
            {
            case 0:
            {
                if(!isdigit(line[i]))
                {
                    op = line[i];
                    status = 1;
                }
                else left.push_back(line[i++]);
            }
            break;
            case 1:
                i++;
                status = 2;
                break;
            case 2:
                right.push_back(line[i++]);
                break;
            }
        }
        std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
        return Request(std::stoi(left), std::stoi(right), op);
    }
    ~CalClient()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    int _sock;
    std::string _serverip;
    uint16_t _serverport;
};

calServer.cc:

#include "calServer.hpp"
#include <memory>

using namespace server;
using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

bool cal(const Request &req, Response &resp)
{
  
    resp.exitcode = OK;
    resp.result = OK;

    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 = DIV_ZERO;
        else
            resp.result = req.x / req.y;
    }
    break;
    case '%':
    {
        if (req.y == 0)
            resp.exitcode = MOD_ZERO;
        else
            resp.result = req.x % req.y;
    }
    break;
    default:
        resp.exitcode = OP_ERROR;
        break;
    }

    return true;
}


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

    unique_ptr<CalServer> tsvr(new CalServer(port));
    tsvr->initServer();
    tsvr->start(cal);
    return 0;
}

calServer.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include "log.hpp"
#include "protocol.hpp"

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

   
    typedef std::function<bool(const Request &req, Response &resp)> func_t;


    void handlerEntery(int sock, func_t func)
    {
        std::string inbuffer;
        while (true)
        {
           
            std::string req_text, req_str;
          
            if (!recvPackage(sock, inbuffer, &req_text))
                return;
            std::cout << "帶報頭的請求:\n" << req_text << std::endl;
            if (!deLength(req_text, &req_str))
                return;
            std::cout << "去掉報頭的正文:\n" << req_str << std::endl;
            

          
            Request req;
            if (!req.deserialize(req_str))
                return;
            
            Response resp;
            func(req, resp); 

           
            std::string resp_str;
            resp.serialize(&resp_str);

            std::cout << "計算完成, 序列化響應: " <<  resp_str << std::endl;

          
            std::string send_string = enLength(resp_str);
            std::cout << "構建完成完整的響應\n" <<  send_string << std::endl;
        
            send(sock, send_string.c_str(), send_string.size(), 0); 
        }
    }

    class CalServer
    {
    public:
        CalServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
        {
        }
        void initServer()
        {
        
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success: %d", _listensock);

           
            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;
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

        
            if (listen(_listensock, gbacklog) < 0) 
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }
        void start(func_t func)
        {
            for (;;)
            {
               
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?

                // version 2 多進程版(2)
                pid_t id = fork();
                if (id == 0) // child
                {
                    close(_listensock);
                    // if(fork()>0) exit(0);
                    //  serviceIO(sock);
                    handlerEntery(sock, func);
                    close(sock);
                    exit(0);
                }
                close(sock);

                // father
                pid_t ret = waitpid(id, nullptr, 0);
                if (ret > 0)
                {
                    logMessage(NORMAL, "wait child success"); // ?
                }
            }
        }
        ~CalServer() {}

    private:
        int _listensock;
        uint16_t _port;
    };

} 

日志:

#pragma once

#include <iostream>
#include <string>
#include <cstdarg>
#include <ctime>
#include <unistd.h>

#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char * to_levelstr(int level)
{
    switch(level)
    {
        case DEBUG : return "DEBUG";
        case NORMAL: return "NORMAL";
        case WARNING: return "WARNING";
        case ERROR: return "ERROR";
        case FATAL: return "FATAL";
        default : return nullptr;
    }
}

void logMessage(int level, const char *format, ...)
{
#define NUM 1024
    char logprefix[NUM];
    snprintf(logprefix, sizeof(logprefix), "[%s][%ld][pid: %d]",
        to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    vsnprintf(logcontent, sizeof(logcontent), format, arg);

    std::cout << logprefix << logcontent << std::endl;
}

protocol.hpp:

#pragma once

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

#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()

enum
{
    OK = 0,
    DIV_ZERO,
    MOD_ZERO,
    OP_ERROR
};

// "x op y" -> "content_len"\r\n"x op y"\r\n
// "exitcode result" -> "content_len"\r\n"exitcode result"\r\n
std::string enLength(const std::string &text)
{
    std::string send_string = std::to_string(text.size());
    send_string += LINE_SEP;
    send_string += text;
    send_string += LINE_SEP;

    return send_string;
}

// "content_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == std::string::npos)
        return false;
    std::string text_len_string = package.substr(0, pos);
    int text_len = std::stoi(text_len_string);
    *text = package.substr(pos + LINE_SEP_LEN, text_len);
    return true;
}

// 沒有人規(guī)定我們網(wǎng)絡通信的時候,只能有一種協(xié)議!!
// 我們怎么讓系統(tǒng)知道我們用的是哪一種協(xié)議呢??
// "content_len"\r\n"協(xié)議編號"\r\n"x op y"\r\n

class Request
{
public:
    Request() : x(0), y(0), op(0)
    {
    }
    Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
    {
    }
    // 1. 自己寫
    // 2. 用現(xiàn)成的
    bool serialize(std::string *out)
    {
#ifdef MYSELF
        *out = "";
        // 結構化 -> "x op y";
        std::string x_string = std::to_string(x);
        std::string y_string = std::to_string(y);

        *out = x_string;
        *out += SEP;
        *out += op;
        *out += SEP;
        *out += y_string;
#else
        Json::Value root;
        root["first"] = x;
        root["second"] = y;
        root["oper"] = op;

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

    // "x op yyyy";
    bool deserialize(const std::string &in)
    {
#ifdef MYSELF
        // "x op y" -> 結構化
        auto left = in.find(SEP);
        auto right = in.rfind(SEP);
        if (left == std::string::npos || right == std::string::npos)
            return false;
        if (left == right)
            return false;
        if (right - (left + SEP_LEN) != 1)
            return false;

        std::string x_string = in.substr(0, left); // [0, 2) [start, end) , start, end - start
        std::string y_string = in.substr(right + SEP_LEN);

        if (x_string.empty())
            return false;
        if (y_string.empty())
            return false;
        x = std::stoi(x_string);
        y = std::stoi(y_string);
        op = in[left + SEP_LEN];
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);

        x = root["first"].asInt();
        y = root["second"].asInt();
        op = root["oper"].asInt();
#endif
        return true;
    }

public:
    // "x op y"
    int x;
    int y;
    char op;
};

class Response
{
public:
    Response() : exitcode(0), result(0)
    {
    }
    Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
    {
    }
    bool serialize(std::string *out)
    {
#ifdef MYSELF
        *out = "";
        std::string ec_string = std::to_string(exitcode);
        std::string res_string = std::to_string(result);

        *out = ec_string;
        *out += SEP;
        *out += res_string;
#else
        Json::Value root;
        root["exitcode"] = exitcode;
        root["result"] = result;

        Json::FastWriter writer;
        *out = writer.write(root);
#endif
        return true;
    }
    bool deserialize(const std::string &in)
    {
#ifdef MYSELF
        // "exitcode result"
        auto mid = in.find(SEP);
        if (mid == std::string::npos)
            return false;
        std::string ec_string = in.substr(0, mid);
        std::string res_string = in.substr(mid + SEP_LEN);
        if (ec_string.empty() || res_string.empty())
            return false;

        exitcode = std::stoi(ec_string);
        result = std::stoi(res_string);
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in, root);
        
        exitcode = root["exitcode"].asInt();
        result = root["result"].asInt();
#endif
        return true;
    }

public:
    int exitcode; // 0:計算成功,!0表示計算失敗,具體是多少,定好標準
    int result;   // 計算結果
};

// "content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op
bool recvPackage(int sock, std::string &inbuffer, std::string *text)
{
    char buffer[1024];
    while (true)
    {
        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer += buffer;
            // 分析處理
            auto pos = inbuffer.find(LINE_SEP);
            if (pos == std::string::npos)
                continue;
            std::string text_len_string = inbuffer.substr(0, pos);
            int text_len = std::stoi(text_len_string);
            int total_len = text_len_string.size() + 2 * LINE_SEP_LEN + text_len;
            // text_len_string + "\r\n" + text + "\r\n" <= inbuffer.size();
            std::cout << "處理前#inbuffer: \n" << inbuffer << std::endl;

            if (inbuffer.size() < total_len)
            {
                std::cout << "你輸入的消息,沒有嚴格遵守我們的協(xié)議,正在等待后續(xù)的內容, continue" << std::endl;
                continue;
            }

            // 至少有一個完整的報文
            *text = inbuffer.substr(0, total_len);
            inbuffer.erase(0, total_len);

            std::cout << "處理后#inbuffer:\n " << inbuffer << std::endl;

            break;
        }
        else
            return false;
    }
    return true;
}

makefile:文章來源地址http://www.zghlxwxcb.cn/news/detail-476253.html

cc=g++
LD=-DMYSELF
.PHONY:all
all:calserver calclient

calclient:calClient.cc
	$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}

calserver:calServer.cc
	$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}

.PHONY:clean
clean:
	rm -f calclient calserver

到了這里,關于【網(wǎng)絡】協(xié)議定制+序列化/反序列化的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 談談linux網(wǎng)絡編程中的應用層協(xié)議定制、Json序列化與反序列化那些事

    談談linux網(wǎng)絡編程中的應用層協(xié)議定制、Json序列化與反序列化那些事

    由于socket api的接口,在讀寫數(shù)據(jù)的時候是以字符串的方式發(fā)送接收的,如果需要傳輸 結構化的數(shù)據(jù) ,就需要制定一個協(xié)議 結構化數(shù)據(jù)在發(fā)送到網(wǎng)絡中之前需要完成序列化 接收方收到的是序列字節(jié)流,需要完成反序列化才能使用(如ChatInfo._name) 當我們進行網(wǎng)絡通信的的時

    2024年02月06日
    瀏覽(26)
  • 協(xié)議定制 + Json序列化反序列化

    協(xié)議定制 + Json序列化反序列化

    1.1 結構化數(shù)據(jù) 協(xié)議是一種 “約定”,socket api的接口, 在讀寫數(shù)據(jù)時,都是按 “字符串” 的方式來發(fā)送接收的。如果我們要傳輸一些\\\"結構化的數(shù)據(jù)\\\" 怎么辦呢? 結構化數(shù)據(jù): 比如我們在QQ聊天時,并不是單純地只發(fā)送了消息本身,是把自己的頭像、昵稱、發(fā)送時間、消息本身

    2024年02月09日
    瀏覽(26)
  • TCP定制協(xié)議,序列化和反序列化

    TCP定制協(xié)議,序列化和反序列化

    目錄 前言 1.理解協(xié)議 2.網(wǎng)絡版本計算器 2.1設計思路 2.2接口設計 2.3代碼實現(xiàn): 2.4編譯測試 總結 ? ? ? ? 在之前的文章中,我們說TCP是面向字節(jié)流的,但是可能對于面向字節(jié)流這個概念,其實并不理解的,今天我們要介紹的是如何理解TCP是面向字節(jié)流的,通過編碼的方式,自

    2024年02月12日
    瀏覽(22)
  • C#: Json序列化和反序列化,集合為什么多出來一些元素?

    C#: Json序列化和反序列化,集合為什么多出來一些元素?

    如下面的例子,很容易看出問題: 如果類本身的無參構造函數(shù),?就添加了一些元素,序列化,再反序列化,會導致元素增加。 如果要避免,必須添加: new JsonSerializerSettings() { ObjectCreationHandling = ObjectCreationHandling.Replace }

    2024年02月10日
    瀏覽(19)
  • 【Linux后端服務器開發(fā)】協(xié)議定制(序列化與反序列化)

    【Linux后端服務器開發(fā)】協(xié)議定制(序列化與反序列化)

    目錄 一、應用層協(xié)議概述 二、序列化與反序列化 Protocal.h頭文件 Server.h頭文件 Client.h頭文件 server.cpp源文件 client.cpp源文件 什么是應用層 ?我們通過編寫程序解決一個個實際問題、滿足我們日常需求的網(wǎng)絡程序,都是應用層程序。 協(xié)議是一種“約定”,socket的api接口,在讀

    2024年02月16日
    瀏覽(21)
  • 又一個難題:Java 序列化和反序列化為什么要實現(xiàn) Serializable 接口?

    作者:椰子Tyshawn 來源:https://blog.csdn.net/litianxiang_kaola 最近公司的在做服務化, 需要把所有model包里的類都實現(xiàn)Serializable接口, 同時還要顯示指定serialVersionUID的值. 聽到這個需求, 我腦海里就突然出現(xiàn)了好幾個問題, 比如說: 序列化和反序列化是什么? 實現(xiàn)序列化和反序列化為什

    2024年02月08日
    瀏覽(19)
  • 【Linux】簡單的網(wǎng)絡計算器的實現(xiàn)(自定義協(xié)議,序列化,反序列化)

    【Linux】簡單的網(wǎng)絡計算器的實現(xiàn)(自定義協(xié)議,序列化,反序列化)

    我們需要實現(xiàn)一個服務器版的加法器. 我們需要客戶端把要計算的兩個加數(shù)發(fā)過去, 然后由服務器進行計算, 最后再把結果返回給客戶端` 詳細可參考我之前寫的博客【Linux】記錄錯誤信息日志的實現(xiàn)

    2024年02月19日
    瀏覽(29)
  • 協(xié)議,序列化,反序列化,Json

    協(xié)議,序列化,反序列化,Json

    協(xié)議究竟是什么呢?首先得知道主機之間的網(wǎng)絡通信交互的是什么數(shù)據(jù),像平時使用聊天APP聊天可以清楚,用戶看到的不僅僅是聊天的文字,還能夠看到用戶的頭像昵稱等其他屬性。也就可以證明網(wǎng)絡通信不僅僅是交互字符串那么簡單。事實上網(wǎng)絡通信還可能會通過一個結構

    2024年02月13日
    瀏覽(24)
  • 【Linux】自定義協(xié)議+序列化+反序列化

    【Linux】自定義協(xié)議+序列化+反序列化

    喜歡的點贊,收藏,關注一下把! 協(xié)議是一種 “約定”。在前面我們說過父親和兒子約定打電話的例子,不過這是感性的認識,今天我們理性的認識一下協(xié)議。 socket api的接口, 在讀寫數(shù)據(jù)時,都是按 “字符串”(其實TCP是字節(jié)流,這里是為了理解) 的方式來發(fā)送接收的。如

    2024年04月08日
    瀏覽(27)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包