欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux中的自定義協(xié)議+序列反序列化用法

 更新時間:2025年08月05日 17:03:34   作者:??小陳在拼命??  
文章探討網(wǎng)絡(luò)程序在應(yīng)用層的實現(xiàn),涉及TCP協(xié)議的數(shù)據(jù)傳輸機制、結(jié)構(gòu)化數(shù)據(jù)的序列化與反序列化方法,以及通過JSON和自定義協(xié)議構(gòu)建網(wǎng)絡(luò)計算器的思路,強調(diào)分層處理與解耦的重要性

我們程序員寫的一個個解決我們實際問題, 滿足我們?nèi)粘P枨蟮木W(wǎng)絡(luò)程序, 都是在應(yīng)用層.

一,再次理解協(xié)議

思考1: 我們究竟是如何將數(shù)據(jù)發(fā)送出去的?

(1)我們都知道TCP是雙加工的,所以在內(nèi)核中存在著發(fā)送緩沖區(qū)和接受緩沖區(qū),而我們write是寫入發(fā)送緩沖區(qū)的,read是從接受緩沖區(qū)里讀的,所以我們會發(fā)現(xiàn)這兩個函數(shù)其實就是拷貝函數(shù)!!write將數(shù)據(jù)從用戶層拷貝到內(nèi)核層就返回,read將數(shù)據(jù)從內(nèi)核層拷貝到用戶層就返回,意思就是我用戶不管,反正我把數(shù)據(jù)都交給你OS了,發(fā)送過程中的可靠性由你來維護(hù)。(就像當(dāng)年我們只需要將內(nèi)容寫到文件內(nèi)核緩沖區(qū),而由OS來決定什么時候,以什么方式刷新到磁盤上)

(2)所以TCP協(xié)議是屬于操作系統(tǒng)的網(wǎng)絡(luò)模塊部分,他之所以叫做傳輸控制協(xié)議,就是因為他需要確保數(shù)據(jù)傳輸過程中的可靠性,比方說我什么時候應(yīng)該發(fā)給對方?要發(fā)多少?萬一出錯了怎么辦?

思考2:回憶管道和文件系統(tǒng)

(1)以往我們往管道文件里寫了很多次的時候,可能我們一次就全部讀上來了

(2)而在文件系統(tǒng)中,我們的寫很容易,可以分很多次寫,各種類型比如整數(shù)/字符串…… 但是讀的時候就很難去讀,同時不同的文件的讀取方式可能也不一樣,比如按行讀也僅僅只是讀取文件的一種方式而已。

思考3:TCP是面向字節(jié)流的,你怎么保證你讀上來的數(shù)據(jù)一定是一個"完整的報文"呢?

(1)早期的時候我們可能會想到比方說我們約定必須要湊齊多少字節(jié)才往上讀,但是這個其實只適用于一些固定類型的報文,比方說我們規(guī)定讀的是int,而int對應(yīng)的就是一個錯誤碼或者是某一個固定的任務(wù)。但是如果是一些長度不固定的報文,比如說我們在聊天的時候發(fā)送的字符串長度都是不一樣的,那么這個時候就很容易讀到不完整的報文

(2)所以為了應(yīng)對這種情況,我們就需要在報文直接加入一下分割符,或者是標(biāo)識這個報文有多長,以確保能夠讀到完整的報文, 而協(xié)議要添加報頭就會有很多新得問題出來-比如序列化和反序列化。

二,序列化和反序列化

問題1:協(xié)議是一種 "約定". socket api的接口, 在讀寫數(shù)據(jù)時, 都是按 "字符串" 的方式來發(fā)送接收的. 如果我們要傳輸一些"結(jié)構(gòu)化的數(shù)據(jù)" 怎么辦呢?

(1)同一個結(jié)構(gòu)體在不同的編譯器下編譯的大小不一定一樣(結(jié)構(gòu)體的內(nèi)存對齊),其實Linux的協(xié)議就是傳結(jié)構(gòu)體做到的,但是他底層可以把所有方方面面的情況都考慮到了,但是我們用戶去定的時候很難考慮得這么周全。

