谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事

news2024/10/7 8:23:16

linux【网络编程】之协议定制、序列化与反序列化

  • 一、序列化与反序列化
  • 二、应用层协议如何定制
  • 三、网络通信中数据流动的本质
  • 四、网络版计算器编写
    • 4.1 业务流程
    • 4.2 核心代码

一、序列化与反序列化

由于socket api的接口,在读写数据的时候是以字符串的方式发送接收的,如果需要传输结构化的数据,就需要制定一个协议
在这里插入图片描述
结构化数据在发送到网络中之前需要完成序列化
接收方收到的是序列字节流,需要完成反序列化才能使用(如ChatInfo._name)

二、应用层协议如何定制

当我们进行网络通信的的时候,一端发送时构造的数据, 在另一端能够正确的进行解析(完整的读到一条报文), 就是可行的的. 这种约定, 就是 应用层协议

如何保证读到的消息是一个完整的请求
TCP是面向字节流的,无法直接读取,需要明确报文和报文的边界,常见的方法有.定长:固定报文长度、特殊符号:在报文前面加上一个字段、自描述

三、网络通信中数据流动的本质

在这里插入图片描述
我们调用的所有的发送函数(read),不是把数据发送到网络中,发送函数的本质是拷贝函数(将数据从应用层缓冲区拷贝到发送缓冲区)

  1. Client->Server:tcp发送的本质,其实就是将数据从Client的发送缓冲区拷贝到Server的接收缓冲区
  2. 反过来Server->CLient:其实就是将数据从Server的发送缓冲区拷贝到
    Client的接收缓冲区
  3. 这也说明了网络编程(套接字编程)是全双工的

四、网络版计算器编写

有了前面的知识,下面实现一个服务器版的计算器. 我们需要客户端把要计算的两个数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端

4.1 业务流程

在完成服务器与客户端正常通信的基础上完成一次请求与响应的流程
客户端:

  1. 从键盘读取数据并调用ParseLine()函数将输入的数据转换成类似“123+123"的格式
  2. 序列化字符串,将结构化的数据转化成用于网络通信的一个大字符串,调用请求类的序列化函数enLength()
  3. 添加报头:通过协议定制的规则,将"x op y"---->“content_len”\r\n"x op y"\r\n
  4. 向服务端发送已经构建好的报文
  5. 阻塞读取服务端处理后的响应数据
    等待服务器发送响应报文
  6. 读取到一个完整的报文
  7. 调用协议方法去掉报头并将结果输出到text里面
  8. 对收到的响应正文序列化填充到响应类对象的成员变量里
  9. 通过正常调用,访问处理后的结果
  10. 发送新的请求

服务端:

  1. 创建子进程去执行任务
  2. 死循环式的读取来自客户端的报文,调用recvRequest,读取一个完整的请求放入输出型参数text里面
  3. 根据协议定制,调用deLength()去掉报文的报头,得到有效数据req_str
  4. 将来自网络的字符串通过调用请求类的反序列化转化成结构化的数据,请求类对象成员完成赋值
  5. 通过回调函数(一个输入型参数(请求类对象),一个输出型参数(响应类对象))传递两个类对象,将计算结果赋值给响应类的成员变量
  6. 开始将处理结果返回给客户端,调用响应类中的序列化方法将结构化数据转换成一个大字符串
  7. 对大字符串添加报头构建成一个完整的报文,发送给客户端
  8. 服务端等待新的请求…

4.2 核心代码

Protocol.hpp包含了协议定制函数、请求响应序列化与反序列化函数、完整报文获取函数

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h>

using namespace std;

#define SEP " "             // 分隔符
#define SEP_LEN strlen(SEP) // 分隔符长度,不能用sizeof

#define LINE_SEP "\r\n"

#define LINE_SEP_LINE strlen(LINE_SEP)

enum
{
    OK = 0,
    DIV_ZERO,
    MOD_ZERO,
    OP_ERROR
};

// 协议定制:给报文段加一个特殊字段:有效载荷的长度

//                报头            有效载荷
// 
//"exitcode result"---->"content_len"\r\n"exitcode result"\r\n----
std::string enlength(const std::string &text)
{



    // text就是"x op y"
    string send_string = std::to_string(text.size()); // content_len
    send_string += LINE_SEP;
    send_string += text;
    send_string += LINE_SEP;

    return send_string;
}

