Linux网络应用层自定义协议与序列化

协议是一种"约定",如果我们要传输一些结构化数据就需要协议,协议就是约定好的数据结构。

假设现在我们要实现一个服务器版的加法器,我们需要客户端把要计算的两个加数发过去,然后由服务器进行计算,最后再把结果返回给客户端。对此,我们有两种传输数据的形式:

约定方案一:

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

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

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

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

约定方案二:

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

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

对于方案二,双方都能看到同一个数据结果,转换成结构体的这个过程叫做 "序列化" 和 "反序列化"。

序列化和反序列化

序列化是为了方便网络发送,反序列化是转回结构体,方便上层业务。

TCPsocket中读写都是一个fd,这是因为在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,并不冲突,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工。read,write,recv,send本质是拷贝函数。

发数据:是从发送方的发送缓冲区把数据通过协议栈和网络拷贝给对方的接收缓冲区。

数据怎么发,发多少,出错解决方案由TCP自己决定,因此TCP叫做传输控制协议。先这样在接收缓冲区中,一个进程写,一个进程读,就很像生产消费模型,IO函数阻塞的原因就是在维护同步关系。

但是TCP有个问题,它是面向字节流的,客户端可能只接收到数据的一半,或者发送方一下子发送了好几条信息,那么就不能轻易处理(UDP是数据包不需要考虑)。

因此我们要想办法保证获取的是完整的请求(数据)。

Jsoncpp

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各 种需要处理 JSON 数据的 C++ 项目中。

特性:

  1. 简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。

  2. 高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。

  3. 全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数 字、布尔值和 null。

  4. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便 开发者调试

安装:

复制代码
sudo apt-get install libjsoncpp-dev

序列化使用方法:

cpp 复制代码
//使用Json::Value的toStyledString
Json::Value root;
root["name"] = "syx";
root["msg"] = "666";
std::string s = root.toStyledString();
std::cout << s << std::endl;
cpp 复制代码
//使用StreamWriter
Json::Value root;
root["name"] = "syx";
root["msg"] = "666";
Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂
std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
std::stringstream ss;
writer->write(root, &ss);
std::cout << ss.str() << std::endl;
cpp 复制代码
//使用Json::FastWriter或Json::StyledWriter
Json::Value root;
root["name"] = "syx";
root["msg"] = "666";
Json::FastWriter writer;
//Json::StyledWriter writer
std::string s = writer.write(root);
std::cout << s << std::endl;

反序列化:

cpp 复制代码
//使用Json::Reader
Json::Value root;
Json::Reader reader;
bool res = reader.parse(in, root);
_name = root["name"].asString();
_msg = root["msg"].asString();

案例:网络版计算器

要求服务器给出计算结果,可进行加减乘除取模运算。

cpp 复制代码
//Log.hpp
#pragma once
#include<unistd.h>
#include<iostream>
#include<time.h>
#include<stdarg.h>
#include<fstream>
#include<string.h>
#include<pthread.h>
enum
{
    DEBUG = 1,
    INFO,
    WARNING,
    ERROR,
    FATAL
};
const std::string logfile = "log.txt";
pthread_mutex_t _mutex;
#define SCREEN_TYPE 1
#define FILE_TYPE 2
std::string LevelToString(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
        break;
    case INFO:
        return "INFO";
        break;
    case ERROR:
        return "ERROR";
        break;
    case WARNING:
        return "WARNING";
        break;
    case FATAL:
        return "FATAL";
        break;
    default:
        return "UNKONW";
        break;
    }
}
std::string GetTime()
{
    time_t now = time(nullptr);
    struct tm *curr = localtime(&now);
    char buf[128];
    snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d",
             curr->tm_year+1900,
             curr->tm_mon+1,
             curr->tm_mday,
             curr->tm_hour,
             curr->tm_min,
             curr->tm_sec);
    return buf;
}
class LockGuard
{
public:
    LockGuard(pthread_mutex_t* td):_td(td)
    {
        pthread_mutex_lock(_td);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_td);
    }

