GOLANG简单类型定义,在协议解析的妙用

前端之家收集整理的这篇文章主要介绍了GOLANG简单类型定义,在协议解析的妙用前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

原文:https://gocn.io/article/322

在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512,11025,22050,44100。而在AAC中有16种采样率96000,88200,64000,48000,44100,32000,24000,16000,12000,8000,7350(还有4个是保留的)。也就是说,1在FLV中标识11025Hz,而在AAC中表示的是88200Hz。如何实现这个转换呢?

C++当然先得定义枚举:

enum SrsAudioSampleRate
{
    SrsAudioSampleRate5512 = 0,SrsAudioSampleRate11025,SrsAudioSampleRate22050,SrsAudioSampleRate44100,SrsAudioSampleRateForbidden,};

C++当然是用函数了:

SrsAudioSampleRate aac_to_flv(int v) {
    if (v >= 0 && v <=5) {
        return SrsAudioSampleRate44100;
    } else if (v >=6 && v <= 8) {
        return SrsAudioSampleRate22050;
    } else if (v >= 9 && v <= 11) {
        return SrsAudioSampleRate11025;
    } else if (v == 12) {
        return SrsAudioSampleRate5512;
    } else {
        return SrsAudioSampleRateForbidden;
    }
}

看起来还是挺简单的。慢着,还有的时候需要打印出采样率来,所以还得搞个函数

string srs_audio_sample_rate2str(SrsAudioSampleRate v)
{
    switch (v) {
        case SrsAudioSampleRate5512: return "5512";
        case SrsAudioSampleRate11025: return "11025";
        case SrsAudioSampleRate22050: return "22050";
        case SrsAudioSampleRate44100: return "44100";
        default: return "Forbidden";
    }
}

拿到一个AAC的采样率,然后转换成FLV的,并打印出来,是这么使用的:

// 从文件或者流中读取出AAC的采样率的值。
int samplingFrequencyIndex = ...;
// 转换成FLV的采样率。
SrsAudioSampleRate sampleRate = aac_to_flv(samplingFrequencyIndex);
// 转换成字符串格式。
string sSampleRate = srs_audio_sample_rate2str(sampleRate);
// 打印采样率。
printf("SampleRate=%d/%sHz\n",sampleRate,sSampleRate);

有什么麻烦的呢?

  1. 函数和类型之间没有关系,每次使用的时候都得去翻手册啊翻手册。
  2. 如果定义成一个struct,那转换的时候又太麻烦了。

还能不能愉快的玩耍呢?用GOLANG吧!先看用法

var sampleRate AudioSamplingRate
sampleRate.From(samplingFrequencyIndex)
fmt.Printf("SampleRate=%d/%v\n",sampleRate)

就是这么简单(此处应该有掌声)~

其实实现起来也非常自然:

type AudioSamplingRate uint8

const (
    AudioSamplingRate5kHz  AudioSamplingRate = iota // 0 = 5.5 kHz
    AudioSamplingRate11kHz                          // 1 = 11 kHz
    AudioSamplingRate22kHz                          // 2 = 22 kHz
    AudioSamplingRate44kHz                          // 3 = 44 kHz
    AudioSamplingRateForbidden
)

func (v AudioSamplingRate) String() string {
    switch v {
    case AudioSamplingRate5kHz:
        return "5.5kHz"
    case AudioSamplingRate11kHz:
        return "11kHz"
    case AudioSamplingRate22kHz:
        return "22kHz"
    case AudioSamplingRate44kHz:
        return "44kHz"
    default:
        return "Forbidden"
    }
}

func (v *AudioSamplingRate) From(a int) {
    switch a {
    case 0, 1, 2, 3, 4, 5:
        *v = AudioSamplingRate44kHz
    case 6, 7, 8:
        *v = AudioSamplingRate22kHz
    case 9, 10, 11:
        *v = AudioSamplingRate11kHz
    case 12:
        *v = AudioSamplingRate5kHz
    default:
        *v = AudioSamplingRateForbidden
    }
}

Remark: 代码参考commit.

有几个地方非常不同:

  1. 虽然GOLANG只是在uint8上面加了函数,但是使用起来方便很多了,以前在C++中用这两个枚举,每次都要跳到枚举的定义来看对应的函数是什么。
  2. GOLANG的switch比较强大,可以case好几个值,和C++的if有点想,但是GOLANG的case更直观,知道这几个值会被转换成另外的值,而if读起来像是将一个范围的值转换,不好懂。
  3. GOLANG的枚举使用const实现,也可以带类型,而且有个iota很强大,特别是在定义那些移位的枚举时就很好用。

