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

这篇具有很好参考价值的文章主要介绍了谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、序列化与反序列化

由于socket api的接口,在读写数据的时候是以字符串的方式发送接收的,如果需要传输结构化的数据,就需要制定一个协议
谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事
结构化数据在发送到网络中之前需要完成序列化
接收方收到的是序列字节流,需要完成反序列化才能使用(如ChatInfo._name)

二、应用层协议如何定制

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

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

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

谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事
我们调用的所有的发送函数(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;     
                }
            }
        }

正常输入输出显示如下图
谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事

以上只是提供了几个核心的代码块,完整版代码可以去我的Gitee,代码注释详细,希望对你有所帮助文章来源地址https://www.toymoban.com/news/detail-459830.html

到了这里,关于谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 「网络编程」应用层协议_ HTTPS协议学习及原理理解

    「前言」文章内容大致是应用层协议的HTTPS协议讲解,续上篇HTTP协议。 「归属专栏」网络编程 「主页链接」个人主页 「笔者」枫叶先生(fy) HTTPS(HyperText Transfer Protocol Secure) 是一种通过加密和身份验证保护网络通信安全的协议。它是基于HTTP协议的安全版本,也是工作在应用

    2024年02月16日
    浏览(53)
  • 网络编程:TCP粘包问题——各层粘包/拆包、Nagle 算法、Go实现长度字段协议解决TCP粘包、使用TCP的应用层协议设计

    1.1 TCP介绍 如上图,TCP具有面向连接、可靠、基于字节流三大特点。 字节流可以理解为一个双向的通道里流淌的数据,这个数据其实就是我们常说的二进制数据,简单来说就是一大堆 01 串。纯裸TCP收发的这些 01 串之间是没有任何边界的,你根本不知道到哪个地方才算一条完

    2024年02月04日
    浏览(42)
  • Linux系统应用编程(五)Linux网络编程(上篇)

    1.两个网络模型和常见协议 (1)OSI七层模型(物数网传会表应) 物理层、数据链路层、网络层、传输层、会话层、表示层、应用层(自下到上) (2)TCP/IP四层模型(网网传应) 网络接口层(链路层)、网络层、传输层、应用层 (3)常见网络协议所属层 2.字节序 (1)两种

    2023年04月25日
    浏览(38)
  • C++中的网络编程和安全性:实现安全的Web应用程序和网络应用程序

    作者:禅与计算机程序设计艺术 《67. C++中的网络编程和安全性:实现安全的Web应用程序和网络应用程序》 1.1. 背景介绍 随着互联网的快速发展,网络应用程序在人们的生活和工作中扮演着越来越重要的角色,网络编程和安全性也成为了现代应用程序的重要组成部分。在网络

    2024年02月16日
    浏览(52)
  • 初学记录【linux应用】 TCP/UDP 网络编程 C语言

    以下内容分别为TCP 与 UDP编程,内容有相似或者重合部分,可根据流程 相互对照学习,都已经附上源码 。 **1.** socket 创建 tcp套接字 (监听的套接字) 2、IPv4套接字地址结构 #include netinet/in.h struct in_addr: 如果使用 Internet 所以 sin_family 一般为 AF_INET。 ⚫ sin_addr 设置为 INADDR_AN

    2024年02月03日
    浏览(64)
  • 《3.linux应用编程和网络编程-第8部分-3.8.网络基础》 3.8.1.网络通信概述 3.8.3.网络通信基础知识2

        进程间通信: 管道 、 信号量、 共享内存, 技术多,操作麻烦     线程就是解决 进程间 通信 麻烦的事情,这是线程的 优势 3.8.1.网络通信概述 3.8.1.1、从进程间通信说起: 网络域套接字socket , 网络通信其实就是位于网络中不同主机上面                   的 

    2024年02月15日
    浏览(56)
  • 了解ET模式和LT模式:Linux网络编程中的事件触发方式

    当谈到Linux网络编程中的ET(边缘触发)模式和LT(水平触发)模式时,我们需要理解它们在事件驱动编程中的作用和区别。下面是一篇详细解释这两种模式的博文,包含代码示例。 摘要: 在Linux网络编程中,ET(边缘触发)模式和LT(水平触发)模式是两种常用的事件触发方式

    2024年02月11日
    浏览(38)
  • 网络编程套接字应用分享【Linux &C/C++ 】【UDP应用 | TCP应用 | TCP&线程池小项目】

    目录 前提知识 1. 理解源ip,目的ip和Macip 2. 端口号 3. 初识TCP,UDP协议 4. 网络字节序 5. socket 编程 sockaddr类型  一,基于udp协议编程  1. socket——创建套接字 2. bind——将套接字强绑定  3. recvfrom——接受数据 4. sendto——发出信息  遇到的问题 (1. 云服务器中以及无法分配I

    2024年04月08日
    浏览(90)
  • [linux--->应用层网络通信协议]

    协议本质是收发端双方约定好格式的数据,常见协议是用结构体或者类的方式来表达,结构化的数据是为了方便被应用层解读,这个结构体中可能包括发送者ip和端口号以及主机名,还有通信信息,应用层可以用结构体区分并使用信息;使用结构体直接传递,但是可能会因为系统的不同

    2024年02月15日
    浏览(38)
  • Linux网络:应用层之HTTP协议

    我们程序员写的一个个解决实际问题,满足日常需求的网络程序,都是在应用层。 协议是一种约定。网络协议是计算机网络中通信双方都必须遵守的一组约定。 在网络通信中,都是以 “字符串” 的方式来发送和接收数据的。 如果要发送和接收一些结构化的数据,就需要序

    2023年04月26日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包