TCP定制协议,序列化和反序列化

这篇具有很好参考价值的文章主要介绍了TCP定制协议,序列化和反序列化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

1.理解协议

2.网络版本计算器

2.1设计思路

2.2接口设计

2.3代码实现:

2.4编译测试

总结


前言

        在之前的文章中,我们说TCP是面向字节流的,但是可能对于面向字节流这个概念,其实并不理解的,今天我们要介绍的是如何理解TCP是面向字节流的,通过编码的方式,自己定制协议,实现序列化和反序列化,相信看完这篇文章之后,关于TCP面向字节流的这个概念,你将会有一个清晰的认识,下面我们就一起来看看。

1.理解协议

        前面,我们通俗的介绍过协议,在网络中协议是属于一种约定,今天要说的是,数据在网络中传输的时候,协议又是如何体现的。

根据我们之前写的TCP服务器实现数据通信知道socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?

什么是结构化的数据呢?

举个简单的例子,比如在微信上发送信息的时候,除了有发送的信息之外还包含有昵称,时间,头像这些信息,这些合起来就称为是结构化的数据。

所以我们将结构化的数据打包形成一个字符串的过程就称为是序列化,将打包形成的一个字符串转化为结构化数据的过程就称为是反序列化

如图所示:

TCP定制协议,序列化和反序列化,tcp/ip,网络,java

TCP发送和接受数据的流程

如图所示:

TCP定制协议,序列化和反序列化,tcp/ip,网络,java作为程序员,在应用层定义一个缓冲区,然后send接口将数据发送,在这里发送不是将数据直接发送到网络里了,而是调用send接口将数据拷贝到传输层操作系统维护的缓冲区中,而read是将传输层的数据拷贝到应用层,当数据拷贝到传输层之后,剩下数据如何继续发送是由操作系统进行维护的,所以将TCP协议称为是传输控制协议,又因为TCP协议既可以是客户端向服务端发送信息,也可以是服务端向客户端发送信息,所以TCP是全双工的。

了解了TCP协议发送和接受数据的流程之后,因为TCP是面向字节流的,思考当数据由客户端发送给服务端的时候,有没有可能服务端的接受缓冲区中不足一个报文,有没有可能上层来不及处理导致服务端传输层接受缓冲区中有多个报文的情况,此时如何正确的拿到一个完整的报文呢?

因为这些问题的存在,所以我们要定制协议,明确一个完整报文大小,明确一个报文和一个报文的边界,所以我们要采取定制协议的方案获取到一个正确的报文。

一般采取的策略有三种:

1.定长

2.特殊符号

3.自描述的方式

下面我们按照上述的三种方式实现编码上的协议定制。

2.网络版本计算器

说明:为了演示协议定制和序列化以及反序列化在编码上如何实现,以及如何在编码上体现TCP面向字节流的特性,我们通过实现一个网络版本计算器为大家进行介绍

实现网络版本计算机约定:

客户端发送一个形如"1+1"的字符串;
这个字符串中有两个操作数, 都是整形;
两个数字之间会有一个字符是运算符, 运算符只能是 + ;
数字和运算符之间没有空格;

2.1设计思路

        客户端将想要计算的请求按照序列化的方式打包成一个字符串,然后发送给服务端,服务端按照定制协议的方式准确收到客户端的请求,然后服务端进行反序列化获取到结构化的数据,然后进行处理业务逻辑计算结果,结果计算完成之后,服务端将计算结果序列化打包形成一个字符串发送给客户端,然后客户端按照定制协议的方式准确获取到服务端发送过来的一个完整报文,至此就基于TCP协议实现了一个网络版本的计算器

2.2接口设计

要向完成上述的要求,就必须要包含几个接口:

a.请求的序列化和反序列化

b.响应的序列化和反序列化

c.协议定制

d.计算业务逻辑

e.准确获取一个报文

f.客户端和服务端编写

2.3代码实现:

1.请求的序列化和反序列化

class Request
{
public:
    Request()
    :x(0),y(0),op(char()){}
    Request(int x_, int y_, char op_) : x(x_), y(y_), op(op_)
    {}
    bool serialize(std::string *out)
    {
        *out = "";
        // 结构化 -> "x op y";
        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;
        return true;
    }

    // "x op yyyy";
    bool deserialize(const std::string &in)
    {
        // "x op y" -> 结构化
        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); // [0, 2) [start, end) , start, end - start
        std::string y_string = in.substr(right + SEP_LEN);

