如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?

这篇具有很好参考价值的文章主要介绍了如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?,高质量干货博客汇总,操作系统,Linux,服务器,后端,centos,linux,tcp/ip,网络协议

前言

那么这里博主先安利一些干货满满的专栏了!

首先是博主的高质量博客的汇总,这个专栏里面的博客,都是博主最最用心写的一部分,干货满满,希望对大家有帮助。

高质量干货博客汇总https://blog.csdn.net/yu_cblog/category_12379430.html?spm=1001.2014.3001.5482

该项目GITHUB地址

网络计算器-序列化和反序列化-协议定制https://github.com/Yufccode/BitCode/tree/main/Linux/%E4%BB%A3%E7%A0%81/0226%E7%BD%91%E7%BB%9C%E8%AE%A1%E7%AE%97%E5%99%A8-%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E5%8D%8F%E8%AE%AE%E5%AE%9A%E5%88%B6

什么是字节流和数据报

在计算机网络理论中,什么是字节流,什么是数据报,他们和TCP,UDP的关系是什么,字节流和数据报的关系是什么?

字节流是一种连续的、无边界的数据流。它将数据视为一个连续的字节序列,没有明确的分组或边界。在字节流中,数据按照发送的顺序进行传输,接收方按照相同的顺序接收数据。字节流是一种面向连接的传输方式,它提供可靠的、有序的、基于错误检测和纠正的数据传输。TCP(传输控制协议)是一个使用字节流传输的协议。

数据报是一种离散的、有边界的数据传输方式。它将数据划分为固定大小的数据包,每个数据包都有自己的头部信息(通常包含源地址、目标地址、序列号等),并独立发送。每个数据包在网络中独立传输,可能沿不同的路径到达接收方。数据报传输通常是无连接的,不保证可靠性和有序性。UDP(用户数据报协议)是一个使用数据报传输的协议。

关于它们和TCP、UDP的关系:

  • TCP使用字节流传输方式,通过TCP连接来提供可靠的、有序的、面向连接的数据传输。TCP使用序号和确认机制来确保数据的可靠性和有序性。
  • UDP使用数据报传输方式,提供了无连接、不可靠的数据传输。UDP适用于对实时性要求较高,但对数据可靠性和顺序性要求不高的应用场景。

字节流和数据报之间没有直接的关系,它们是不同的传输方式。TCP使用字节流传输方式,而UDP使用数据报传输方式。这两种传输方式适用于不同的网络应用场景,具体选择取决于应用的要求和设计。

粘包问题

粘包问题是在网络通信中常见的一个现象,指的是接收方无法准确地将字节流拆分为原始的数据报,导致数据解析错误。为了解决粘包问题,可以采取以下方法:

  • 使用固定长度分割,每个数据报长度固定,接收方按照固定长度提取数据。
  • 使用特定字符分割,定义一个特殊的字符或字符序列作为数据报之间的分隔符,接收方根据分隔符提取数据报。
  • 使用长度字段分割,数据报头部添加表示长度的字段,接收方读取长度字段并提取相应长度的字节作为数据报。

学习计算机网络应用层协议的原理是为了理解网络通信机制和实现自定义协议。定制协议原理的学习能帮助我们根据特殊字符将字节流分割为数据报,并通过反序列化解析出所需报文。这对于开发网络应用、数据交互和错误处理至关重要。

序列化和反序列化

序列化(Serialization)是将对象的状态转换为字节流的过程,以便将其存储在内存、文件或网络中,或者将其传输到其他远程系统。序列化将对象转换为字节序列的形式,使得可以在不同的平台、系统或编程语言之间进行数据交换和持久化存储。

反序列化(Deserialization)是将字节流转换回对象的状态的过程,即从序列化的字节流中恢复对象的属性和数据结构。反序列化将字节流重新转换为原始对象的形式,以便可以使用和操作这些对象。

序列化和反序列化通常在分布式系统、网络通信和持久化存储等场景中使用。通过序列化,可以将对象转换为字节流,在网络传输中发送或存储到磁盘上。然后,通过反序列化,可以将字节流重新还原为原始对象,以便进行处理、操作或者重新恢复对象的状态。

在使用HTTP协议的时候,使用过浏览器的时候,浏览器的后端,服务器的后端,会帮我们做好这些事情,但是今天,博主要带着大家来定制一个自己的协议,带着大家来理解上述的原理。

本项目:实现一个网络版本计算器利用自己定制的协议

Github地址

网络计算器-序列化和反序列化-协议定制https://github.com/Yufccode/BitCode/tree/main/Linux/%E4%BB%A3%E7%A0%81/0226%E7%BD%91%E7%BB%9C%E8%AE%A1%E7%AE%97%E5%99%A8-%E5%BA%8F%E5%88%97%E5%8C%96%E5%92%8C%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E5%8D%8F%E8%AE%AE%E5%AE%9A%E5%88%B6

