【Socket 编程】应用层自定义协议与序列化

再谈协议

协议就是约定,协议的内容就是约定好的某种结构化数据。比如,我们要实现一个网络版的计算器,客户端发送一个算术式子,服务端根据算术式子计算出结果,再把结果返回给客户端。在此之前,客户端和服务端可以做出如下两种方案的约定

  • 方案一:
    • 客户端发送一个形如“1+1"的字符串
    • 这个字符串中有两个操作数,都是整型
    • 两个数字之间会有一个字符是运算符,运算符只能是+
    • 数字和运算符之间没有空格
    • 等等
  • 方案二:
    • 定义一个结构体表示一个请求信息,信息内容包括两个整数,和一个操作符
    • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
    • 定义一个结构体表示一个服务器的响应,响应的信息包括,计算结果、错误码、错误信息等

为什么要做出诸如此类的约定呢? 一切交给服务器去处理不就行了吗?

做约定的本质就是为了方便读取数据和发送数据。假使客户端和服务端没有做任何约定,在我们这个网络版计算器中,客户端就有可能发送很多无效的信息,比如随便输入一段字符串。很显然,如果客户端不做任何处理直接发送给服务端,服务端虽然也能做一些手段去检测数据的合法性,但是那样太浪费时间了

所以,为了能让整个交互过程有序且高效,客户端一定要与服务端达成某种协议,即规定发送时数据的格式与接收数据的格式。这样一来,客户端发送数据时按照约定好的格式打包好再发送,客户端就不需要考虑服务端怎么读取数据了,因为已经约定好了。相反,服务端接收数据包时按照约定,正确的实施解包的步骤就能读取到正确的数据,因为已经约定好了,客户端发送过来的数据一定是按照约定的格式存储的。
这就是约定,这就是协议

序列化和反序列化

在上面我们谈到,协议是客户端与服务端约定好的某种存储数据的结构,说白了就是一个特定的结构体。但是无论是发送请求还是发送响应,都是需要通过网络来传输的,具体地说,在将传输给网络之前,我们要把上述包含数据的结构体进一步转换成可供网络传输的格式。同样的,从网络读取到数据后,在应用层还需要将其转换回来。前者就叫序列化,后者叫反序列化
简单理解,序列化,就是网络化,就是把数据转换成某种在网络中传输的格式。反序列化,就是逆序列化过程

其中Jsoncpp库中提供了一些供序列化和反序列化的方法。

  • 创建JSON对象
Json::Value root;
  • 设置值
root["name"] = "John Doe";
root["age"] = 30;
  • 访问值
td::string name = root["name"].asString();
int age = root["age"].asInt();
  • 将数据序列化为字符串:
Json::Value root;
root["name"] = "joe";
root["sex"] = "男";
Json::FastWriter writer;
std::string s = writer.write(root);

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

在这里插入图片描述

  • 在每台主机上,TCP连接维护了两个缓冲区,分别用来发送数据和读取数据。所以发送数据和读取数据可以同时进行。因为这俩操作不是在同一个缓冲区进行的
  • sockfd描述符本质就是一个文件描述符,但是它对应着读写两个缓冲区,操作系统会自动区分读和写操作,所以tcp通过一个sockfd就能进行读和写两种操作
  • 实际数据什么时候发,发多少,处理错误,由TCP协议控制,所以TCP叫做传输控制协议。

自定义协议网络计算器

根据前面的学习,我们可以自己简单模式一个用户层的协议,来实现网络版的计算器。
对于服务端,事件流程如下:

  • 创建套接字
  • 进入监听状态
  • 获取连接
  • 获取请求,将其反序列化,得到一个包含客户端数据的结构体
  • 处理业务
  • 将应答消息序列化,得到一个字符串,发送给客户端
  • 上述步骤重复执行
  • 关闭连接,关闭套接字

对于客户端,事件如下:

  • 创建套接字
  • 连接服务端
  • 将序列化后的数据打包成一个请求发送给服务端
  • 获取服务端的应答,将应答反序列化获取结果
  • 关闭连接,关闭套接字