好吧,这只是几个小的改进,虽然用起来很方便。来看看在AMF0中基本类型的妙用,AMF0是一种传输格式,和JSON很像,不过JSON是文本的,而AMF0是字节的,都是用来在网络中传输对象的。因此,AMF0定义了几个基本的类型:String,Number,Boolean,Object,其中Object的属性定义为String的属性名和值,值可以是其他的类型。

先看看C++的实现,首先定义一个AMF0Any对象,可以转换成具体的String或者Object等对象:

class SrsAmf0Any {
    // 提供转换的函数获取实际的值。
    virtual std::string to_str();
    virtual bool to_boolean();
    virtual double to_number();
    virtual SrsAmf0Object* to_object();
    // 当然还得提供判断的函数,得知道是什么类型才能转。
    virtual bool is_string();
    virtual bool is_boolean();
    virtual bool is_number();
    virtual bool is_object();
    // 提供创建基本类型的函数
    static SrsAmf0Any* str(const char* value = NULL);
    static SrsAmf0Any* boolean(bool value = false);
    static SrsAmf0Any* number(double value = 0.0);
    static SrsAmf0Object* object();
};

在实现时,String和Number等基本类型可以隐藏起来(在cpp中实现):

namespace _srs_internal {
    class SrsAmf0String : public SrsAmf0Any {
    public:
        std::string value;
        // 当然它必须实现编码和解码的函数
        virtual int total_size();
        virtual int read(SrsBuffer* stream);
        virtual int write(SrsBuffer* stream);
    };
}

AMF0Object当然得暴露出来的:

class SrsAmf0Object : public SrsAmf0Any {
public:
    virtual int total_size();
    virtual int read(SrsBuffer* stream);
    virtual int write(SrsBuffer* stream);
    // 提供设置和读取属性方法
    virtual void set(std::string key,SrsAmf0Any* value);
    virtual SrsAmf0Any* get_property(std::string name);
};

用起来是这样:

// 设置Object的属性,并发送给服务器。
SrsConnectAppPacket* pkt = NULL;
pkt->command_object->set("app",SrsAmf0Any::str(app.c_str()));
pkt->command_object->set("tcUrl",SrsAmf0Any::str(tcUrl.c_str()));

// 读取服务器的响应,取出服务器的IP等信息。
SrsConnectAppResPacket* pkt = NULL;
SrsAmf0Any* data = pkt->info->get_property("data");
if (si && data && data->is_object()) {
    SrsAmf0Object* obj = data->to_objet();

    SrsAmf0Any* prop = obj->get_property("srs_server_ip");
    if (prop && prop->is_string()) {
        printf("Server IP: %s\n",prop->to_str().c_str());
    }

    prop = obj->get_property("srs_pid");
    if (prop && prop->is_number()) {
        printf("Server PID: %d\n,prop->to_number()); } }

看起来巨繁琐吧?快用GOLANG,如果换成GOLANG,可以用基本类型定义AMF0的基本类型,这样使用起来是这样:

pkt := or.NewConnectAppPacket()
pkt.CommandObject.Set("tcUrl",amf0.NewString(tcUrl))
pkt.CommandObject.Set("app",amf0.NewString(app))

var res *or.ConnectAppResPacket
if data,ok := res.Args.Get("data").(*amf0.Object); ok {
    if data,ok := data.Get("srs_server_ip").(*amf0.String); ok {
        fmt.Printf("Server IP: %s\n",string(*data))
    }
    if data,ok := data.Get("srs_pid").(*amf0.Number); ok {
        fmt.Printf("Server PID: %d\n,int(*data)) } }

区别在于:

  1. C++由于不能在基本类型上定义方法,导致必须创建struct或者class类型,有比较繁琐的类型转换和判断。
  2. GOLANG的类型判断,提供了ok的方式,一句话就能把类型转换弄好,而且接口和实现struct的对象可以重用变量名。
  3. 不必加很多类型判断,没有多余的变量,干净利索,需要维护的信息比较少。

爱生活,爱够浪(此处可以响起掌声了)~

猜你在找的Go相关文章