private:
    pthread_mutex_t *_td;
};
class Logmessage
{
public:
    std::string _level;
    pid_t _id;
    std::string _filename;
    int _filenumber;//行号
    std::string _curr_time;
    std::string _message_info;
};
class Log
{
public:
    Log(const std::string& filename=logfile):_logfile(filename)
    {}
    void Enable(int type)
    {
        _type = type;
    }
    void FlushToScreen(Logmessage& lg)
    {
        printf("[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());

    }
    void FlushToFile(Logmessage& lg)
    {
        std::ofstream t(_logfile,std::ios::app);
        if(!t.is_open())
            return;
        char logtxt[1024];
        snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s", lg._level.c_str(), lg._id, lg._filename.c_str(), lg._filenumber, lg._curr_time.c_str(), lg._message_info.c_str());
        t.write(logtxt, strlen(logtxt));
        t.close();
    }
    void FlushLog(Logmessage& lg)
    {
        LockGuard ld(&_mutex);
        // 此处可以加过滤,本代码没加
        if(_type==SCREEN_TYPE)
        {
            FlushToScreen(lg);
        }
        else if(_type==FILE_TYPE)
        {
            FlushToFile(lg);
        }
    }

    void logmessage(int level,std::string filename,int filenumber,const char* format,...)
    {
        Logmessage lg;
        lg._level = LevelToString(level);
        lg._id = getpid();
        lg._filename = filename;
        lg._filenumber = filenumber;
        lg._curr_time = GetTime();
        va_list ap;
        va_start(ap, format);
        char info[512];
        vsnprintf(info, sizeof(info), format, ap);
        va_end(ap);
        lg._message_info = info;
        FlushLog(lg);
    }
    ~Log(){}
private:
    int _type=SCREEN_TYPE;
    std::string _logfile;
};
Log lg;
#define LOG(level,format,...) do{lg.logmessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__);}while (0)
#define EnableScreen() do{ lg.Enable(SCREEN_TYPE);} while (0)
#define EnableFILE() do{ lg.Enable(FILE_TYPE);} while (0)
cpp 复制代码
//InetAddr.hpp
#pragma once
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<iostream>
using namespace std;
class InetAddr
{
private:
    void ToHost()
    {
        _port = ntohs(_addr.sin_port);
        _ip = inet_ntoa(_addr.sin_addr);
    }
public:
    InetAddr(const struct sockaddr_in& addr):_addr(addr)
    {
        ToHost();
    }
    InetAddr() = default;
    string Ip() { return _ip; }
    uint16_t Port() { return _port; }

private:
    string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};
cpp 复制代码
//Socket.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include <cstring>
#include "InetAddr.hpp"
#include <sys/wait.h>
#include <functional>
#include <memory>
using namespace std;
const static uint16_t gport = 6666;
const static int gsock = -1;
const static int gblcklog = 8;
enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR
};
class Socket;
using SockSPtr = std::shared_ptr<Socket>;