既然是自定义协议,那么我们可以规定请求和应答的数据格式为:len\r\n{json}\r\njson其实就是一个用Json库处理序列化后的一个字符串(该字符串包含有效数据),len表示的是json字符串的长度

中间的换行符用于区分字节流读取进度。假设读取到的数据流有一个换行符,说明该数据流一定包含len信息,拿到len信息后就可以再判断该数据流是否包含json字符串。

于是,在请求之前,我们需要先把有效数据转换成一个Json处理过的字符串,这叫做序列化。由于是网络中是字节流传输数据,为了辨别拿到的数据是否完整,我们还需要添加一些”标识“数据,这就是报头,整合起来就是一个报文
完整代码可以去我gitee上去获取:点击查看
下面只给出序列化和反序列化实现模块

序列化和反序列化

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

static const std::string sep = "\r\n";

// 报文格式 len\r\n{joson}\r\n

// 将jsonstr格式的字符串加上报头
std::string Encode(const std::string &jsonstr)
{
    int len = jsonstr.size();
    std::string lenstr = std::to_string(len);
    return lenstr + sep + jsonstr + sep;
}

// 将接收到的数据中的joson段提取出来
std::string Decode(std::string &packagestream)
{
    // 分析
    auto pos = packagestream.find(sep);
    if (pos == std::string::npos)
    {
        return std::string();
    }

    std::string lenstr = packagestream.substr(0, pos);
    int len = std::stoi(lenstr);
    // 计算一个完整的报文应该多长
    int total = lenstr.size() + 2 * sep.size() + len;
    if (packagestream.size() < total)
    {
        return std::string();
    }
    // 提取
    std::string josonstr = packagestream.substr(pos + sep.size(), len);
    packagestream.erase(0, total);
    return josonstr;
}

class Request
{

public:
    Request() {}
    ~Request() {}

    Request(int x, int y, char oper)
        : _x(x), _y(y), _oper(oper)
    {
    }

    // 序列化
    bool Serialize(std::string *out)
    {
        // 使用joson库
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;
        Json::FastWriter write;
        std::string s = write.write(root);
        *out = s;
        return true;
    }
    // 反序列化
    bool Deserialize(const std::string &josonstr)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(josonstr, root); // 将josonstr的内容提取到root中
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _oper = root["oper"].asInt();
        return res;
    }

    void Print()
    {
        std::cout << _x << std::endl;
        std::cout << _y << std::endl;
        std::cout << _oper << std::endl;
    }

    int X()
    {
        return _x;
    }
    int Y()
    {
        return _y;
    }
    char Oper()
    {
        return _oper;
    }

    void SetValue(int x, int y, char oper)
    {
        _x = x;
        _y = y;
        _oper = oper;
    }

private:
    int _x;
    int _y;
    char _oper;
};

// 应答
class Response
{
public:
    ~Response() {}
    Response()
        : _result(0), _code(0), _desc("success")
    {
    }
    // 序列化
    bool Serialize(std::string *out)
    {
        // 使用joson库
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        root["desc"] = _desc;
        Json::FastWriter write;
        std::string s = write.write(root);
        *out = s;
        return true;
    }
    // 反序列化
    bool Deserialize(const std::string &josonstr)
    {
        Json::Value root;
        Json::Reader reader;
        bool res = reader.parse(josonstr, root); // 将josonstr的内容提取到root中
        _result = root["result"].asInt();
        _code = root["code"].asInt();
        _desc = root["desc"].asString();
        return res;
    }
    void PrintResult()
    {
        std::cout << "result: " << _result << " code: " << _code << " desc: " << _desc << std::endl;
    }

    int _result;
    int _code;
    std::string _desc;
};

class Factory
{
public:
    static std::shared_ptr<Request> BuildRequestDefault()
    {
        return make_shared<Request>();
    }

    static std::shared_ptr<Response> BuildResponseDefault()
    {
        return make_shared<Response>();
    }
};
  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值