(2) 可以如果不用結(jié)構(gòu)體的話,我們?nèi)绻米址??舉個例子,我們聊天的時候我發(fā)了一句哈哈,但是發(fā)送的時候還會帶上昵稱以及發(fā)送時間,所以我們肯定不能把這三個字符串分開發(fā),因為這也服務(wù)端就不知道你這話是誰說的,所以我肯定希望把三個字符串打包成一個字符串發(fā)過去,然后服務(wù)的把消息廣播給所有客戶端的時候,會再把這個包根據(jù)一定的方法解析分成三個字符串然后再給你顯現(xiàn)出來!------------->也就是說我們需要在類里面定義兩種方法,一種是把類內(nèi)的數(shù)據(jù)打包成一個字符串發(fā)送過去,另一種是解析的時候?qū)⑦@個字符串再拆分出來。 而這個過程就是序列化和反序列化

問題2 :我們需要實現(xiàn)一個服務(wù)器版的加法器. 我們需要客戶端把要計算的兩個加數(shù)發(fā)過去, 然后由服務(wù)器進(jìn)行計算, 最 后再把結(jié)果返回給客戶端.

約定方案一:

  • 客戶端發(fā)送一個形如"1+1"的字符串;
  • 這個字符串中有兩個操作數(shù), 都是整形;
  • 兩個數(shù)字之間會有一個字符是運算符, 運算符只能是 + ;
  • 數(shù)字和運算符之間沒有空格;
  • ...

約定方案二:

定義結(jié)構(gòu)體來表示我們需要交互的信息;

發(fā)送數(shù)據(jù)時將這個結(jié)構(gòu)體按照一個規(guī)則轉(zhuǎn)換成字符串, 接收到數(shù)據(jù)的時候再按照相同的規(guī)則把字符串轉(zhuǎn) 化回結(jié)構(gòu)體; 這個過程叫做 "序列化" 和”反序列化“

三,實現(xiàn)網(wǎng)絡(luò)計算器

3.1 日志文件

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#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);

    //     // 格式:默認(rèn)部分+自定義部分
    //     char logtxt[SIZE * 2];
    //     snprintf(logtxt, sizeof(logtxt), "%s %s\n", 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);

        // 格式:默認(rèn)部分+自定義部分
        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.2Socket.hpp

我們可以寫個套接字的小組件,這樣未來我們就可以直接去使用

#pragma once
//寫一個套接字的小組件 這樣我們未來就可以直接用
#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include "Log.hpp"