// 去掉报头,提取有效载荷
//"content_len"\r\n"exitcode result"\r\n---->exitcode result
bool delength(const std::string &package, std::string *text)
{


    auto pos = package.find(LINE_SEP);
    if (pos == string::npos)
        return false;
    // 提取报头字符串
    string text_len_string = package.substr(0, pos);
    // 将报头信息转化成字符串
    int text_len = std::stoi(text_len_string);
    // 提取有效载荷
    *text = package.substr(pos + LINE_SEP_LINE, text_len);

    return true;
}

// 请求
class Request
{
public:
    Request()
        : x_(0), y_(0), op_(0)
    {
    }
    Request(int x, int y, char op)
        : x_(x), y_(y), op_(op)
    {
    }
    // 序列化
    bool serialize(std::string *out)
    {
#ifdef MYSELF        
        // 将结构化数据转化成-->"x op y"

        out->clear();
        std::string x_string = std::to_string(x_);
        std::string y_string = std::to_string(y_);
        *out = x_string;
        *out += SEP;
        *out += op_;
        *out += SEP;
        *out += y_string;
#else
        Json::Value root;//定义一个万能对象
        root["first"]=x_;
        root["second"]=y_;
        root["oper"]=op_;

        Json::FastWriter writer;
        *out=writer.write(root);

#endif        
        return true;
    }

    // 反序列化
    bool deserialize(const std::string &in)
    {
 #ifdef MYSELF         
        //"x op yyy";
        auto left = in.find(SEP);
        auto right = in.rfind(SEP);

        if (left == std::string::npos || right == std::string::npos)
            return false;
        if (left == right)
            return false;

        // 截取子串
        if (right - (left + SEP_LEN) != 1)
            return false;

        std::string x_string = in.substr(0, left);         // 定位到x
        std::string y_string = in.substr(right + SEP_LEN); // 定位到yyyy

        if (x_string.empty())
            return false;
        if (y_string.empty())
            return false;

        x_ = std::stoi(x_string);
        y_ = std::stoi(y_string);
        op_ = in[left + SEP_LEN]; // 截取op
#else
        Json::Value root;
        Json::Reader reader;
        reader.parse(in,root);//将解析出来的值放进root里面
        x_=root["first"].asInt();//将val转化成整数
        y_=root["second"].asInt();
        op_=root["oper"].asInt();
#endif
        return true;
    }

public:
    int x_;
    int y_;
    char op_;
};

// 响应
class Response
{
public:
    Response()
        : exitcode_(0), result_(0)
    {
    }
    Response(int exitcode, int result)
        : exitcode_(exitcode), result_(result)
    {
    }

    // 序列化
    bool serialize(std::string *out)
    {
#ifdef MYSELF
        // 清空字符串
        out->clear();
        // 将退出码和结果转换成字符串
        string ec_string = std::to_string(exitcode_);
        string res_string = std::to_string(result_);

        // 合并字符
        *out = ec_string;
        *out += SEP;
        *out += res_string;
#else
        Json::Value root;
        root["exitcode"]=exitcode_;
        root["result"]=result_;
        Json::FastWriter writer;
        *out=writer.write(root);
        

#endif
        return true;
    }

    // 反序列化
    bool deserialize(const std::string &in)
    {

#ifdef MYSELF        
        //"exitcode result"
        auto mid = in.find(SEP);
        if (mid == std::string::npos)
            return false;
        // 截取字符串
        string ec_string = in.substr(0, mid);
        string res_string = in.substr(mid + SEP_LEN);

        if (ec_string.empty() || res_string.empty())
            return false;

        // 写入退出码和结果
        exitcode_ = std::stoi(ec_string);
        result_ = std::stoi(res_string);
#else
        Json::Reader reader;
        Json::Value root;
        reader.parse(in,root);
        exitcode_=root["exitcode"].asInt();
        result_=root["result"].asInt();
#endif
        return true;
    }

public:
    int exitcode_; // 0成功,!0错误
    int result_;   // 计算结果
};