class Socket
{
public:
    virtual void CreateSocketOrDie() = 0;
    virtual void CreateBindOrDie(uint16_t port = gport) = 0;
    virtual void CreateListenOrDie(int backlog = gblcklog) = 0;
    virtual SockSPtr Accepter(InetAddr *cliaddr) = 0;
    virtual bool Connector(const string &serverip, uint16_t serverport) = 0;
    virtual int sockfd() = 0;
    virtual int Close() = 0;
    virtual int Recv(string *out) = 0;
    virtual int Send(string &in) = 0;

public:
    bool BuildListenSocket(uint16_t port = gport)
    {
        CreateSocketOrDie();
        CreateBindOrDie(port);
        CreateListenOrDie();
        return true;
    }
    bool BuildClientSocket(const string &serverip, uint16_t serverport = gport)
    {
        CreateSocketOrDie();
        return Connector(serverip, serverport);
    }
};
class TcpSocket : public Socket
{
public:
    TcpSocket(int sockfd = gsock) : _sockfd(sockfd) {}
    void CreateSocketOrDie()
    {
        _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "socket create success\n");
    }
    void CreateBindOrDie(uint16_t port = gport)
    {
        _port = port;
        struct sockaddr_in local;
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        int n = ::bind(_sockfd, (sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success\n");
    }
    void CreateListenOrDie(int backlog = gblcklog)
    {
        if (::listen(_sockfd, backlog) < 0)
        {
            LOG(FATAL, "listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO, "listen success\n");
    }
    SockSPtr Accepter(InetAddr *cliaddr)
    {

        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int sockfd = ::accept(_sockfd, (struct sockaddr *)&client, &len);
        if (sockfd < 0)
        {
            LOG(WARNING, "accept error\n");
            return nullptr;
        }
        *cliaddr = InetAddr(client);
        LOG(INFO, "new connect %s\n", cliaddr->Ip().c_str());
        return std::make_shared<TcpSocket>(sockfd);
    }
    bool Connector(const string &serverip, uint16_t serverport)
    {
        struct sockaddr_in server;
        server.sin_port = serverport;
        server.sin_family = AF_INET;
        ::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);
        int n = ::connect(_sockfd, (struct sockaddr *)&server, sizeof(server));
        if (n < 0)
        {
            cerr << "connect error" << endl;
            return false;
        }
        return true;
    }
    int sockfd()
    {
        return _sockfd;
    }
    int Close()
    {
        if (_sockfd > 0)
            ::close(_sockfd);
        return 1;
    }
    int Recv(string *out)
    {
        char buf[2048];
        ssize_t n = ::recv(_sockfd, buf, sizeof(buf) - 1, 0);
        if (n > 0)
        {
            buf[n] = 0;
            *out += buf;
        }
        return n;
    }
    int Send(string &in)
    {
        return ::send(_sockfd, in.c_str(), in.size(), 0);
    }

private:
    uint16_t _port;
    int _sockfd; //_listensockfd or _sockfd;
};
cpp 复制代码
//TcpServer.hpp
#include"Socket.hpp"
using service_io_t = function<void(SockSPtr, InetAddr&)>;
class TcpServer
{
public:
    TcpServer(service_io_t service,uint16_t port=gport):_listensock(make_shared<TcpSocket>(gport)),_service(service)
    {
        _listensock->BuildListenSocket();
    }
    class ThreadDate
    {
    public:
    ThreadDate(SockSPtr sockfd,TcpServer* self,const InetAddr& addr):_sockfd(sockfd),_self(self),_addr(addr)
    {}
        SockSPtr _sockfd;
        TcpServer *_self;
        InetAddr _addr;
    };
   
    void Loop()
    {
        _isrunning = true;
        while(_isrunning)
        {
            InetAddr client;
            SockSPtr newsock=_listensock->Accepter(&client);
            LOG(INFO, "new connect %s\n", client.Ip().c_str());
            if(newsock==nullptr)
                continue;
            // 多线程版本
            pthread_t tid;
            ThreadDate *td = new ThreadDate(newsock, this,client);
            pthread_create(&tid, nullptr, Excute, td);
        }
    }
    static void* Excute(void* args)
    {
        pthread_detach(pthread_self());
        ThreadDate *td = static_cast<ThreadDate *>(args);
        td->_self->_service(td->_sockfd,td->_addr);
        td->_sockfd->Close();
        delete td;
        return nullptr;
    }

    ~TcpServer()
    {}

private:
    SockSPtr _listensock;
    bool _isrunning = false;
    service_io_t _service;
};
cpp 复制代码
//Service.hpp
#pragma once
#include <iostream>
#include "InetAddr.hpp"
#include<functional>
#include "Socket.hpp"
#include"Protocol.hpp"
using process_t = std::function<std::shared_ptr<Response>(std::shared_ptr<Request>)>;
class IOService
{
public:
    IOService(process_t process):_process(process)
    {}
    void IOExcute(SockSPtr sock, InetAddr &addr)
    {
        string packagestreamqueue;
        while (1)
        {
            int n = sock->Recv(&packagestreamqueue);
            if (n > 0)
            {
                string jsonstr=Decode(packagestreamqueue);
                if(jsonstr!="")
                {
                    cout << "get:" << jsonstr << endl;
                    // 获取到完整的报文
                    auto req = Factory::BuildRequest();
                    req->Deserialize(jsonstr);
                    auto resp = _process(req);
                    string respjson;
                    resp->Serialize(&respjson);
                    respjson=Encode(respjson);
                    cout << "result:" << respjson << endl;
                    cout << "-----------------------" << endl;
                    sock->Send(respjson);
                }
            }
            else if (n == 0)
            {
                LOG(INFO, "client %s quit\n", addr.Ip().c_str());
                break;
            }
            else
            {
                LOG(ERROR, "read %s error\n", addr.Ip().c_str());
                break;
            }
        }
       
    }
    ~IOService()
    {
    }

private:
    process_t _process;
};
cpp 复制代码
//Protocol.hpp
#pragma once
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
#include <memory>
using namespace std;
static const string jsep = "\r\n";
// 设计协议的报头和报文的完整格式
//"len"/r/n{json}/r/n ----len有效载荷长度
//  /r/n:区分len和json串 第二个是为了方便打印

// 添加报头
string Encode(const string &jsonstr)
{
    int len = jsonstr.size();
    string lenstr = to_string(len);
    return lenstr + jsep + jsonstr + jsep;
}
string Decode(string &packagestream)
{
    auto pos = packagestream.find(jsep);
    if (pos == std::string::npos) //
        return "";
    string lenstr = packagestream.substr(0, pos); // 保证长度有
    int len = std::stoi(lenstr);
    int total = lenstr.size() + len + 2 * jsep.size(); // 完整报文的长度;
    if (packagestream.size() < total)                  // 没读完整
    {
        return "";
    }
    string jsonstr = packagestream.substr(pos + jsep.size(), len);
    packagestream.erase(0, total);
    return jsonstr;
}
class Request
{
public:
    Request()
    {
    }
    Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
    {
    }
    void Serialize(string *out)
    {
        // jsoncpp
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;
        Json::FastWriter writer;
        string s = writer.write(root);
        *out = s;
    }
    void Deserialize(const string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(in, root);
        if (res)
        {
            _x = root["x"].asInt();
            _y = root["y"].asInt();
            _oper = root["oper"].asInt();
        }
        else
            exit(1);
    }
    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    char Oper()
    {
        return _oper;
    }
    void Set(int x,int y,char oper)
    {
        _x=x;
        _y = y;
        _oper = oper;
    }
    ~Request()
    {
    }

private:
    int _x;
    int _y;
    char _oper; //+ - * / %
};
class Response
{
public:
    Response()
    {
    }
    void Serialize(string *out)
    {
        // jsoncpp
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        root["desc"] = _desc;
        Json::FastWriter writer;
        string s = writer.write(root);
        *out = s;
    }
    void Deserialize(const string &in)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(in, root);
        if (res)
        {
            _result = root["result"].asInt();
            _code = root["code"].asInt();
            _desc = root["desc"].asCString();
        }
        else

            exit(1);
    }
    void PrintResult()
    {
        cout << "result:" << _result << " code:" << _code << " desc:" << _desc << endl;
    }
    ~Response()
    {
    }

    int _result=0;
    int _code=0; // 0 :success 1 :div zero 2:非法操作
    string _desc="success";
};
class Factory
{
public:
    static std::shared_ptr<Request> BuildRequest()
    {
        return make_shared<Request>();
    }
    static std::shared_ptr<Response> BuildResponse()
    {
        return make_shared<Response>();
    }
};
cpp 复制代码
//NetCal.hpp
#pragma once
#include "Protocol.hpp"
#include <functional>
#include <map>
class NetCal
{
public:
    NetCal() {}
    ~NetCal() {}
    std::shared_ptr<Response> Caculator(std::shared_ptr<Request> req)
    {
        auto resp = make_shared<Response>();
        if (req->Oper() == '/' || req->Oper() == '%')
        {
            if (req->Y() == 0)
            {
                resp->_code = 1;
                if (req->Oper() == '-')
                {
                    resp->_desc = "div zero";
                }
                else
                {
                    resp->_desc = "mod zero";
                }
                return resp;
            }
        }
        map<char, std::function<int(int, int)>> cal;
        cal.insert(std::make_pair('+', [](int x, int y)
                   { return x + y; }));
        cal.insert(std::make_pair('-', [](int x, int y)
                   { return x - y; }));
        cal.insert(std::make_pair('*', [](int x, int y)
                   { return x * y; }));
        cal.insert(std::make_pair('/', [](int x, int y)
                   { return x / y; }));
        cal.insert(std::make_pair('%', [](int x, int y)
                   { return x % y; }));
        if (cal.find(req->Oper()) == cal.end())
        {
            resp->_code = 2;
            resp->_desc = "非法操作";
            return resp;
        }
        resp->_result = cal[req->Oper()](req->X(), req->Y());
        return resp;
    }
};
cpp 复制代码
//ServerMain.cpp
#include"TcpServer.hpp"
#include"Service.hpp"
#include"NetCal.hpp"
int main()
{
    NetCal cal;
    IOService service(std::bind(&NetCal::Caculator,&cal, std::placeholders::_1));
    service_io_t fuc = std::bind(&IOService::IOExcute, &service, placeholders::_1, placeholders::_2);
    std::unique_ptr<TcpServer> server = std::make_unique<TcpServer>(fuc);
    server->Loop();
    return 0;
}
cpp 复制代码
//ClientMain.cpp
#include "Socket.hpp"
#include "Protocol.hpp"
#include <ctime>
#include <unistd.h>
using namespace std;
int main(int argc, char *argv[])
{
    srand(time(nullptr));
    if (argc != 3)
    {
        cerr << "usage:" << argv[0] << "error" << endl;
        exit(0);
    }
    string serverip = argv[1];
    uint16_t serverport = htons(stoi(argv[2]));
    SockSPtr sock = std::make_shared<TcpSocket>();
    if (!sock->BuildClientSocket(serverip, serverport))
    {
        cerr << "connect error" << endl;
        exit(1);
    }
    const string opers = "+-*/%";
    while (1)
    {
        sleep(1);
        int x = rand() % 100 + 1;
        int y = rand() % 100 ;
        char oper = opers[rand() % 5];
        // 请求
        auto req = Factory::BuildRequest();
        req->Set(x, y, oper);
        // 序列化
        string reqstr;
        req->Serialize(&reqstr);
        // 增加报头
        reqstr = Encode(reqstr);
        cout << "request:" << reqstr << endl;
        // 发送
        sock->Send(reqstr);
        // 接收
        string packagestream;
        while (1)
        {
            int n = sock->Recv(&packagestream);
            if (n < 0)
                break;
            string package = Decode(packagestream);
            if (package == "")
                continue;
            else
            {
                auto resp = Factory::BuildResponse();
                resp->Deserialize(package);
                resp->PrintResult();
                cout << "-------------------------" << endl;
                break;
            }
        }
    }
    return 0;
}
cpp 复制代码
//makefile
.phony:all
all:calserver calclient
calclient:ClientMain.cpp
	g++ -o $@ $^ -std=c++14 -ljsoncpp
calserver:ServerMain.cpp 
	g++ -o $@ $^ -std=c++14 -l pthread -ljsoncpp
.phony:clean
clean:
	rm -rf calserver calclient
相关推荐
egoist20232 小时前
[linux仓库]图解System V共享内存:从shmget到内存映射的完整指南
linux·开发语言·共享内存·system v
葵花日记2 小时前
LINUX——进度条
linux·运维·服务器
hmcjn(小何同学)3 小时前
轻松Linux-10.进程信号
linux·运维·服务器
用户31187945592183 小时前
libopenssl1_0_0-1.0.2p-3.49.1.x86_64安装教程(RPM包手动安装步骤+依赖解决附安装包下载)
linux
驱动探索者3 小时前
linux 学习平台 arm+x86 搭建
linux·arm开发·学习
深思慎考3 小时前
【新版】Elasticsearch 8.15.2 完整安装流程(Linux国内镜像提速版)
java·linux·c++·elasticsearch·jenkins·框架
微电子爱好者3 小时前
TCP和UDP调试工具的介绍和使用
linux·tcp/ip·udp
mxpan3 小时前
VirtualBox中ubuntu1804虚拟机共享文件夹设置
linux·运维·服务器
别多香了3 小时前
项目实战:ecshop
linux·运维·服务器