enum {
    SocketError = 1,
    BindError =2,
    ListenError = 3,
    AcceptError = 4
};
const int defaultbacklog = 10;//監(jiān)聽隊列默認(rèn)長度
class Sock
{
public:
    Sock(){}
    ~Sock(){}
    void Socket()//創(chuàng)建套接字
    {
        _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)//監(jiān)聽套接字
    {
        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和端口返回(輸出型參數(shù))
    {
        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()//關(guān)閉套接字
    {
        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地址轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序
        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 <functional>
#include <signal.h>
#include "Socket.hpp"

using func_t = std::function<std::string(std::string &package)>; // 回調(diào)函數(shù)

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); // 看看服務(wù)器在哪個端口上運行
    }
    void Start() // 啟動服務(wù)器
    {
        _isrunning = true;
        signal(SIGCHLD, SIG_IGN); // 忽略子進(jìn)程結(jié)束信號 因為子進(jìn)程結(jié)束時會產(chǎn)生SIGCHLD信號
        signal(SIGPIPE, SIG_IGN); // 忽略管道錯誤信號 因為當(dāng)網(wǎng)絡(luò)連接中某個套接字被關(guān)閉或者重啟時,該套接字已經(jīng)發(fā)送緩沖區(qū)中的數(shù)據(jù)都發(fā)送完畢了,但是它仍然可以接收數(shù)據(jù) 此時該套接字就會產(chǎn)生SIGPIPE信號
        while (_isrunning)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = _listensock.Accept(&clientip, &clientport); // 接受客戶端連接并返回套接字描述符
            if (sockfd < 0)
                continue;       // 連接失敗就繼續(xù)嘗試重連
            pid_t pid = fork(); // 創(chuàng)建子進(jìn)程 幫助我們處理每個客戶端的請求
            if (pid < 0)        // 出錯
            {
                lg(Error, "Fork error");
                continue;
            }
            if (pid == 0) // 子進(jìn)程
            {
                _listensock.Close();          // 關(guān)閉監(jiān)聽套接字 防止accept阻塞
                std::string inbuffer_stream; // 用來獲取客戶端發(fā)來的所有數(shù)據(jù)
                while (true)
                {
                    char buffer[1024];
                    ssize_t n = read(sockfd, buffer, sizeof(buffer)); // 讀取客戶端數(shù)據(jù)
                    if (n == 0)                                       // 客戶端斷開了
                    {
                        lg(Info, "Client %s:%d disconnected", clientip.c_str(), clientport);
                        break;
                    }
                    if (n < 0) // 讀取出錯
                    {
                        lg(Error, "Read error");
                        break;
                    }
                    // 拼接所有數(shù)據(jù)
                    buffer[n]=0;
                    inbuffer_stream+=buffer;
                    lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 調(diào)試看看整個流的信息
                    //有可能一個流里面有多個報文,所以我們要循環(huán)去處理
                    while(1)
                    {
                        std::string info =_callback(inbuffer_stream);// 調(diào)用回調(diào)函數(shù)獲取服務(wù)端需要的數(shù)據(jù)
                        //看看剩余的報文信息
                        if(info.empty()) break;//說明讀不到完整報文了
                        lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 調(diào)試看看整個流的信息
                        lg(Debug, "debug,response:\n%s",info.c_str());// 調(diào)試看看發(fā)給客戶端數(shù)據(jù)
                        write(sockfd, info.c_str(), info.size()); // 發(fā)送數(shù)據(jù)給客戶端
                    }
                }
                exit(0); // 子進(jìn)程結(jié)束
            }
            close(sockfd); // 關(guān)閉套接字
        }
    }

private:
    uint16_t _port;   // 端口號
    Sock _listensock; // 監(jiān)聽套接字
    func_t _callback; // 回調(diào)函數(shù) 用來提供服務(wù)
    // ip模式是0
    bool _isrunning; // 是否運行
};

1、tcpserver只負(fù)責(zé)網(wǎng)絡(luò)通信讀到了那些數(shù)據(jù),但是關(guān)于數(shù)據(jù)的解析 全部交給callback回調(diào)函數(shù)去處理這樣就是將網(wǎng)絡(luò)通信和協(xié)議解析進(jìn)行了解耦

2、因為我們并不確定讀到的是否是一個完整報文,所以我們要將讀到的內(nèi)容加到inbuffer-stream里

3.4 Protocol.hpp

協(xié)議其實就是我們雙方約定好的結(jié)構(gòu)化字段

為了確保讀到完整的報文,在前面加個有關(guān)報文長度的字段是是一種方案,在報文的后面加個分割符\其實也是一個方案

甚至我們可以在前面增加protocol select字段,表示我們選擇的不同的協(xié)議類型!

  • "len"/n"x op y"/n就是我們約定request的協(xié)議,里面要提供序列化和反序列化的方法
  • ”len“/n"result code"/n 就是我們約定的respose的協(xié)議里面要提供序列化和反序列化的方法
#pragma once

#include <iostream>
#include <string>

// #define MySelf 1

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

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"\n"x op y"\nXXXXXX
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
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)
    {
        // 構(gòu)建報文的有效載荷
        // 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 << "新請求構(gòu)建完成:  " << _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"
        // 構(gòu)建報文的有效載荷
        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 << "結(jié)果響應(yīng)完成, result: " << _result << ", code: "<< _code << std::endl;
    }
public:
    int _result;
    int _code; // 0,可信,否則!0具體是幾,表明對應(yīng)的錯誤原因
};

不僅需要有序列化和反序列化的方法,還要有添加報頭和解析報頭(要有很多檢查 將一個有效的報文提取出來)的方法

3.5 Servercal.hpp

// 計算器服務(wù)

#include <iostream>
#include "Protocol.hpp" //協(xié)議頭文件 必須遵守

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;
}

