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

目录

1.应用层

2.再谈"协议"

3.网络版计算器

4.序列化和反序列化

[5.重新理解read、write、recv、send和tcp为什 么支持全双工](#5.重新理解read、write、recv、send和tcp为什 么支持全双工)

6.代码实现

代码结构

Socket封装

定制协议

关于流式数据的处理

7.Jsoncpp

特性

安装

序列化

Json::Value

使用Json::Value的toStyledString方法

使用Json::StreamWriter

使用Json::FastWriter

反序列化

使用Json::Reader:


1.应用层

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

2.再谈"协议"

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

其实,协议就是双方约定好的结构化的数据

3.网络版计算器

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

4.序列化和反序列化

无论我们采用方案一,还是方案二,还是其他的方案,**只要保证,一端发送时构造的数据, 在另一端能够正确的进行解析,**就是ok的.这种约定,就是应用层协议 但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。

我们采用方案2,我们也要体现协议定制的细节

• 我们要引入序列化和反序列化,手写序列化和反序列化或者通过jsoncpp 库实现

• 我们要对socket进行字节流的读取处理

5.重新理解read、write、recv、send和tcp为什 么支持全双工

所以:

• 在任何一台主机上,TCP连接既有发送缓冲区,又有接受缓冲区,所以,在内核 中,可以在发消息的同时,也可以收消息,即全双工

• 这就是为什么一个tcpsockfd读写都是它的原因

•实际数据什么时候发,发多少,出错了怎么办,由TCP控制,所以TCP叫做传输控制协议

6.代码实现

代码结构

Socket封装

tcpServer.hpp(基于之前tcp套接字修改,采用多进程)

cpp 复制代码
#include <iostream>
#include "log.hpp"
#include "Protocol.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h>  // 提供 inet
#include <functional>
#include <sys/types.h>
#include <sys/wait.h>
#include <functional>

#define MAX_NUM 1024
namespace Server
{
    typedef function<bool(const Request&,Reponse&)> func_t;
    static uint16_t deport = 8080; // 默认端口
    static int gbacklog = 5;
    enum
    {
        SOCkET_ERR = 0,
        BIND_ERR = 1,
        LISTEN_ERR = 2
    }; // 错误值
    
        void hander(int sock, func_t func)
        {
            while (1)
            {
                // 1.接受接受客户端信息(确保信息接受的完整性)
                std::string requ_text, requ_str;
                if (!recvPackage(sock, &requ_text))
                    return;
                std::cout<<"server requ反序列化前"<<requ_text<<std::endl;
                if (!delength(requ_text, &requ_str))
                    return;
                std::cout<<"server requ简报文头后"<<requ_str<<std::endl;
                
                // 2.服务端进行反序列化
                Request requ;
                requ.deserialization(requ_str);

                // 3.处理任务
                Reponse repo;
                func(requ, repo);
                // 4.进行序列化
                std::string reponse;
                repo.serialization(&reponse);
                // 返回客服端
                std::string send_str = enlength(reponse);
                send(sock, send_str.c_str(), send_str.size(), 0);
            }
        }
    class tcpServer
    {
    public:
        tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
        {
        }
        void init()
        {
            // 1.创建套接字
            _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensockfd < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCkET_ERR);
            }
            logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);

            // 端口复用:解决程序重启时端口TIME_WAIT问题
            int opt = 1;
            setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

            // 2.bind绑定port与ip(ANY)
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = htonl(INADDR_ANY);
            local.sin_port = htons(_port);
            int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
            if (n < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");
            // 3.设置监听状态
            if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
            // 4.在start中
        }
        void start(func_t func)
        {
            // 4.获取新链接
            while (1)
            {
                struct sockaddr_in peer; // 用于获取客户端ip和port
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accpet error,again");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success,sock:%d",sock);

                // 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)
                int k = fork();
                if (k < 0)
                {
                    logMessage(FATAL, "fork error");
                    exit(0);
                }
                if(k == 0)
                {
                    logMessage(NORMAL, "fork success");
                    hander(sock,func);
                }
                close(sock);
                // 非阻塞回收已退出的子进程(写在父进程中)
                pid_t ret_pid;
                // 循环回收所有已退出的子进程,避免僵尸进程堆积
                while ((ret_pid = waitpid(-1, NULL, WNOHANG)) > 0) {
                logMessage(NORMAL, "child process %d exited, recycled", ret_pid);
    }
            }
        }
        ~tcpServer() {}

    private:
        int _listensockfd;
        // 这里的sock不同于udp的sock,这个sock只负责监听
        uint16_t _port;
    };
}

tcpClient.hpp