        if (x_string.empty())
            return false;
        if (y_string.empty())
            return false;
        x = stoi(x_string);
        y = stoi(y_string);
        op = in[left + SEP_LEN];
        return true;
    }

public:
    int x;
    int y;
    char op;
};

序列化结果:将x,y,op - > 转化为 "x y op\r\n"

反序列化结果:"x y op\r\n" - > 转化为 x,y,op

2.响应的序列化和反序列化

#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()
class Response
{
public:
    Response()
    :exitcode(0),result(0) {}
    Response(int exitcode_, int result_) : exitcode(exitcode_), result(result_)
    {}
    bool serialize(std::string *out)
    {
        *out = "";
        std::string ec_string = std::to_string(exitcode);
        std::string res_string = std::to_string(result);

        *out = ec_string;
        *out += SEP;
        *out += res_string;
        return true;
    }
    bool deserialize(const std::string &in)
    {
        // "exitcode result"
        auto mid = in.find(SEP);
        if (mid == std::string::npos)
            return false;
        std::string ec_string = in.substr(0, mid);
        std::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);
        return true;
    }
public:
    int exitcode;
    int result;
};

序列化结果:将exitcode,result - > 转化为 "exitcode result\r\n"

反序列化结果: "exitcode result\r\n" - > 转化为 exitcode,result

3.协议定制

说明:采用自描述的方式+特殊符号,给一个报文头部加上报文的长度,特殊符号"\r\n"用来区分报文长度和报文数据

#define SEP " "
#define SEP_LEN strlen(SEP) // 不敢使用sizeof()
#define LINE_SEP "\r\n"
#define LINE_SEP_LEN strlen(LINE_SEP) // 不敢使用sizeof()
//enLength 和 deLength:打包和解包,解决服务端和客户端准确拿到数据
// "x op y" -> "content_len"\r\n"x op y"\r\n
// "exitcode result" -> "content_len"\r\n"exitcode result"\r\n
std::string enLength(const std::string &text)
{
    std::string send_string = std::to_string(text.size());
    send_string += LINE_SEP;
    send_string += text;
    send_string += LINE_SEP;

    return send_string;
}

// "content_len"\r\n"exitcode result"\r\n
bool deLength(const std::string &package, std::string *text)
{
    auto pos = package.find(LINE_SEP);
    if (pos == std::string::npos)
        return false;
    std::string text_len_string = package.substr(0, pos);
    int text_len = std::stoi(text_len_string);
    *text = package.substr(pos + LINE_SEP_LEN, text_len);
    return true;
}

4.计算业务逻辑

//req是反序列化后的结果,根据res业务处理填充req即可
bool cal(const Request& req,Response& res)
{
    //req是结构化的数据,可以直接使用
    // req已经有结构化完成的数据啦,你可以直接使用
    res.exitcode = OK;
    res.result = OK;

    switch (req.op)
    {
    case '+':
        res.result = req.x + req.y;
        break;
    case '-':
        res.result = req.x - req.y;
        break;
    case '*':
        res.result = req.x * req.y;
        break;
    case '/':
    {
        if (req.y == 0)
            res.exitcode = DIV_ZERO;
        else
            res.result = req.x / req.y;
    }
    break;
    case '%':
    {
        if (req.y == 0)
            res.exitcode = MOD_ZERO;
        else
            res.result = req.x % req.y;
    }
    break;
    default:
        res.exitcode = OP_ERROR;
        break;
    }

    return true;
}

5.准确获取一个报文

//从sock中读取数据保存到text中
//continue是因为tcp协议是面向字节流的,传输数据的时候可能不完整
bool recvPackage(int sock,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);
            if (pos == std::string::npos)
                continue;
            std::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_LEN + text_len;
            // text_len_string + "\r\n" + text + "\r\n" <= inbuffer.size();
            std::cout << "处理前#inbuffer: \n" << inbuffer << std::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 << std::endl;

            break;
        }
        else
            return false;
    }
    return true;
}

注:看到这里我们就可以理解了TCP是面向字节流的概念了。

6.客户端和服务端实现:

calServer.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include "log.hpp"
#include "protocol.hpp" //按照协议约定读取请求
using namespace std;
namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };
    static const uint16_t gport = 8080;
    static const int gbacklog = 5;
    typedef function<bool(const Request& req,Response& res)> func_t;
    //读取请求,保证解耦
    void handlerEnter(int sock,func_t fun)
    {
       string inbuffer;
       while(true)
       {
            //1. 读取:"content_len"\r\n"x op y"\r\n
            // 1.1 你怎么保证你读到的消息是 【一个】完整的请求
            string req_text, req_str;
            if (!recvPackage(sock,inbuffer,&req_text))
                return;
            std::cout << "带报头的请求:\n" << req_text << std::endl;
            //req_str:获取报文
            if (!deLength(req_text, &req_str))
                return;
            std::cout << "去掉报头的正文:\n" << req_str << std::endl;
            // 2. 对请求Request,反序列化
            // 2.1 得到一个结构化的请求对象
            Request req;
            if(!req.deserialize(req_str))
                return;
            // 3. 计算机处理,req.x, req.op, req.y --- 业务逻辑
            // 3.1 得到一个结构化的响应
            Response res;
            fun(req,res);//req处理的结果放到res中,采用回调的方式保证上层业务逻辑和服务器的解耦
            // 4.对响应Response,进行序列化
            // 4.1 得到了一个"字符串"
            string resp_str;
            if(!res.serialize(&resp_str))
                return;
             std::cout << "计算完成, 序列化响应: " <<  resp_str << std::endl;
            // 5. 然后我们在发送响应
            // 5.1 构建成为一个完整的报文
            std::string send_string = enLength(resp_str);
            std::cout << "构建完成完整的响应\n" <<  send_string << std::endl;
            send(sock, send_string.c_str(), send_string.size(), 0); // 其实这里的发送也是有问题的,不过后面再说
       }
    }
    class CalServer
    {
    public:
        CalServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
        {}
        void initServer()
        {
            // 1. 创建socket文件套接字对象
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success: %d", _listensock);

            // 2. bind绑定自己的网络信息
            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(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            // 3. 设置socket 为监听状态
            if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面在填这个坑
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }
        void start(func_t fun)
        {
            for (;;)
            {
                // 4. server 获取新链接
                // sock, 和client进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?

                // version 2 多进程版(2)
                pid_t id = fork();
                if (id == 0) // child
                {
                    close(_listensock);
                    handlerEnter(sock,fun);
                    close(sock);
                    exit(0);
                }
                close(sock);

                // father
                pid_t ret = waitpid(id, nullptr, 0);
                if (ret > 0)
                {
                    logMessage(NORMAL, "wait child success"); // ?
                }
            }
        }
        ~CalServer() {}

    private:
        int _listensock; // 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
        uint16_t _port;
    };

} // namespace server

calClient.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "protocol.hpp"

#define NUM 1024

class CalClient
{
public:
    CalClient(const std::string &serverip, const uint16_t &serverport)
        : _sock(-1), _serverip(serverip), _serverport(serverport)
    {}
    void initClient()
    {
        // 1. 创建socket
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            std::cerr << "socket create error" << std::endl;
            exit(2);
        }
    }
    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
        {
            std::cerr << "socket connect error" << std::endl;
        }
        else
        {
            std::string line;
            std::string inbuffer;
            while (true)
            {
                std::cout << "mycal>>> ";
                std::getline(std::cin, line);  // 1+1
                Request req = ParseLine(line); // "1+1"
                std::string content;
                req.serialize(&content);
                std::string send_string = enLength(content);
                send(_sock, send_string.c_str(), send_string.size(), 0); // bug?? 不管

                std::string package, text;
                //  "content_len"\r\n"exitcode result"\r\n
                if (!recvPackage(_sock, inbuffer, &package))
                    continue;
                if (!deLength(package, &text))
                    continue;
                // "exitcode result"
                Response resp;
                resp.deserialize(text);
                std::cout << "exitCode: " << resp.exitcode << std::endl;
                std::cout << "result: " << resp.result << std::endl;
            }
        }
    }
    Request ParseLine(const std::string &line)
    {
        // 建议版本的状态机!
        //"1+1" "123*456" "12/0"
        int status = 0; // 0:操作符之前,1:碰到了操作符 2:操作符之后
        int i = 0;
        int cnt = line.size();
        std::string left, right;
        char op;
        while (i < cnt)
        {
            switch (status)
            {
            case 0:
            {
                if(!isdigit(line[i]))
                {
                    op = line[i];
                    status = 1;
                }
                else left.push_back(line[i++]);
            }
            break;
            case 1:
                i++;
                status = 2;
                break;
            case 2:
                right.push_back(line[i++]);
                break;
            }
        }
        std::cout << std::stoi(left)<<" " << std::stoi(right) << " " << op << std::endl;
        return Request(std::stoi(left), std::stoi(right), op);
    }
    ~CalClient()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    int _sock;
    std::string _serverip;
    uint16_t _serverport;
};