//設(shè)計一個回調(diào)方法 來幫助我們計算
std::string Cal(std::string package) //回調(diào)方法  到時傳過去
{
    std::string content;//返回的內(nèi)容
    bool r=Decode(package,&content);
    if(!r)  return "";//報文不完整
    //我們要將這個報文反序列化拿到數(shù)據(jù) 然后計算成respond 然后再序列化發(fā)給客戶端
    Request req;
    r=req.Deserialize(content); //"10 + 20 " ->
    if(!r) return ""; //解析失敗
    content=""; //清空再利用
    Response resp=Calhelper(req);
    resp.Serialize(&content);
    content=Encode(content);//把報頭加上去
    return content;
}

};

在這里寫一個回調(diào)方法,如果解析失敗的話就返回空串

3.6 ServerCal.cc

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

static void Usage(const std::string &proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << 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.7ClientCal.cc

#include <iostream>
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp" //客戶端也得知道協(xié)議

static void Usage(const std::string &proc)
{
    std::cout << "\nUsage: " << proc << " serverip serverport\n"
              << 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);//嘗試和服務(wù)端連接
    if(!r)
    {
        std::cout<<"連接失敗"<<std::endl;
        return -1;
    }
    //否則就是連接成功
    srand(time(nullptr) ^ getpid());
    int cnt = 1;
    const std::string opers = "+-*/%=-=&^";

    std::string inbuffer_stream;
    while(cnt <= 10)
    {
        std::cout << "===============第" << cnt << "次測試....., " << "===============" << std::endl;
        int x = rand() % 100 + 1;
        usleep(1234);
        int y = rand() % 100;
        usleep(4321);
        char oper = opers[rand()%opers.size()];
        Request req(x, y, oper);
        req.DebugPrint();

        std::string package;
        req.Serialize(&package);

        package = Encode(package);

        write(sockfd.GetSockfd(), package.c_str(), package.size());
        // std::cout << "這是最新的發(fā)出去的請求: " << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "這是最新的發(fā)出去的請求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "這是最新的發(fā)出去的請求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "這是最新的發(fā)出去的請求: \n" << n << "\n" << package;


        char buffer[128];
        ssize_t n = read(sockfd.GetSockfd(), buffer, sizeof(buffer)); // 我們也無法保證我們能讀到一個完整的報文
        if(n > 0)
        {
            buffer[n] = 0;
            inbuffer_stream += buffer; // "len"\n"result code"\n
            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 <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

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. 更改當(dāng)前調(diào)用進(jìn)程的工作目錄
    if (!cwd.empty())
        chdir(cwd.c_str());

    // 4. 標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,標(biāo)準(zhǔn)錯誤重定向至/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

  • 關(guān)于json的下載

安裝這個json,其實就是將頭文件和源文件安裝在制定的路徑下

  • 頭文件:

  • 庫文件:

  • 使用第三方庫必須要指定鏈接

測試代碼:

#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>

// {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是風(fēng)格寫

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

3.10Protocol.hpp改進(jìn)版

#pragma once

#include <iostream>
#include <string>
#include<jsoncpp/json/json.h>
// #define MySelf 1

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

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"\n"x op y"\nXXXXXX
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
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
        // 構(gòu)建報文的有效載荷
        // 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 << "新請求構(gòu)建完成:  " << _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"
        // 構(gòu)建報文的有效載荷
        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 << "結(jié)果響應(yīng)完成, result: " << _result << ", code: "<< _code << std::endl;
    }
public:
    int _result;
    int _code; // 0,可信,否則!0具體是幾,表明對應(yīng)的錯誤原因
};

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

四,再談七層協(xié)議

  • 會話層: 由服務(wù)端解決獲取新鏈接,維護(hù)整個鏈接的使用情況,創(chuàng)建子進(jìn)程來對外提供服務(wù),相當(dāng)于沒訪問一次服務(wù)我就會創(chuàng)建一個新的會話,然后去處理新的連接,不需要就關(guān)掉
  • 表示層:相當(dāng)于協(xié)議的序列化和反序列化
  • 應(yīng)用層:處理數(shù)據(jù),不同的數(shù)據(jù)需要有不同的協(xié)議

為什么要壓成一層呢??因為以上都是在用戶層去實現(xiàn)的,因為方法由很多但是誰也說服不了誰,所以無法統(tǒng)一把他搞到OS模塊而必須由用戶根據(jù)不同的場景去定制

每次服務(wù)都要自己去定自定義協(xié)議嗎??其實是不需要的,有人想就會有人去做,所以應(yīng)用層的協(xié)議大部分不需要自己寫,可以直接用就可以了,因為他全都考慮到了。 一般公司產(chǎn)品比較嚴(yán)格不會讓你使用第三方庫。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Linux中的進(jìn)程守護(hù)supervisor安裝配置及使用

    Linux中的進(jìn)程守護(hù)supervisor安裝配置及使用

    supervisor是一個很好的守護(hù)程序管理工具,配置方面自動啟動,日志輸出,自動切割日志等等一系列強大功能,下面是在CentOS下安裝使用supervisor的記錄,非常不錯,感興趣的朋友跟隨小編一起看看吧
    2019-07-07
  • 動態(tài)在線擴(kuò)容root根分區(qū)大小的方法詳解

    動態(tài)在線擴(kuò)容root根分區(qū)大小的方法詳解

    這篇文章主要給大家介紹了關(guān)于如何動態(tài)在線擴(kuò)容root根分區(qū)大小的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-05-05
  • Ubuntu環(huán)境源碼編譯安裝xdebug的方法

    Ubuntu環(huán)境源碼編譯安裝xdebug的方法

    這篇文章主要介紹了Ubuntu環(huán)境源碼編譯安裝xdebug的方法,較為詳細(xì)的分析了Ubuntu環(huán)境xdebug編譯安裝的具體步驟、命令與相關(guān)操作注意事項,需要的朋友可以參考下
    2019-08-08
  • Linux下yum命令及軟件的安裝方式

    Linux下yum命令及軟件的安裝方式

    這篇文章主要介紹了Linux下yum命令及軟件的安裝方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • Yum中報錯:“pycurl.so: undefined symbol: CRYPTO_num_locks”的問題排查

    Yum中報錯:“pycurl.so: undefined symbol: CRYPTO_num_locks”的問題排查

    這篇文章主要給大家介紹了在Yum中報錯: "pycurl.so: undefined symbol: CRYPTO_num_locks"的問題排查的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-06-06
  • centos6利用yum安裝php mysql gd的步驟

    centos6利用yum安裝php mysql gd的步驟

    我在84vps中利用yum順利安裝mysql php 及gd庫,因為vps本身自帶了apahce2.2所以沒有apache安裝過程
    2012-09-09
  • Apache?HTTP?安裝和配置下載詳解

    Apache?HTTP?安裝和配置下載詳解

    這篇文章主要介紹了Apache?HTTP?安裝和配置下載,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2023-12-12
  • Linux的Socket IO模型趣解

    Linux的Socket IO模型趣解

    這篇文章主要通過一個幽默的方式為大家詳細(xì)介紹了Linux的Socket IO模型,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • vscode遠(yuǎn)程開發(fā)使用SSH遠(yuǎn)程連接服務(wù)器的方法「內(nèi)網(wǎng)穿透」

    vscode遠(yuǎn)程開發(fā)使用SSH遠(yuǎn)程連接服務(wù)器的方法「內(nèi)網(wǎng)穿透」

    這篇文章主要介紹了vscode遠(yuǎn)程開發(fā)使用SSH遠(yuǎn)程連接服務(wù)器?「內(nèi)網(wǎng)穿透」,通過本文學(xué)習(xí)我們將通過vscode實現(xiàn)遠(yuǎn)程開發(fā),并做內(nèi)網(wǎng)穿透實現(xiàn)在公網(wǎng)環(huán)境下的遠(yuǎn)程連接,在外任意地方也可以遠(yuǎn)程連接服務(wù)器進(jìn)行開發(fā)寫代碼,需要的朋友可以參考下
    2023-02-02
  • ZFS是什么?使用ZFS的理由及特性介紹

    ZFS是什么?使用ZFS的理由及特性介紹

    今天小編就為大家分享一篇關(guān)于ZFS是什么,及其特性介紹與使用理由的文章,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2018-09-09

最新評論