底层服务器准备

底层使用TCP连接,在博主实现的代码中,博主简单实用了一下多线程,写了一个多线程的版本,这里博主就不展示底层服务器的代码了,小伙伴可以直接看博主github上的那个就行。

制定序列化反序列化规则和报文规则

规定一个运算包括三个字段:

x,y,op 

x代表第一个操作数

y代表第二个操作数

op代表运算符

1+2 即 x=1,y=2,op='+'

规定,客户端输入格式如下所示:

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?,高质量干货博客汇总,操作系统,Linux,服务器,后端,centos,linux,tcp/ip,网络协议

 序列化后为:

Request报文格式

length\r\n__x __op __y\r\n

即第一个字段是报文长度,紧接着两个特殊字符\r\n 然后是x,y和op,中间用空格分开,最后再加上一个\r\n。

这个就是我们的报文! 

步骤如下:

当用户输入到客户端之后,客户端首先会将三个字段用结构体Request存起来,然后调用序列化的接口,把结构体序列化成一个Request字符串,就是上面我们展示的报文的格式。

当服务端收到这个Request报文之后,再调用反序列化的接口得到三个字段,得到一个结Request构体。

然后通过这个Request结构体的内容,计算得到结果后,形成Respone结构体,对Respone进行序列化,发送给客户端,客户端再把Respone反序列化,得到最后的结果。

Respones报文格式

length\r\n__x __op __y\r\n

Protocol.hpp



#ifndef __Yufc_Protocol_For_Cal
#define __Yufc_Protocol_For_Cal

#include <iostream>
#include <string>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"

/* 协议的本质 --> 约定! */

#define RECV_MAX_SIZE 1024

/* 请求协议是必须自带序列化功能的 一个结构体是很难发送的 需要序列化成string */
/* 上次0222定制的协议是不完善的!我们需要定制报文*/

// 协议
/* length\r\n__x __op __y\r\n*/
/* length这些就叫做协议报头 */

