结构体作为接口的注意事项

Posted cherishui

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了结构体作为接口的注意事项相关的知识,希望对你有一定的参考价值。

在后端向前端回复数据时,需要将结构化数据通过网络传输给前端,而网络传输是字节流传输,前端收到的是一段数据,那么,问题就落脚在如何解析这段数据。

很多请求的场景,返回的条数是动态变化的,比如订单数量。用户每下一个订单,那么请求返回的数量就会加1.这时候,如何较好的返回动态数据呢?这个看使用怎样的存储格式来承载可变长度的数据返回。就目前已知的处理方法:

  • 使用 json 格式
  • 使用 protobuf 格式
  • 使用 struct 格式

由于工作需要,这里使用 struct 格式来进行数据的包裹。

固定长度的返回数据

比如,查询某个用户的账户余额,返回的结构体类似如下:

struct TAccountCapital : struct PacketHead
{
    double m_dCapital;
}

struct PacketHead为包头结构体,里面的内容为传输过程中的一些通用选项,在此不表。

当该结构体后续有扩展时,只要保证后续新增的字段始终放在后面,那就可以保持升级后新旧版本的兼容性。

可变长度的返回数据

比如,查询某个用户的当日订单信息,返回的结构体类似如下:


struct TAccountOrder :struct PacketHead
{
    int m_nCount;   // 订单个数
    TOrderInfo  m_OrderInfo[1];  // 订单数据
}

struct TOrderInfo
{
    int m_nOrderNo;              // 订单编号
    string m_nOrderProductName;  // 订单商品名称
}

这里用到的 linux下的 零长数组技巧,此处不再赘述。

客户端在接收到此类数据时,先通过 sizeof(TOrderInfo) * m_nCount得到订单数据的真实长度,然后以sizeof(TOrderInfo)为步长,将内存中数据整理输出,显示出来。

到这一步,看起来都很美好。

再往下看,随着业务的发展,订单信息需要新增一个字段,比如新增订单折扣信息。此时的返回结构体如下:


struct TOrderInfo
{
    int m_nOrderNo;              // 订单编号
    string m_nOrderProductName;  // 订单商品名称
    double m_dOrderDiscount;     // 订单折扣信息
}

一旦结构体有所改变,就存在前后端面对的结构体不一致,如果前端还按照原有的步长来截取内存输出,随后的订单信息就会全部错乱。

造成这个问题的原因是前端使用的结构体与后台的不一致,后台的修改带来了兼容性问题。既然问题根源知道了,那么解决的方法也有浮出水面了。那就是让前端知道真正的步长,这个步长不能依赖于前端能够看到的结构体长度,而要通过其他途径了解真实的长度,而这个真实的长度,只有后台了解,因此,需要后台增加一个字段,表示传递结构体真正的长度。


struct TAccountOrder :struct PacketHead
{
    int m_nCount;                // 订单个数
    int m_nItemSize;             // 订单数据结构体大小
    TOrderInfo  m_OrderInfo[1];  // 订单数据
}

结论

在使用结构体传递可变数据时,使用零长数组传输数据时,需要增加返回的结构体大小成员。

同时,要同前端开发人员做好约定,在解析数据时,要依赖接口中的结构体大小成员,而不要依赖自己算出来的结构体成员大小。

以上是关于结构体作为接口的注意事项的主要内容,如果未能解决你的问题,请参考以下文章

Golang学习+深入-面向“对象“编程

分享几个实用的代码片段(第二弹)

分享几个实用的代码片段(第二弹)

Go-接口类型详解(定义实现接口继承比较等)

C语言开发函数库时利用不透明指针对外隐藏结构体细节

C 语言结构体 ( 结构体作为函数参数 | 结构体指针作为函数参数 )