GOLANG將型別作為引數,用反射設定指標的指標,實現類似模板功能
在協議解析中,C++的模板有比較大的作用,有時候我們希望丟棄所有的包,只留下特定型別的包。參考SRS的程式碼SrsRtmpClient::connect_app2:
型別系統的設計, SrsConnectAppResPacket
繼承自SrsPacket
:
class SrsPacket;
class SrsConnectAppResPacket : public SrsPacket
協議棧提供了expect_message
模板函式,接收特定型別的包:
SrsCommonMessage* msg = NULL;
SrsConnectAppResPacket* pkt = NULL;
if ((ret = protocol.expect_message<SrsConnectAppResPacket>(&msg, &pkt)) != ERROR_SUCCESS) {
return ret;
}
SrsAmf0Any* data = pkt->info->get_property("data");
SrsAmf0EcmaArray* arr = data->to_ecma_array();
SrsAmf0Any* prop = arr->ensure_property_string("srs_server_ip");
string srs_server_ip = prop->to_str();
在向伺服器傳送了ConnectApp
後,就等待ConnectAppRes
響應包,丟棄所有的其他的。這個時候,型別SrsConnectAppResPacket
就作為了一個引數,也就是C++的模板。如果是GOLANG怎麼實現呢?沒有直接的辦法的,因為沒有泛型。
在GOLANG中,也需要定義個interface,參考Packet,當然也是有ConnectAppResPacket
實現了這個介面(Message
是原始訊息,它的Payload可以Unmarshal為Packet
):
type Message struct { Payload []byte }
type Packet interface {} // Message.Payload = Packet.Marshal()
type ConnectAppResPacket struct { Args amf0.Amf0 }
第一種方法,協議棧只需要收取Message,然後解析Message為Packet,收到packet後使用型別轉換,判斷不是自己需要的包就丟棄:
func (v *Protocol) ReadMessage() (m *Message, err error)
func (v *Protocol) DecodeMessage(m *Message) (pkt Packet, err error)
不過這兩個基礎的API,User在使用時,比較麻煩些,每次都得寫一個for迴圈:
var protocol *Protocol
for {
var m *Message
m,_ = protocol.ReadMessage()
var p Packet
p,_ = protocol.DecodeMessage(m)
if res,ok := p.(*ConnectAppResPacket); ok {
if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}
}
}
比較方便的做法,就是用回撥函式,協議棧需要提供個ExpectPacket
方法:
func (v *Protocol) ExpectPacket(filter func(m *Message, p Packet)(ok bool)) (err error)
這樣可以利用回撥函式可以訪問上面函式的作用域,直接轉換型別和設定目標型別的包:
var protocol *Protocol
var res *ConnectAppResPacket
_ = protocol.ExpectPacket(func(m *Message, p Packet) (ok bool){
res,ok = p.(*ConnectAppResPacket)
})
if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}
這樣已經比較方便了,不過還是需要每次都給個回撥函式。要是能直接這樣用就好了:
var protocol *Protocol
var res *ConnectAppResPacket
_ = protocol.ExpectPacket(&res)
if data, ok := res.Args.Get("data").(*amf0.EcmaArray); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
srs_server_ip = string(*data)
}
}
這樣也是可以做到的,不過協議棧函式要定義為:
func (v *Protocol) ExpectPacket(ppkt interface{}) (err error)
在函式內部,使用reflect判斷型別是否符合要求,設定返回值。程式碼參考ExpectPacket,下面是一個簡要說明:
func (v *Protocol) ExpectPacket(ppkt interface{}) (m *Message, err error) {
// 由於ppkt是**ptr, 所以取型別後取Elem(),就是*ptr,用來判斷是否實現了Packet介面。
ppktt := reflect.TypeOf(ppkt).Elem()
// ppktv是發現匹配的包後,設定值的。
ppktv := reflect.ValueOf(ppkt)
// 要求引數必須是實現了Packet,避免傳遞錯誤的值進來。
if required := reflect.TypeOf((*Packet)(nil)).Elem(); !ppktt.Implements(required) {
return nil,fmt.Errorf("Type mismatch")
}
for {
m, err = v.ReadMessage()
pkt, err = v.DecodeMessage(m)
// 判斷包是否是匹配的那個型別,如果不是就丟棄這個包。
if pktt = reflect.TypeOf(pkt); !pktt.AssignableTo(ppktt) {
continue
}
// 相當於 *ppkt = pkt,類似C++中對指標的指標賦值。
ppktv.Elem().Set(reflect.ValueOf(pkt))
break
}
return
}
遺憾的就是這個引數ppkt型別不能是Packet
,因為會有型別不匹配;也不能是*Packet
,因為在GOLANG中傳遞介面的指標也是不可以的,會導致型別錯誤(**ConnectAppResPacket
並不能匹配*Packet
);這個引數只能是interface{}
。不過用法也很簡單,只是需要注意引數的傳遞。
var res *ConnectAppResPacket
// 這是正確的做法,傳遞res指標的地址,相當於指標的指標。
_ = protocol.ExpectPacket(&res)
// 這是錯誤的做法,會在ExpectPacket檢查返回錯誤,沒有實現Packet介面
_ = protocol.ExpectPacket(res)
用起來還不錯。
相關文章
- 指標問題的一點體會(區別 [指向指標的指標] 與 [指標的指標] .) (轉)指標
- 指標常量和常量指標的區別指標
- 指向指標的指標指標
- Golang研學:在用好Golang指標型別Golang指標型別
- golang 指標型別引起的神奇 bugGolang指標型別
- 型別的本質:對變數、型別、指標的理解型別變數指標
- 詳解c++指標的指標和指標的引用C++指標
- golang中 值型別,指標,引用的區別Golang型別指標
- 讀讀objc原始碼(二):weak型別指標的實現OBJ原始碼型別指標
- 關於指標傳遞和指標的指標指標
- 如何理解指向指標的指標?指標
- 【C++系列】指標物件和物件指標的區別C++指標物件
- C/C++指向指標的指標C++指標
- 第 10 節:複合型別-5. 指標 -- 指標與指標變數 -8. 多級指標型別指標變數
- C++ 指標陣列與陣列指標的區別C++指標陣列
- golang指標Golang指標
- golang 指標Golang指標
- 預算指標 技術指標 操作引數指標
- 關於C++引用做為函式引數和指標作為函式引數C++函式指標
- 淺析weak指標的實現指標
- All I know about A/B Test (1) : 均值型指標與比值(率)型指標的計算區別指標
- C#指標型別C#指標型別
- 指標的刪除動作指標
- C++基於模板實現智慧指標C++指標
- C/C++—— 寫一個函式,它的引數為指向函式的指標,返回型別也為指向函式的指標C++函式指標型別
- 對 “C語言指標變數作為函式引數” 的個人理解C語言指標變數函式
- go 方法接受者 是指標型別和非指標型別的 區別Go指標型別
- 利用指標實現strncmp函式功能指標函式
- Golang 學習——陣列指標和指標陣列的區別Golang陣列指標
- C++智慧指標模板類複習C++指標
- 指標的申明和定義指標
- C++ 在 Visual Studio 如何將指標星號設定成靠近變數而不是型別C++指標變數型別
- 引用與指標的區別指標
- c指標型別的作用指標型別
- C++智慧指標作為成員變數C++指標變數
- Oracle遊標型別作為傳入傳出引數Oracle型別
- 多型體驗,和探索爺爺類指標的多型性多型指標
- 用DataFrame作為容器分析市場行情指標指標