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

应用层与协议

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.

协议是一种 "约定". socket api 的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "结构化的数据" 怎么办呢?

可以自定义协议!

网络版计算器(应用层)

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端。

约定方案(大体框架):

cpp 复制代码
struct request
{
	int x;//计算数1
	int y;//计算数2
	char op;//计算符号
}
cpp 复制代码
struct response
{
	int result;//计算结果
	int code;//表示结果是否正确,正确为0等等
}

这就是一种约定。

假设两个人在聊天,发送的消息经过这个协议会形成字符串数据,这个就叫序列化,然后通过网络发送到对面手里,拿到之后把里面的字符串数据通过协议依次拿到手中。

同理,上面的计算器结构也是一样的。

x+y的时候计算符号两边规定添加一个空格。

\n作为一个有效字符串的结束符,这样就不会导致两个字符串合在一起无法区分的问题。

我们可以添加一个报头,报头里面装字符串的长度。(不算\n)

"9"\n"100 + 200"\n

如果是多个报文就是这种格式

"9"\n"100 + 200"\n"9"\n"100 + 200"\n"9"\n"100 + 200"\n

代码实现

打印模块

cpp 复制代码
//log.hpp
#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 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);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};
Log lg;

计算协议

cpp 复制代码
//Protocol.hpp
#pragma once
#include <iostream>
#include <string>

using namespace std;

const string blank_space_sep = " ";
const string protocol_sep = "\n";//有效字符串数据的分隔符,防止很多字符串数据叠加在一起难以分辨
string Encode(string &content)//添加字符串长度报文
{
    string package = 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(string &package,string *content)
{
    size_t pos = package.find(protocol_sep);
    if(pos == string::npos) return false;
    string len_str = package.substr(0, pos);
    size_t len = stoi(len_str);
    // package = len_str + content_str + 2
    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;
}
class Request
{
public:
    Request(int data1,int data2,char oper):x(data1),y(data2),op(oper)
    {}
    Request()
    {}
public:
    bool Serialize(string *out)//序列化
    {
        // 构建报文的有效载荷
        // struct => string, "x op y"
        string s = to_string(x);
        s += blank_space_sep;
        s += op;
        s += blank_space_sep;
        s += to_string(y);

        *out = s;
        return true;
    }
    bool Deserialize(const string &in)//反序列化
    {
        size_t left = in.find(blank_space_sep);
        if (left == string::npos)
            return false;
        string part_x = in.substr(0, left);

        size_t right = in.rfind(blank_space_sep);
        if (right == string::npos)
            return false;
        string part_y = in.substr(right + 1);

        if (left + 2 != right)
            return false;
        op = in[left + 1];
        x = stoi(part_x);
        y = stoi(part_y);
        return true;
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl;
    }
public:
    int x;
    int y;
    char op;
};


class Response
{
public:
    Response(int res, int c) : result(res), code(c)
    {}
    Response()
    {}
public:
    bool Serialize(string *out)
    {
        // "result code"
        // 构建报文的有效载荷
        string s = to_string(result);
        s += blank_space_sep;
        s += to_string(code);
        *out = s;
        return true;
    }
    bool Deserialize(const std::string &in)
    {
        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 << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
    }
public:
    int result;
    int code;//0,可信,否则!0具体是几,表明对应错误的原因
};

创建套接字模块

cpp 复制代码
//Socket.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 "log.hpp"
using namespace std;
enum
{
    SocketErr = 2,
    BindErr,
    ListenErr,
};
const int backlog = 10;
class Sock
{
public:
    Sock()
    {}
    ~Sock()
    {}
    void Socket()
    {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        {
            lg(Fatal, "socker error, %s: %d", strerror(errno), errno);
            exit(SocketErr);
        }
    }
    void Bind(uint16_t port)
    {
        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(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, %s: %d", strerror(errno), errno);
            exit(BindErr);
        }
    }
    void Listen()
    {
        if (listen(sockfd_, backlog) < 0)
        {
            lg(Fatal, "listen error, %s: %d", strerror(errno), errno);
            exit(ListenErr);
        }
    }
    int Accept(string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len);
        if(newfd < 0)
        {
            lg(Warning, "accept error, %s: %d", strerror(errno), errno);
            return -1;
        }
        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
        *clientip = ipstr;
        *clientport = ntohs(peer.sin_port);

        return newfd;
    }
    bool Connect(const string &ip, const uint16_t &port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

        int n = connect(sockfd_, (struct sockaddr*)&peer, sizeof(peer));
        if(n == -1) 
        {
            std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
            return false;
        }
        return true;
    }
    void Close()
    {
        close(sockfd_);
    }
    int Fd()
    {
        return sockfd_;
    }
private:
    int sockfd_;
};