namespace yufc_ns_protocol
{
#define MYSELF true
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP)
    class Request
    {
    public:
        std::string Serialize()
        {
#if MYSELF
            std::string str;
            str = std::to_string(__x);
            str += SPACE;
            str += __op; // BUG
            str += SPACE;
            str += std::to_string(__y);
            return str;
#else
            /* 使用别人的序列化方案 */
            #include <jsoncpp/json/json.h>
            Json::Value root;
            root["x"] = __x;
            root["y"] = __y;
            root["op"] = __op;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        bool Deserialize(const std::string &str)
        {
#if MYSELF
            std::size_t left = str.find(SPACE);
            if (left == std::string::npos)
            {
                return false; // 反序列化失败
            }
            std::size_t right = str.rfind(SPACE);
            if (right == std::string::npos)
            {
                return false; // 反序列化失败
            }
            __x = atoi(str.substr(0, left).c_str()); // 拿到__x的字符串之后直接 atoi 就行
            __y = atoi(str.substr(right + SPACE_LEN).c_str());
            if (left + SPACE_LEN > str.size())
                return false;
            __op = str[left + SPACE_LEN];
            return true;
#else
            #include <jsoncpp/json/json.h>
            Json::Value root;
            Json::Reader reader;
            reader.parse(str, root);
            __x = root["x"].asInt();
            __y = root["y"].anInt();
            __op = root["op"].asInt();
            return true;
#endif
        }

    public:
        Request() {}
        Request(int x, int y, char op) : __x(x), __y(y), __op(op) {}
        ~Request() {}

    public:
        int __x;
        int __y;
        char __op; // '+' ...
    };

    class Response
    {
    public:
        /* "code result" */
        std::string Serialize()
        {
#if MYSELF
            std::string s;
            s = std::to_string(__code);
            s += SPACE;
            s += std::to_string(__result);
            return s;
#else
            #include <jsoncpp/json/json.h>
            Json::Value root;
            root["code"] = __code;
            root["result"] = __result;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        bool Deserialize(const std::string &s)
        {
#if MYSELF
            // std::cerr << "s: " << s << std::endl;
            std::size_t pos = s.find(SPACE);
            if (pos == std::string::npos)
                return false;
            __code = atoi(s.substr(0, pos).c_str());
            __result = atoi(s.substr(pos + SPACE_LEN).c_str());
            return true;
#else   
            Json::Value root;
            Json::Reader reader;
            reader.parse(s, root);
            __code = root["code"].asInt();
            __result = root["result"].asInt();
            return true;
#endif
        }

    public:
        Response() {}
        Response(int result, int code) : __result(result), __code(code) {}
        ~Response() {}

    public:
        int __result; // 计算结果
        int __code;   // 计算结果的状态码
        /* 0表示计算结果可信任 */
    };

    // 临时方案
    bool Recv(int sock, std::string *out)
    {
        char buffer[RECV_MAX_SIZE];
        memset(buffer, 0, sizeof buffer);
        ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
        if (s > 0)
        {
            buffer[s] = 0;
            *out += buffer;
        }
        else if (s == 0)
        {
            logMessage(NORMAL, "client quit");
            return false;
        }
        else
        {
            std::cerr << "recv error" << std::endl;
            return false;
        }
        return true;
    }

    // 临时方案
    void Send(int sock, const std::string sendStr)
    {
        send(sock, sendStr.c_str(), sendStr.size(), 0);
    }

    // "length\r\n__x __op __y\r\n"
    std::string Decode(std::string &buffer)
    {
        std::size_t pos = buffer.find(SEP);
        if (pos == std::string::npos) // 没有找到 返回空串
            return "";
        int size = atoi(buffer.substr(0, pos).c_str());
        int surplus = buffer.size() - pos - SEP_LEN - SEP_LEN; // 如果这个surplus大于报文长度
        // 则说明 这个报文长度是可以保证我们整体解析出一个合法字符串的
        if (surplus >= size)
        {
            // 至少具有一个合法报文,可以动手提取了
            buffer.erase(0, pos + SEP_LEN); // 此时"__x __op __y\r\n"
            std::string s = buffer.substr(0, size);
            buffer.erase(0, size + SEP_LEN);
            // 此时我们就把合法的部分截取出来了 就是s
            return s;
        }
        else
        {
            return "";
        }
    }

    std::string Encode(std::string &s)
    {
        // 添加报头
        std::string length = std::to_string(s.size());
        std::string new_package = length;
        new_package += SEP;
        new_package += s;
        new_package += SEP;
        return new_package;
    }

}

#endif

设置服务器进程为守护进程

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?,高质量干货博客汇总,操作系统,Linux,服务器,后端,centos,linux,tcp/ip,网络协议

 

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?,高质量干货博客汇总,操作系统,Linux,服务器,后端,centos,linux,tcp/ip,网络协议

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?,高质量干货博客汇总,操作系统,Linux,服务器,后端,centos,linux,tcp/ip,网络协议

CalServer.cc

#include "Protocol.hpp"
#include "TcpServer.hpp"
#include <memory>
#include <signal.h>
#include "Daemon.hpp"

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

void debug(int sock)
{
    std::cout << "service for debug, sock: " << sock << std::endl;
}

static yufc_ns_protocol::Response calHelper(const yufc_ns_protocol::Request &req)
{
    yufc_ns_protocol::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 (0 == req.__y)
            resp.__code = 1;
        else
            resp.__result = req.__x / req.__y;
        break;
    case '%':
        if (0 == req.__y)
            resp.__code = 2;
        else
            resp.__result = req.__x % req.__y;
        break;
    default:
        resp.__code = 3;
        break;
    }
    return resp;
}

void Calculator(int sock)
{
    /* 此处我们就要去定制协议 */
    std::string inbuffer;
    while (true)
    {
        // 1. 读取数据
        bool res = yufc_ns_protocol::Recv(sock, &inbuffer); // 在这里我们读到了一个请求
        // 2. 协议解析 -- 需要保证得到一个完整的报文
        if (!res)
            break;                                                // 读取失败
        std::string package = yufc_ns_protocol::Decode(inbuffer); // 这里Decode保证返回的是一个完整的字节流,是正确的,是可以序列化反序列化的!
        // 如果这个package是空 表示Decode没有给我们返回完整报文
        if (package.empty())
            continue;
        // 3. 保证该报文是一个完整的报文
        yufc_ns_protocol::Request req;
        // 4. 反序列化
        req.Deserialize(package);
        // 5. 业务逻辑
        yufc_ns_protocol::Response resp = calHelper(req);
        // 6. 序列化
        std::string respString = resp.Serialize(); // 序列化

        // 7. 在发送之前,添加报头
        // "length\r\ncode result\r\n"
        respString = yufc_ns_protocol::Encode(respString);
        // 8. 发送(暂时这样写,高级IO的时候,我们再来谈发送的逻辑)
        yufc_ns_protocol::Send(sock, respString); // 发送
    }
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    signal(SIGPIPE, SIG_IGN);
    /*
    一般经验: server 在编写的时候都要有严谨的判断逻辑
    一般的服务器,都是要忽略SIGPIPE信号的!防止运行中出现非法写入的问题
    */
    std::unique_ptr<yufc_tcpServer::TcpServer> server(new yufc_tcpServer::TcpServer(atoi(argv[1])));
    server->BindService(Calculator);
    MyDaemon();
    server->Start();
    return 0;
}