cpp 复制代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h>  // 提供 inet
#include "Protocol.hpp"
#include <ctype.h>

namespace Client
{
    class tcpClient
    {
    public:
        tcpClient(std::string ip,uint16_t port):_sockfd(-1),_ip(ip),_port(port)
        {}
        void init()
        {
            //1.创建socket
            _sockfd = socket(AF_INET,SOCK_STREAM,0);
            if(_sockfd < 0)
            {
                std::cerr<<"create socket error"<<std::endl;
                exit(0);
            }
            //2.bind(客户端为系统隐式绑定)
        }
        void run()
        {
            //3.发起链接
            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_ip.c_str());
            server.sin_port = htons(_port);
            socklen_t len = sizeof(server);
            if (connect(_sockfd, (struct sockaddr *)&server, len) != 0)
            {
                std::cerr << "connect socket error" << std::endl;
                exit(0);
            }
            else
            {
                while (1)
                {
                    std::string buffer;
                    std::cout << "mycal<<";
                    getline(std::cin,buffer);
                    Request requ = ParseLine(buffer);
                    //序列化
                    std::string requ_text,requ_str;
                    requ.serialization(&requ_text);
                    requ_str = enlength(requ_text);
                    //发送给服务端
                    send(_sockfd,requ_str.c_str(),requ_str.size(),0);

                    //接受服务器的返回信息
                    std::string repo_text,repo_str;
                    if(!recvPackage(_sockfd,&repo_text)) return;
                    if(!delength(repo_text,&repo_str))return;
                    Reponse repo;
                    repo.deserialization(repo_str);
                    std::cout<<"exitcode:"<< repo.exit_code<<"\nresult:"<<repo.result<<std::endl;
                }
            }
        }
        Request ParseLine(const std::string line)
        {
            int status = 0;
            int i = 0;
            int len = line.size();
            std::string left,right;
            char op;
            while (i < len)
            {
                switch (status)
                {
                case 0:
                    if (!isdigit(line[i]))
                    {
                        op = line[i++];
                        status = 1;
                    }
                    else
                        left.push_back(line[i++]);
                    break;
                case 1:
                    status++;
                    break;
                case 2:
                    right.push_back(line[i++]);
                    break;
                default:
                    break;
                }
            }

            try
            {
                int left_val = std::stoi(left);
                int right_val = std::stoi(right);
                return Request(left_val, op, right_val);
            }
            catch (const std::invalid_argument &e)
            {
                std::cerr << "[ERROR] [ParseLine] stoi 转换失败: 非数字字符无法转换 | left=" << left << " | right=" << right << " | 错误信息: " << e.what() << std::endl;
                exit(1);
            }
            catch (const std::out_of_range &e)
            {
                std::cerr << "[ERROR] [ParseLine] stoi 转换失败: 数值超出int范围 | left=" << left << " | right=" << right << " | 错误信息: " << e.what() << std::endl;
                exit(1);
            }
        }
        ~tcpClient()
        {
            if(_sockfd >= 0) close(_sockfd);
        }
    private:
        int _sockfd;
        std::string _ip;
        uint16_t _port;
    };
}

定制协议

Protocol.hpp

自定义定制协议类型为:总长度 --x 操作符 y--

首先客服端接受信息进行序列化,发送给客户端。客服端反序列化并将返回结果序列化返回给客户端,客户端反序列化得到结果。

程序采用条件编译另一个我们通过jsoncpp库来实现序列化和反序列化

cpp 复制代码
#include <jsoncpp/json/json.h>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>

#define SP " "
#define SP_L strlen(SP)
#define CH "--"
#define CH_L strlen(CH)

//协议类型:--x op y--
class Request
{
public:
    Request()
    {}
    Request(int x, char op, int y) : _x(x), _op(op), _y(y)
    {}
    bool serialization(std::string *requ)
    {
#ifdef MYSELF
        *requ = "";
        *requ += CH;
        *requ += std::to_string(_x);
        *requ += SP;
        *requ += _op;
        *requ += SP;
        *requ += std::to_string(_y);
        *requ += CH;
#else
        Json::Value root;
        root["first"] = _x;
        root["second"] = _y;
        root["oper"] = _op;

        Json::FastWriter writer;
        *requ = writer.write(root);
#endif
        return true;
    }
    bool deserialization(const std::string &text)
    {
#ifdef MYSELF
        //!!!substr(起始位置,剪切长度)

        // 边界判断1:字符串长度至少要满足 --1 + 1-- 最小格式(比如--1 + 2--)
        if (text.size() < CH_L * 2 + 5) return false;
        // 边界判断2:字符串必须以 -- 开头和结尾,否则是非法协议
        if (text.substr(0, CH_L) != CH || text.substr(text.size() - CH_L) != CH) return false;
        int len = text.size();
        auto pos = text.find(SP);
        // 边界判断3:没找到空格/空格位置非法,返回失败
        if (pos == std::string::npos || pos <= CH_L) return false;

        _x = std::stoi(text.substr(CH_L,pos-CH_L));
        _op = text[pos + 1];
        auto rpos = text.rfind(SP);
        // 边界判断4:没找到最后一个空格/位置非法,返回失败
        if (rpos == std::string::npos || rpos >= len - CH_L - SP_L) return false;
        _y = std::stoi(text.substr(rpos+SP_L,len-rpos-SP_L-CH_L));
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(text,root,true);
        _x = root["first"].asInt();
        _y = root["second"].asInt();
        _op = root["oper"].asInt();
#endif
        return true;
    }
public:
    int _x;
    int _y;
    char _op;
};
//协议类型:exitcode result
class Reponse
{
public:
    Reponse():exit_code(0),result(0)
    {}
    Reponse(int exit_code,int result):exit_code(exit_code),result(result)
    {}
    bool serialization(std::string *repo)
    {
#ifdef MYSELF
        *repo = "";
        std::string exit = std::to_string(exit_code);
        std::string res = std::to_string(result);
        
        *repo = exit;
        *repo += SP;
        *repo += res;
#else
        Json::Value root;
        root["exit_code"] = exit_code;
        root["result"] = result;

        Json::FastWriter writer;
        *repo = writer.write(root);
#endif
        return true;
    }
    bool deserialization(const std::string &text)
    {
#ifdef MYSELF
        int len = text.size();

        auto pos = text.find(SP);
        if(pos == std::string::npos) return false;
        exit_code = std::stoi(text.substr(0,pos));
        result = std::stoi(text.substr(pos+SP_L));
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(text,root,true);
        exit_code = root["exit_code"].asInt();
        result = root["result"].asInt();
#endif
        return true;
    }
public:
    int exit_code;//0为成功
    int result;
};
//格式:长度 --x op y--
std::string enlength(std::string &text)
{
    std::string send_str = std::to_string(text.size());
    send_str+=SP;
    send_str+=text;
    return send_str;
}
//去除长度:--x op y--
bool delength(const std::string &package,std::string *text)
{
    auto pos = package.find(SP);
    if(pos == std::string::npos) return false;
    int len;
    try
    {
        len = std::stoi(package.substr(0, pos));
    }
    catch (const std::invalid_argument &e)
    {
        std::cerr << "[ERROR] [delength] stoi 转换失败: 非数字字符无法转换 | 待转换字符串: " << package.substr(0, pos) << " | 错误信息: " << e.what() << std::endl;
        return false;
    }
    catch (const std::out_of_range &e)
    {
        std::cerr << "[ERROR] [delength] stoi 转换失败: 数值超出int范围 | 待转换字符串: " << package.substr(0, pos) << " | 错误信息: " << e.what() << std::endl;
        return false;
    }
    *text = package.substr(pos+SP_L,len);
    return true;
} 

//格式:长度 --x op y--
bool recvPackage(int sock,std::string *text)
{
    static std::string inbuffer;//设置为静态,方便追加字符
    char buffer[1024];
    while (1)
    {
        memset(buffer, 0, sizeof(buffer));//!!!!!!!!!清楚缓冲区
        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            inbuffer += buffer;
            // 分析是否为一个完整报文
            auto pos = inbuffer.find(SP);
            if (pos == std::string::npos)
                continue;
            std::string text_len_string = inbuffer.substr(0, pos);
            int content_len;
            try
            {
                content_len = std::stoi(text_len_string);
            }
            catch (const std::invalid_argument &e)
            {
                std::cerr << "[ERROR] [recvPackage] stoi 转换失败: 非数字字符无法转换 | 待转换字符串: " << text_len_string << " | 错误信息: " << e.what() << std::endl;
                return false;
            }
            catch (const std::out_of_range &e)
            {
                std::cerr << "[ERROR] [recvPackage] stoi 转换失败: 数值超出int范围 | 待转换字符串: " << text_len_string << " | 错误信息: " << e.what() << std::endl;
                return false;
            }
            int total_len = text_len_string.size() + SP_L + content_len;
            if (inbuffer.size() < total_len)
                continue;

            // 此时至少有一个完整报文
            *text = inbuffer.substr(0, total_len);
            inbuffer.erase(0, total_len);
            break;
        }
        else
        {
            return false;
        }
    }
    return true;
}

makefile(日志log.hpp不在展示,前边文章有)

cpp 复制代码
.PHONY:all
all:tcpServer tcpClient

tcpServer:tcpServer.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp

tcpClient:tcpClient.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp

.PHONY:clean
clean:
	rm -f tcpServer tcpClient

关于流式数据的处理

你如何保证你每次读取就能读完请求缓冲区的所有内容?

•你怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢?

•处理TCP缓冲区中的数据,一定要保证正确处理请求

这里的解决方法可以看recvPackage()的实现方法看如何解决

7.Jsoncpp

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

特性

安装

序列化

Json::Value

Json::Value 是 Jsoncpp 库中的一个重要类,用于表示和操作JSON数据结构。以 下是一些常用的Json::Value操作列表:

  1. 构造函数 • Json::Value():默认构造函数,创建一个空的Json::Value对象。

• J son::Value(ValueType type, bool allocated = false):根据给定的 ValueType(如 nullValue, intValue, stringValue 等)创建一个 Json::Value 对 象。

  1. 访问元素

• J son::Value& operator[](const char* key):通过键(字符串)访问对象 中的元素。如果键不存在,则创建一个新的元素。

• J son::Value& operator[](const std::string& key):同上,但使用 std::string 类型的键。

• J son::Value& operator[](ArrayIndex index):通过索引访问数组中的元 素。如果索引超出范围,则创建一个新的元素

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件 中。Jsoncpp提供了多种方式进行序列化:

使用Json::Value的toStyledString方法

优点:将Json::Value对象直接转换为格式化的JSON字符串。

cpp 复制代码
#include<iostream>
#include<string>
#include<jsoncpp/json/json.h>
intmain()
{
Json::Value root;
root["name"]="joe";
root["sex"]="男";
std::strings=root.toStyledString();
std::cout<<s<<std::endl;
return0;
}

使用Json::StreamWriter

优点:提供了更多的定制选项,如缩进、换行符等

cpp 复制代码
#include<iostream>
#include<string>
#include<sstream>
#include<memory>
#include<jsoncpp/json/json.h>
intmain()
{
Json::Value root;
root["name"]="joe";
root["sex"]="男";
Json::StreamWriterBuilderwbuilder;//StreamWriter的
工厂
std::unique_ptr<Json::StreamWriter>
writer(wbuilder.newStreamWriter());
std::stringstreamss;
writer->write(root,&ss);
std::cout<<ss.str()<<std::endl;
return0;
}

使用Json::FastWriter

优点:比StyledWriter更快,因为它不添加额外的空格和换行符。

cpp 复制代码
#include<iostream>
#include<string>
#include<sstream>
#include<memory>
#include<jsoncpp/json/json.h>
intmain()
{
Json::Value root;
root["name"]="joe";
root["sex"]="男";
Json::FastWriterwriter;
std::strings=writer.write(root);
std::cout<<s<<std::endl;
return0;
}

反序列化

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp提供 了以下方法进行反序列化:

使用Json::Reader:

优点:提供详细的错误信息和位置,方便调试。

cpp 复制代码
#include <iostream>
#include <string>
#include "jsoncpp/json.h"
int main () 
{
// 对应序列化生成的 JSON 字符串
std::string jsonStr = "{"name":"joe","sex":" 男 "}";
Json::Value root;Json::Reader reader;
reader.parse (jsonStr, root);
std::string name = root ["name"].asString ();
std::string sex = root ["sex"].asString ();
std::cout << "name:" << name << std::endl;
std::cout << "sex:" << sex << std::endl;
return 0;
}
相关推荐
晚风吹长发2 小时前
初步了解Linux中的线程概率及线程控制
linux·运维·服务器·开发语言·c++·centos·线程
Art&Code2 小时前
M系列Mac保姆级教程:Clawdbot安装+API配置,30分钟解锁AI自动化!
运维·macos·自动化
科技块儿2 小时前
如何定期向监管部门报送IP属地统计报表?
网络·网络协议·tcp/ip
win x2 小时前
UDP Socket
网络·网络协议·udp
玉梅小洋2 小时前
GitHub SSH配置教程
运维·ssh·github
等什么君!2 小时前
Docker 数据卷:MySQL 数据同步实战
运维·docker·容器
瘾大侠2 小时前
HTB赛季10 - Facts
网络·安全·web安全·网络安全
码云数智-大飞2 小时前
零拷贝 IPC:用内存映射文件打造 .NET 高性能进程间通信队列
java·开发语言·网络
礼拜天没时间.2 小时前
《Docker实战入门与部署指南:从核心概念到网络与数据管理》:环境准备与Docker安装
运维·网络·docker·容器·centos