最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

我們程序員寫的一個(gè)個(gè)解決我們實(shí)際問題, 滿足我們?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ū)里讀的,所以我們會(huì)發(fā)現(xiàn)這兩個(gè)函數(shù)其實(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來決定什么時(shí)候,以什么方式刷新到磁盤上)

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

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

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

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

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

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

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

二,序列化和反序列化

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

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

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

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

約定方案一:

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

約定方案二:

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

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

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

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); // 暫時(shí)打印
    //     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); // 暫時(shí)打印
        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; // 命令對(duì)象 用來打印日志信息

3.2Socket.hpp

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

#pragma once
//寫一個(gè)套接字的小組件 這樣我們未來就可以直接用
#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)聽隊(duì)列默認(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);//如果接受失敗 打印錯(cuò)誤信息并退出程序
            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ù)器在哪個(gè)端口上運(yùn)行
    }
    void Start() // 啟動(dòng)服務(wù)器
    {
        _isrunning = true;
        signal(SIGCHLD, SIG_IGN); // 忽略子進(jìn)程結(jié)束信號(hào) 因?yàn)樽舆M(jìn)程結(jié)束時(shí)會(huì)產(chǎn)生SIGCHLD信號(hào)
        signal(SIGPIPE, SIG_IGN); // 忽略管道錯(cuò)誤信號(hào) 因?yàn)楫?dāng)網(wǎng)絡(luò)連接中某個(gè)套接字被關(guān)閉或者重啟時(shí),該套接字已經(jīng)發(fā)送緩沖區(qū)中的數(shù)據(jù)都發(fā)送完畢了,但是它仍然可以接收數(shù)據(jù) 此時(shí)該套接字就會(huì)產(chǎn)生SIGPIPE信號(hào)
        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)程 幫助我們處理每個(gè)客戶端的請(qǐng)求
            if (pid < 0)        // 出錯(cuò)
            {
                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) // 讀取出錯(cuò)
                    {
                        lg(Error, "Read error");
                        break;
                    }
                    // 拼接所有數(shù)據(jù)
                    buffer[n]=0;
                    inbuffer_stream+=buffer;
                    lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 調(diào)試看看整個(gè)流的信息
                    //有可能一個(gè)流里面有多個(gè)報(bào)文,所以我們要循環(huán)去處理
                    while(1)
                    {
                        std::string info =_callback(inbuffer_stream);// 調(diào)用回調(diào)函數(shù)獲取服務(wù)端需要的數(shù)據(jù)
                        //看看剩余的報(bào)文信息
                        if(info.empty()) break;//說明讀不到完整報(bào)文了
                        lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 調(diào)試看看整個(gè)流的信息
                        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;   // 端口號(hào)
    Sock _listensock; // 監(jiān)聽套接字
    func_t _callback; // 回調(diào)函數(shù) 用來提供服務(wù)
    // ip模式是0
    bool _isrunning; // 是否運(yùn)行
};

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

2、因?yàn)槲覀儾⒉淮_定讀到的是否是一個(gè)完整報(bào)文,所以我們要將讀到的內(nèi)容加到inbuffer-stream里

3.4 Protocol.hpp

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

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

甚至我們可以在前面增加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 移除報(bào)文 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)建報(bào)文的有效載荷
        // 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 << "新請(qǐng)求構(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)建報(bào)文的有效載荷
        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具體是幾,表明對(duì)應(yīng)的錯(cuò)誤原因
};

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

3.5 Servercal.hpp

// 計(jì)算器服務(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è)計(jì)一個(gè)回調(diào)方法 來幫助我們計(jì)算
std::string Cal(std::string package) //回調(diào)方法  到時(shí)傳過去
{
    std::string content;//返回的內(nèi)容
    bool r=Decode(package,&content);
    if(!r)  return "";//報(bào)文不完整
    //我們要將這個(gè)報(bào)文反序列化拿到數(shù)據(jù) 然后計(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);//把報(bào)頭加上去
    return content;
}

};

在這里寫一個(gè)回調(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;//用來做計(jì)算請(qǐng)求的對(duì)象
   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ā)出去的請(qǐng)求: " << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "這是最新的發(fā)出去的請(qǐng)求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "這是最新的發(fā)出去的請(qǐng)求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "這是最新的發(fā)出去的請(qǐng)求: \n" << n << "\n" << package;


        char buffer[128];
        ssize_t n = read(sockfd.GetSockfd(), buffer, sizeof(buffer)); // 我們也無法保證我們能讀到一個(gè)完整的報(bào)文
        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. 忽略其他異常信號(hào)
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2. 將自己變成獨(dú)立的會(huì)話
    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)錯(cuò)誤重定向至/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簡單使用

其實(shí)我們有更好的兩個(gè)工具能幫我們完成序列化和反序列化,一個(gè)是json 一個(gè)是productor

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