// 读取一个完整的请求放入text里面
//"content_len"\r\n"x op y"\r\n"content_len"\r\n"x op y"\r\n
bool recvRequest(int sock, std::string &inbuffer, string *text)
{
    char buffer[1024];
    while (true)
    {
        ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer += buffer;

            // 边读边处理
            auto pos = inbuffer.find(LINE_SEP);
            // 如果没有读到\r\n,接着去读
            if (pos == string::npos)
                continue;
            // 走到这已经读到了content_len,知道了有效载荷长度

            string text_len_string = inbuffer.substr(0, pos); // 报头
            int text_len = std::stoi(text_len_string);        // 正文长度
            int total_len = text_len_string.size() + 2 * LINE_SEP_LINE + text_len;
            std::cout << "处理前#inbuffer: \n"
                      << inbuffer << endl;
            if (inbuffer.size() < total_len)
            {
                std::cout << "你输入的消息,没有严格遵守我们的协议,正在等待后续的内容, continue" << std::endl;
                continue; // 没有读到一个完整的报文
            }
            // 至少有一个报文
            *text = inbuffer.substr(0, total_len);
            inbuffer.erase(0, total_len);
            std::cout << "处理后#inbuffer: \n"
                      << inbuffer << endl;

            break;
        }
        else
            return false;
    }

    return true;
}

服务端响应流程

void handlerEnter(int sock, func_t func)
    {
        string inuffer;//将所有信息写入到inbuffer
        while(true)
        {
            // 1.读取:"content_len"\r\n"x op y"\r\n
            // 1.1 保证读到的消息是【一个完整】的请求
            std::string req_text; // 输出型参数,整个报文
            if (!recvRequest(sock, inuffer,&req_text))
                return;
            std::cout<<"带报头的请求:\n"<<req_text<<endl;    
            // 1.2 去报头,只要正文
            std::string req_str; // 正文部分
            if (!delength(req_text, &req_str))
                return;
            std::cout<<"去掉报头后的正文:\n"<<req_str<<endl; 

            // 2.反序列化
            // 2.1 得到一个结构化对象,对象中的成员已经被填充
            Request req;
            if (!req.deserialize(req_str))
                return;

            // 3.处理数据---------业务逻辑
            // 3.1 得到一个结构化的响应,resp成员已被填充
            Response resp;
            func(req, resp); // 回调

            // 4.对响应Response,序列化
            // 4.1 得到一个字符串
            std::string resp_str;
            resp.serialize(&resp_str); // 输出型参数,将序列化结果写入resp_str
            std::cout<<"计算完成,序列化响应: "<<resp_str<<endl;

            // 5.然后发送响应
            // 5.1添加协议报头,构建成一个完整的报文
            std::string send_string = enlength(resp_str);
            std::cout<<"构建带报头的响应正文: \n"<<send_string<<endl;

            // 发送
            send(sock, send_string.c_str(), send_string.size(), 0); // 有问题
            std::cout<<"发送响应报文成功: \n"<<endl;
        }
    }

客户端请求流程

		void run()
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(clientport_);
            server.sin_addr.s_addr = inet_addr(clientip_.c_str());

            // 发起链接
            if (connect(sockfd_, (struct sockaddr *)&server, sizeof(server)) != 0)
            {
                std::cerr << "connect create error" << endl;
            }
            else
            {
                string msg;
                string inbuffer;
                while (true)
                {
                    cout << "mycal>>> ";
                    std::getline(std::cin, msg);
                    Request req = ParseLine(msg); // 从键盘提取字符串

                    string content;
                    // 序列化结构数据
                    req.serialize(&content);
                    // 添加报头
                    string send_string = enlength(content);
                    // 发送数据
                    send(sockfd_, send_string.c_str(), send_string.size(), 0);
                    

                    // 接收响应报文
                    
                    string package, text;
                    if (!recvRequest(sockfd_,inbuffer,&package))
                        continue;
                    
                    //去掉报头,获取正文放在text里面   
                    if(!delength(package,&text)) continue;
                    

                    //将收到的响应正文反序列化
                    Response resp;
                    resp.deserialize(text);
                    std::cout << "exitCode: " << resp.exitcode_ << std::endl;
                    std::cout << "result: " << resp.result_ << std::endl;     
                }
            }
        }

正常输入输出显示如下图
在这里插入图片描述

以上只是提供了几个核心的代码块,完整版代码可以去我的Gitee,代码注释详细,希望对你有所帮助

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/569344.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电子科技大学编译原理复习笔记(三):控制结构

目录 前言 重点一览 语句级控制结构 单元级控制结构 四种单元级控制结构 本章小结 前言 本复习笔记基于张老师的课堂PPT&#xff0c;供自己期末复习与学弟学妹参考用。 重点一览 语句级控制结构 定义&#xff1a;用来构造各种语句执行顺序的机制 传统三种语句级控制结…

Hyperledger Fabric explorer区块链浏览器搭建

https://github.com/hyperledger-labs/blockchain-explorer 官方浏览器的github地址 根据文档&#xff0c;采用docker容器的方法搭建explorer。 首先创建explorer的项目&#xff0c; mkdir explorer根据官方提供的文件&#xff0c;需要创建的目录结构如下&#xff1a; 这是官…

【计算机网络复习之路】网络层(谢希仁第八版)万字详解 主打基础

专栏&#xff1a;计算机网络复习之路 目录&#xff1a; 一、网络层的几个重要概念 1.1 网络层提供的两种服务 1.2 网络层的两个层面 二、网际协议 IP 2.1 虚拟互连网络 2.2 IP地址 2.2.1 IP地址及其表示方法 2.2.2 分类的IP地址 2.2.3 无分类编址CIDR &#xff08…

【2023 · CANN训练营第一季】应用开发(初级)第四章——模型推理

AscendCL运行资源管理 申请运行管理资源时&#xff0c;需按顺序依次申请: Device、Context、Stream&#xff0c;然后根据实际需求调用aclrtGetRunMode接口获取软件栈的运行模型(当同一个应用既支持在Host运行&#xff0c;也支持在Device运行时&#xff0c;在编程时需要就需要根…

k8s进阶5——AppArmor、Seccomp、ImagePolicyWebhook

文章目录 一、AppArmor限制容器对资源访问1.1 实现步骤1.1.1 定义策略1.1.2 加载策略1.1.3 引用策略 2.2 案例 二、Seccomp 限制容器进程系统调用案例一&#xff1a;使用自定义策略案例二&#xff1a;使用容器运行时默认策略 三、动态准入控制Webhook3.1 ImagePolicyWebhook控制…

PowerPoint输出图片分辨率设置

最近想用ppt画几张图&#xff0c;但是输出的分辨率有点不够意思&#xff0c;然后就想着改一下输出分辨率&#xff0c;这里记录一下方便以后查阅。 PowerPoint输出图片分辨率设置

PS-调色

图片的储存格式 JPEG格式&#xff1a;日常作图存储格式、只有一个图层、通用图片格式、内存小画质高 RAW格式&#xff1a;CR2、CR3佳能、NEF尼康、ARW索尼、IIQ哈苏、RAF富士、RW2松下 PNG格式&#xff1a;图片透明格式、用于抠出来的素材用这个格式保存可以是透明底没有背景 …

Linux——操作系统详解

目录 一.操作系统的含义 1.操作系统是什么&#xff1f; 2.那么操作系统为什么要对软硬件资源进行管理呢&#xff1f;这样做的好处在哪里&#xff1f; 3.操作系统又是怎么进行管理的&#xff1f; 如何理解“先描述&#xff0c;再组织”&#xff1f; 二.总结&#xff1a; …

conda环境安装使用教程

conda&#xff0c;anaconda&#xff0c;miniconda傻傻分得清楚 Conda是一个开源的包管理系统和环境管理系统&#xff0c;可以用于安装、管理和卸载软件包以及创建和管理虚拟环境。Anaconda是一个基于Python的数据科学平台&#xff0c;包括Python解释器、Conda包管理器、Jupyte…

Linux:为xfs文件系统卷 设置磁盘配额

首先准备一个xfs文件系统的 卷 || 分区 可以是逻辑卷 &#xff0c;也可以是普通卷&#xff0c;等等……但是他们的文件格式都要是xfs格式 我这里选择的是逻辑卷&#xff0c;普通卷也是一样的道理 开始前要有两个软件包需要安装 如果已安装直接看下一步 Linux&#xff1a;rpm…