2.4编译测试

TCP定制协议,序列化和反序列化,tcp/ip,网络,java如图所示:我们准确的实现了网络版本计算器

总结

        通过上面代码的编写,包含定制协议,序列化和反序列代码的实现,我们就能够理解协议在网络传输的重要性了,以及理解了TCP是面向字节流的概念。感谢大家的观看,希望能够帮助到大家,我们下次再见。文章来源地址https://www.toymoban.com/news/detail-663218.html

到了这里,关于TCP定制协议,序列化和反序列化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【网络编程】协议定制+Json序列化与反序列化

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。   目录 一、序列化与反序列化的概念 二、自定义协议设计一个网络计算器 2.1TCP协议,如何保证接收方收到了完整的报文呢?

    2024年02月06日
    浏览(55)
  • 谈谈linux网络编程中的应用层协议定制、Json序列化与反序列化那些事

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

    2024年02月06日
    浏览(60)
  • 【Linux】应用层协议序列化和反序列化

    欢迎来到Cefler的博客😁 🕌博客主页:折纸花满衣 🏠个人专栏:题目解析 🌎推荐文章:C++【智能指针】 前言 在正式代码开始前,会有一些前提知识引入 在网络应用层中,序列化(Serialization)和反序列化(Deserialization)是将数据转换为可在网络上传输的格式,并从网络接

    2024年04月23日
    浏览(40)
  • [计算机网络]---序列化和反序列化

    前言 作者 :小蜗牛向前冲 名言 :我可以接受失败,但我不能接受放弃    如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正  目录  一、再谈协议 二、序列化和反序化 1、网络版本计算器的场景搭建 2、

    2024年02月20日
    浏览(43)
  • 协议定制 + Json序列化反序列化

    1.1 结构化数据 协议是一种 “约定”,socket api的接口, 在读写数据时,都是按 “字符串” 的方式来发送接收的。如果我们要传输一些\\\"结构化的数据\\\" 怎么办呢? 结构化数据: 比如我们在QQ聊天时,并不是单纯地只发送了消息本身,是把自己的头像、昵称、发送时间、消息本身

    2024年02月09日
    浏览(46)
  • 网络数据通信—ProtoBuf实现序列化和反序列化

    目录 前言 1.环境搭建 2. centos下编写的注意事项 3.约定双端交互接口 4.约定双端交互req/resp 5. 客户端代码实现 6.服务端代码实现 Protobuf还常用于通讯协议、服务端数据交换场景。那么在这个示例中,我们将实现一个网络版本的通讯录,模拟实现客户端与服务端的交互,通过P

    2024年02月04日
    浏览(43)
  • 【Linux后端服务器开发】协议定制(序列化与反序列化)

    目录 一、应用层协议概述 二、序列化与反序列化 Protocal.h头文件 Server.h头文件 Client.h头文件 server.cpp源文件 client.cpp源文件 什么是应用层 ?我们通过编写程序解决一个个实际问题、满足我们日常需求的网络程序,都是应用层程序。 协议是一种“约定”,socket的api接口,在读

    2024年02月16日
    浏览(40)
  • Go语言网络编程入门:TCP、HTTP、JSON序列化、Gin、WebSocket、RPC、gRPC示例

    在本文中,我们将介绍Go语言中的网络编程的不同方式,包括TCP、HTTP、Gin框架、WebSocket、RPC、gRPC的介绍与连接实例,并对所有示例代码都给出了详细的注释,最后对每种模式进行了总结。 TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,提供

    2024年02月16日
    浏览(58)
  • Unity-序列化和反序列化

    序列化是指把对象转换为字节序列的过程,而反序列化是指把字节序列恢复为对象的过程。序列化最主要的用途就是传递对象和保存对象。 在Unity中保存和加载、prefab、scene、Inspector窗口、实例化预制体等都使用了序列化与反序列化。 1 自定义的具有Serializable特性的非抽象、

    2024年01月24日
    浏览(56)
  • 【Linux】序列化和反序列化

    在网络编程中,直接使用 结构体 进行数据传输会出错,因为 本质上socket无法传输结构体 ,我们只有将结构体装换为字节数组,或者是字符串格式来传输,然后对端主机收到了数据,再将其转化为结构体,这就是序列化和反序列化的过程! 序列化 (Serialization)是将对象的状态

    2024年02月10日
    浏览(42)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包