安裝這個(gè)json,其實(shí)就是將頭文件和源文件安裝在制定的路徑下

  • 頭文件:

  • 庫文件:

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

測試代碼:

#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代表對(duì)象

(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 移除報(bào)文 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)建報(bào)文的有效載荷
        // 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 << "新請(qǐng)求構(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)建報(bào)文的有效載荷
        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具體是幾,表明對(duì)應(yīng)的錯(cuò)誤原因
};

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é)議

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

為什么要壓成一層呢??因?yàn)橐陨隙际窃谟脩魧尤?shí)現(xiàn)的,因?yàn)榉椒ㄓ珊芏嗟钦l也說服不了誰,所以無法統(tǒng)一把他搞到OS模塊而必須由用戶根據(jù)不同的場景去定制

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

總結(jié)

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

相關(guān)文章

  • linux URL的301重定向代碼分析

    linux URL的301重定向代碼分析

    301重定向已經(jīng)過很多次了,它可以將網(wǎng)站的主域名和二級(jí)域名規(guī)范到一個(gè)url上,使網(wǎng)頁在搜索引擎里只能通過唯一的一個(gè)地址可以訪問到,以免因重復(fù)內(nèi)容造成降權(quán)。
    2008-09-09
  • 讓Apache支持cgi、SSI、shtml的配置方法

    讓Apache支持cgi、SSI、shtml的配置方法

    配置Apache支持cgi、SSI、shtml,供大家學(xué)習(xí)參考
    2013-02-02
  • CentOS下搭建Sendmail郵件服務(wù)器步驟詳解

    CentOS下搭建Sendmail郵件服務(wù)器步驟詳解

    本篇文章主要介紹了CentOS下搭建Sendmail郵件服務(wù)器步驟詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-12-12
  • Linux系統(tǒng)內(nèi)存不足導(dǎo)致find命令失敗的解決方案

    Linux系統(tǒng)內(nèi)存不足導(dǎo)致find命令失敗的解決方案

    在麒麟Linux Advanced Server V10環(huán)境下執(zhí)行find / -name palddumper-debug.log命令時(shí),系統(tǒng)返回了bash: fork: Cannot allocate memory錯(cuò)誤,這表明系統(tǒng)在執(zhí)行文件查找過程中遇到了內(nèi)存分配問題,本文給大家介紹了詳細(xì)的解決方法,需要的朋友可以參考下
    2025-07-07
  • linux服務(wù)器本地如何開啟visdom

    linux服務(wù)器本地如何開啟visdom

    這篇文章主要介紹了linux服務(wù)器本地如何開啟visdom問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-06-06
  • CentOS修改服務(wù)器登錄密碼的詳細(xì)指南

    CentOS修改服務(wù)器登錄密碼的詳細(xì)指南

    在日常使用?Linux?服務(wù)器時(shí),修改用戶登錄密碼是維護(hù)系統(tǒng)安全性的重要環(huán)節(jié),本文將詳細(xì)介紹如何在?CentOS?系統(tǒng)中修改用戶密碼,包括可能遇到的常見問題及其解決方案,需要的朋友可以參考下
    2024-10-10
  • 阿里云 CentOS7.4 安裝 Python3.6的方法講解

    阿里云 CentOS7.4 安裝 Python3.6的方法講解

    下面小編就為大家分享一篇阿里云 CentOS7.4 安裝 Python3.6的方法講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • Linux kde中11個(gè)你不知道的應(yīng)用程序

    Linux kde中11個(gè)你不知道的應(yīng)用程序

    今天小編就為大家分享一篇關(guān)于Linux kde中11個(gè)你不知道的應(yīng)用程序,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2018-10-10
  • 淺談Linux配置定時(shí),使用crontab -e與直接編輯/etc/crontab的區(qū)別

    淺談Linux配置定時(shí),使用crontab -e與直接編輯/etc/crontab的區(qū)別

    下面小編就為大家?guī)硪黄獪\談Linux配置定時(shí),使用crontab -e與直接編輯/etc/crontab的區(qū)別。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-11-11
  • Linux crontab定時(shí)任務(wù)配置方法(詳解)

    Linux crontab定時(shí)任務(wù)配置方法(詳解)

    下面小編就為大家?guī)硪黄狶inux crontab定時(shí)任務(wù)配置方法(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-11-11

最新評(píng)論

宜兰市| 江北区| 竹山县| 霸州市| 乐昌市| 类乌齐县| 驻马店市| 名山县| 吉木乃县| 外汇| 合肥市| 武隆县| 高清| 正镶白旗| 五华县| 渑池县| 理塘县| 灵武市| 宜宾县| 永州市| 惠安县| 禹城市| 闸北区| 成安县| 裕民县| 叙永县| 黎川县| 宜阳县| 晋州市| 博湖县| 曲松县| 思南县| 遂昌县| 大荔县| 米易县| 通化市| 枣强县| 临邑县| 和静县| 太和县| 庄浪县|