运行现象

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?,高质量干货博客汇总,操作系统,Linux,服务器,后端,centos,linux,tcp/ip,网络协议

如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?,高质量干货博客汇总,操作系统,Linux,服务器,后端,centos,linux,tcp/ip,网络协议文章来源地址https://www.toymoban.com/news/detail-571199.html

到了这里,关于如何定制自己的应用层协议?|面向字节流|字节流如何解决黏包问题?如何将字节流分成数据报?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 应用层协议 HTTP

    我们已经学过 TCP/IP , 已然知道数据能从客户端进程经过路径选择跨网络传送到服务器端进程。 我们还需要知道的是,我们把数据从 A 端传送到 B 端, TCP/IP 解决的是顺丰的功能,而两端还要对数据进行加工处理或者使用,所以我们还需要一层协议,不关心通信细节,关心应用

    2024年02月06日
    浏览(44)
  • 【网络】应用层——HTTPS协议

    🐱作者:一只大喵咪1201 🐱专栏:《网络》 🔥格言: 你只管努力,剩下的交给时间! 前面本喵讲解并演示了HTTP协议,在比较 POST 和 GET 方法的时候,本喵说这两个方法都不安全,虽然 POST 的提交的表单内容在请求正文中,无法在地址的 url 中看到,但是它仍然是不安全的。

    2024年02月14日
    浏览(42)
  • 【网络】应用层——HTTP协议

    🐱作者:一只大喵咪1201 🐱专栏:《网络》 🔥格言: 你只管努力,剩下的交给时间! 上篇文章中,本喵带着大家对HTTP有了一个初步的认识,今天就来详细讲解一下这个应用层协议。 如上图所示的 url (网址),里面包含有 / 以及 ? 等字符。 像这样的字符,已经被url当做 特殊

    2024年02月15日
    浏览(39)
  • 【网络原理】应用层协议 与 传输层协议

    ✨个人主页:bit me👇 ✨当前专栏:Java EE初阶👇 我们自己写的应用程序就是在应用层 虽然应用层里面有一些现成的协议,但是在实际工作中也会存在 自定义应用层协议 (发明协议? 协议就是约定,约定好客户端和服务器按照啥样的格式来传输数据 ) 那么应用层协议如何

    2023年04月20日
    浏览(46)
  • 【计算机网络】应用层协议 -- HTTP协议

    协议。网络协议的简称,网络协议是通信计算机双方必须共同遵守的一组约定,比如怎么建立连接,怎么互相识别等。 为了使数据在网络上能够从源头到达目的,网络通信的参与方必须遵守相同的规则,我们称这套相同的规则为协议(protocol),而协议最终都需要通过计算机

    2024年02月15日
    浏览(49)
  • 网络安全——应用层安全协议

    作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。   座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录  前言 一.应用层安全协议  1.应用层安全威胁 2.电子邮件安全协议 1.MIME协议 2.电子邮件安全威胁  3.S/MIME协议 4.PGP协议 本

    2024年02月06日
    浏览(54)
  • JavaEE & HTTP应用层协议

    HTTP应用层协议 超文本传输协议(Hyper Text [Transfer Protocol](https://baike.baidu.com/item/Transfer Protocol/612755?fromModule=lemma_inlink),HTTP) 是一个简单的请求-响应协议 ,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以

    2024年02月06日
    浏览(81)
  • 【JavaEE】HTTP应用层协议

    HTTP应用层协议 超文本传输协议(Hyper Text [Transfer Protocol](https://baike.baidu.com/item/Transfer Protocol/612755?fromModule=lemma_inlink),HTTP) 是一个简单的请求-响应协议 ,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以

    2024年02月07日
    浏览(52)
  • 网络协议(七)应用层-HTTP

    上篇文章介绍了传输层的TCP、UDP协议,在TCP/IP协议中,下三层(网络接口层,网络层,传输层)都是计算机系统联合其他硬件设备自己在干的事,身为程序员的我们平时对其感知不大。而应用层却是与程序开发息息相关的一层,如HTTP,HTTPS,DNS,FTP,SMTP等等,针对不同应用场

    2024年02月03日
    浏览(37)
  • 【网络原理】| 应用层协议与传输层协议 (UDP)

    🎗️ 主页:小夜时雨 🎗️ 专栏:javaEE初阶 🎗️ 乾坤未定,你我皆黑马 应用层是和代码直接相关的一层,决定了数据要传输什么,怎么去使用这些数据等问题。 应用层这里,虽然存在一些现有的协议(比如HTTP),但是也有很多的情况,需要我们去自定义一些协议,这里的自

    2024年02月06日
    浏览(49)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包