TCP服务器

cpp 复制代码
//TcpServer.hpp
#pragma once
#include <functional>
#include <string>
#include <signal.h>
#include "log.hpp"
#include "Socket.hpp"

using func_t = function<string(string &package)>;
class TcpServer
{
public:
    TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
    {}
    bool InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        lg(Info,"init server...done");
        return true;
    }
    void Start()
    {
        signal(SIGCHLD, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        while(true)
        {
            string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip,&clientport);
            if(sockfd < 0) continue;
            //提供服务
            lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
            if(fork()==0)
            {
                listensock_.Close();
                string inbuffer_stream;
                // 数据计算
                while (true)
                {
                    char buffer[1280];
                    ssize_t n = read(sockfd, buffer, sizeof(buffer));
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        inbuffer_stream += buffer;

                        lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
                        while(true)//可以处理多个报文
                        {
                            string info = callback_(inbuffer_stream);
                            if (info.empty()) break;//如果没有报文了就退出
                            write(sockfd, info.c_str(), info.size());
                        }
                    }
                    else if (n == 0)
                        break;
                    else
                        break;
                }
                exit(0);
            }
            close(sockfd);
        }
    }
    ~TcpServer()
    {}
private:
    uint16_t port_;
    Sock listensock_;
    func_t callback_;
};

服务端

cpp 复制代码
//ServerCal.hpp
#pragma once
#include <iostream>
#include "Protocol.hpp"

enum
{
    Div_Zero = 1,
    Mod_Zero,
    Other_Oper
};

class ServerCal
{
public:
    ServerCal()
    {
    }
    Response CalculatorHelper(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;
    }
    // "len"\n"10 + 20"\n
    string Calculator(string &package)
    {
        string content;
        bool r = Decode(package, &content); // "len"\n"10 + 20"\n
        if (!r)
            return "";
        // "10 + 20"
        Request req;
        r = req.Deserialize(content); // "10 + 20" ->x=10 op=+ y=20
        if (!r)
            return "";

        content = "";                          //
        Response resp = CalculatorHelper(req); // result=30 code=0;

        resp.Serialize(&content);  // "30 0"
        content = Encode(content); // "len"\n"30 0"

        return content;
    }
    ~ServerCal()
    {
    }
};
cpp 复制代码
//ServerCal.cc
#include <unistd.h>
// #include "Daemon.hpp"

static void Usage(const std::string &proc)
{
    std::cout << "\nUsage: " << proc << " port\n" << std::endl; 
}

// ./servercal 8080
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t port = std::stoi(argv[1]);
    ServerCal cal;
    TcpServer *tsvp = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
    tsvp->InitServer();
    // Daemon();
    daemon(0, 0);
    tsvp->Start();

    
    // Response resp(1000, 0);
    // std::string content;
    // resp.Serialize(&content);
    // std::cout << content << std::endl;
    // std::string package = Encode(content);
    // std::cout << package;

    // content = "";
    // bool r = Decode(package, &content);
    // std::cout << content << std::endl;

    // Response temp;
    // temp.Deserialize(content);

    // std::cout << temp.result << std::endl;
    // std::cout << temp.code << std::endl;



    // Request req(12364566, 43454356, '+');
    // std::string s;
    // req.Serialize(&s);
    // s = Encode(s);
    // std::cout << s;

    // std::string content;
    // bool r = Decode(s, &content);
    // std::cout << content << std::endl;
    // Request temp;
    // temp.Deserialize(content);

    // std::cout << temp.x << std::endl;
    // std::cout << temp.op << std::endl;
    // std::cout << temp.y << std::endl;
    return 0;
}