一个人的旅行

说一下&#xff0c;两次的旅行。一次是三月底四月初&#xff0c;一次是四月底五月初。 我的第一站&#xff0c;帝都&#xff01;&#xff01;&#xff01; 31号晚上八点半的高铁去北京&#xff0c;到达北京已经快十二点了。武局的G528次列车。 来到北京后&#xff0c;这是我第…

Android开发环境搭建[Java1.7+eclipse+sdk4.0](某高校物联网工程专业必看!)

Android开发环境搭建[Java1.7eclipsesdk4.0]&#xff08;某高校物联网工程专业必看&#xff01;&#xff09; 0. 前言1. 资料拷贝2. 配置环境变量2.1新建 Java_Home2.2 编辑Path情况1情况2 2.3 新建sdk环境变量 3. 验证安装3.1 验证java安装情况3.2 验证eclipse安装 4. 导入已有…

《OrangeS一个操作系统的实现》中printf无法打印数字问题

【问题现象】 《OrangeS一个操作系统的实现》 第9章 a目录下的代码编译运行后&#xff0c;所有printf打印数字的地方都有问题&#xff0c;如下图&#xff1a; HD size 始终为 0MB。 【问题分析】 通过断点&#xff0c;发现printf第61行&#xff1a; int printf(const char *…

【历史上的今天】4 月 28 日:人工智能理论之父出生;大众点评上线;苹果开设 iTunes 音乐商店

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 4 月 28 日&#xff0c;在 1994 年的今天&#xff0c;美国克林顿政府公布了一项价值数百万美元的计划&#xff0c;以帮助那些制造平板显示屏的美国公司维持生计…

Filter详解

Filter是什么&#xff1a; Filter表示过滤器&#xff0c;是Java Web三大组件之一&#xff08;Servlet、Filter、Listener&#xff09;。 过滤器可以把对资源的请求拦截下来&#xff0c;从而实现一些特殊的功能。 过滤器一般完成一些通用的操作&#xff0c;比如&#xff1a;权…

chatgpt赋能python:PythonSearchDialog:利用Python编写高效的搜索对话框

Python Search Dialog&#xff1a;利用Python编写高效的搜索对话框 在当今数字时代&#xff0c;大多数产品都提供搜索功能。而Python Search Dialog 则为产品提供了一个强大、高效的搜索对话框&#xff0c;使得用户能够更快速地、更准确地定位所需信息。 什么是Python Search…

实验三:熟悉常用的HBase操作

实验环境&#xff1a; (1)操作系统&#xff1a;Linux(建议 Ubuntu 16.04 或 Ubuntu 18.04)。 (2)Hadoop 版本&#xff1a;3.1.3。 (3)HBase 版本&#xff1a;2.2.2。 (4)JDK 版本&#xff1a;1.8。 (5)Java IDE: Eclipse。 实验内容与完成情况&#xff1a; (1)现有以下关系数据…

【数据挖掘与商务智能决策】第十七章 神经网络

前言 本人CSDN博客为“仿生程序员会梦见电子羊吗”&#xff0c;本文基于markdown本文书写&#xff0c;平台及软件为CSDN与Typora&#xff0c;文中图片存储地址为CSDN&#xff0c;故部分图片可能带有“CSDN仿生程序员会梦见电子羊吗”的水印&#xff0c;属于本人原创&#xff0c…

视频理解学习笔记(二):I3D and Kinetics Dataset

视频理解学习笔记&#xff08;二&#xff09;&#xff1a;I3D and Kinetics Dataset 视频理解的三个流派&#xff08;怎么处理时序&#xff09;论文概览Kinetics Dataset模型详解将2D卷积网络扩张到3D&#xff08;Inflating 2D ConvNets into 3D&#xff09;如何用预训练好的2D…

2023年最新企业网盘排行榜出炉

随着云计算技术的不断发展&#xff0c;企业日常工作中大量的资料、文档等信息需要实现集中管理&#xff0c;此时企业网盘工具就应运而生。企业网盘是一种可用于企业内部管理、团队协作及文件共享的云存储平台&#xff0c;能够极大提高企业办公效率和安全性。 一、企业网盘的帮助…