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

相关推荐
CaliXz27 分钟前
野草云防火墙风险分析及 Docker 使用注意事项
运维·docker·容器
计算机学无涯28 分钟前
Docker 命令简写配置
运维·docker·容器
the_nov38 分钟前
19.TCP相关实验
linux·服务器·网络·c++·tcp/ip
kk小源1 小时前
Docker常用操作教程
运维·docker·容器
Y淑滢潇潇2 小时前
RHCSA Linux 系统创建文件
linux·运维·服务器
University of Feriburg2 小时前
4-c语言中的数据类型
linux·c语言·笔记·学习·嵌入式实时数据库·嵌入式软件
XYN612 小时前
【嵌入式学习3】基于python的tcp客户端、服务器
服务器·开发语言·网络·笔记·python·学习·tcp/ip
the_nov2 小时前
20.IP协议
linux·服务器·网络·c++·tcp/ip
奔跑的废柴2 小时前
Jenkins学习(B站教程)
运维·学习·jenkins
Tee xm2 小时前
清晰易懂的 Jenkins 安装与核心使用教程
linux·windows·macos·ci/cd·jenkins