客户端

cpp 复制代码
//ClientCal.cc
#include <iostream>
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"

static void Usage(const std::string &proc)
{
    std::cout << "\nUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./clientcal ip port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    Sock sockfd;
    sockfd.Socket();
    bool r = sockfd.Connect(serverip, serverport);
    if(!r) 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.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: " << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;
        // n = write(sockfd.Fd(), package.c_str(), package.size());
        // std::cout << "这是最新的发出去的请求: \n" << n << "\n" << package;


        char buffer[128];
        ssize_t n = read(sockfd.Fd(), 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;
}

json

JSON(JavaScript Object Notation,JavaScript对象表示法)是基于ECMAScript的一个子集设计的,是一种开放标准的文件格式和数据交换格式,它易于人阅读和编写,同时也易于机器解析和生成。JSON独立于语言设计,很多编程语言都支持JSON格式的数据交换。JSON是一种常用的数据格式,在电子数据交换中有多种用途,包括与服务器之间的Web应用程序的数据交换。其简洁和清晰的层次结构有效地提升了网络传输效率,使其成为理想的数据交换语言。其文件通常使用扩展名.json。(百度介绍)

安装json的命令:

sudo yum install epel-releaseyum install -y jq

安装之后库的名字是jsoncpp。

cpp 复制代码
#include <iostream>
#include <jsoncpp/json/json.h>

using namespace std;

int main()
{
    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["dect"] = "this is a + oper";
    Json::FastWriter w;
    string res = w.write(root);
    cout << res <<endl;
    return 0;
}

打印出来的结果是按风格序列化之后的。

cpp 复制代码
#include <iostream>
#include <jsoncpp/json/json.h>

using namespace std;

int main()
{
    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["dect"] = "this is a + oper";
    //Json::FastWriter w;
    Json::StyledWriter w;
    string res = w.write(root);
    cout << res <<endl;
    return 0;
}

这个是按风格增加可读性的序列化。

cpp 复制代码
#include <iostream>
#include <jsoncpp/json/json.h>
#include <unistd.h>
using namespace std;

int main()
{
    Json::Value root;//这里面能放任何类型的数据包括Json
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["dect"] = "this is a + oper";
    Json::FastWriter w;
    //Json::StyledWriter w;
    string res = w.write(root);
    cout << res <<endl;
    Json::Value v;
    Json::Reader r;
    r.parse(res,v);//第一个参数是要反序列化的字符串,第二个参数是要放在哪里,第三个默认缺省
    int x = v["x"].asInt();//提取x的整数
    int y = v["y"].asInt();//提取y的整数
    char op = v["op"].asInt();//提取op的字符
    string dect = v["dect"].asString();//提取dect的字符串
    cout << x <<endl;
    cout << y <<endl;
    cout << op <<endl;
    cout << dect <<endl;

    return 0;
}

这是反序列化。

总结

上述代码说明,OSI七层中的会话层(决定什么时候连接,连接多久,连接什么时候断开),表示层(接收图像文字等信息),应用层(针对每个应用的协议),其实没办法在内核实现,刚刚实现的网络版本计算器说明这些都不是固定的,每个应用和用户需要是不一样的。

相关推荐
aixingkong92125 分钟前
Nvidia为例 XPU的Multi-Host、Socket Direct 、GPUDirect、RDMA 技术点分析
服务器·网络·硬件架构·硬件工程
雯宝3 小时前
mnt_init 函数
linux
律宏阔8 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔9 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴9 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千9 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei379 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei379 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei379 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh9 小时前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker