GOLANG使用簡單型別,在協議解析的妙用
在協議解析中,經常需要用到轉換不同的含義,比如聲音的取樣率,在FLV中定義和AAC中定義是不同的。在FLV中只有4中取樣率5512, 11025, 22050, 44100
。而在AAC中有16種取樣率96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 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);
有什麼麻煩的呢?
- 函式和型別之間沒有關係,每次使用的時候都得去翻手冊啊翻手冊。
- 如果定義成一個struct,那轉換的時候又太麻煩了。
還能不能愉快的玩耍呢?用GOLANG吧!先看用法:
var sampleRate AudioSamplingRate
sampleRate.From(samplingFrequencyIndex)
fmt.Printf("SampleRate=%d/%v\n", sampleRate, 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: 程式碼參考go-oryx-lib flv.
有幾個地方非常不同:
- 雖然GOLANG只是在uint8上面加了函式,但是使用起來方便很多了,以前在C++中用這兩個列舉,每次都要跳到列舉的定義來看對應的函式是什麼。
- GOLANG的switch比較強大,可以case好幾個值,和C++的if有點想,但是GOLANG的case更直觀,知道這幾個值會被轉換成另外的值,而if讀起來像是將一個範圍的值轉換,不好懂。
- 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))
}
}
區別在於:
- C++由於不能在基本型別上定義方法,導致必須建立struct或者class型別,有比較繁瑣的型別轉換和判斷。
- GOLANG的型別判斷,提供了ok的方式,一句話就能把型別轉換弄好,而且介面和實現struct的物件可以重用變數名。
- 不必加很多型別判斷,沒有多餘的變數,乾淨利索,需要維護的資訊比較少。
實現起來更舒服,基本型別不用定義struct:
type String string
func (v *String) Size() int {}
func (v *String) UnmarshalBinary(data []byte) (err error) {}
func (v *String) MarshalBinary() (data []byte, err error) {}
type Object struct {}
func (v *Object) Size() int {}
func (v *Object) UnmarshalBinary(data []byte) (err error) {}
func (v *Object) MarshalBinary() (data []byte, err error) {}
Remark:程式碼參考go-oryx-lib amf0.
更神奇的是,因為Object、EcmaArray和StrictArray都是類似的結構,但是有些細微的差異,因此使用GOLANG的結構體巢狀可以很直接的解決問題:
type Object struct {
objectBase
eof objectEOF
}
type EcmaArray struct {
objectBase
count uint32
eof objectEOF
}
type StrictArray struct {
objectBase
count uint32
}
可以對比下SRS的實現,C++可以採用繼承,而GOLANG直接組合那些基本的單元。
愛生活,愛夠浪(此處可以響起掌聲了)~
相關文章
- Golang切片的三種簡單使用方式及區別Golang
- 簡單談談DNS協議DNS協議
- 《圖解HTTP》——簡單的HTTP協議圖解HTTP協議
- 02 前端HTTP協議(圖解HTTP) 之 簡單的HTTP協議前端HTTP協議圖解
- TCP對應的協議和UDP對應的協議(簡單概述)TCP協議UDP
- Python:多型、協議和鴨子型別Python多型協議型別
- 協程的簡單使用
- 使用go net實現簡單的redis通訊協議YWSVGoRedis協議
- golang — mgo解析各種資料型別分析Golang資料型別
- http協議Content-Type型別表HTTP協議型別
- TCP/IP協議常見漏洞型別TCP協議型別
- Golang《基於 MIME 協議的郵件資訊解析》部分實現Golang協議
- Date簡單型別的setter注入型別
- Java引用型別解析:掌握強引用、軟引用、弱引用和幻象引用的妙用Java型別
- ARP(地址解析協議)和RARP(逆地址解析協議)協議
- Dubbo的簡單使用以及Triple協議的Streaming通訊的實現協議
- WebSocket的Frame協議解析Web協議
- Gossip協議和Grpc協議的區別Go協議RPC
- 不簡單的基本資料型別資料型別
- Zookeeper的ZAB協議與Paxos協議區別協議
- golang的型別轉換Golang型別
- 在 .NET 中使用 OPC UA 協議協議
- 在nginx中使用proxy protocol協議NginxProtocol協議
- 簡單型別與複雜型別及原型鏈型別原型
- 【HITCON 2017】SSRFme——最簡單偽協議思路協議
- 簡單網路管理協議SNMP(史上最全)協議
- UDP 協議簡單瞭解及應用UDP協議
- ARP 地址解析協議協議
- HTTP 協議完全解析HTTP協議
- 記錄 golang 命令列庫 cobra 的簡單使用Golang命令列
- netty系列之:protobuf在UDP協議中的使用NettyUDP協議
- 在flutter中使用hooks的簡單使用FlutterHook
- zookeeper核心之ZAB協議就這麼簡單!協議
- golang資料型別基本介紹與使用Golang資料型別
- JAVA 解析html 型別字串(使用jsoup)JavaHTML型別字串JS
- Golang 引用型別-mapGolang型別
- 在Golang中使用泛型reduce函式 - gosamplesGolang泛型函式
- Http與Https的區別(精簡版包含協議說明)HTTP協議
- 列舉型別在JPA中的使用型別