GOLANG实现类似C++模板,返回符合类型的对象

GOLANG实现类似C++模板,返回符合类型的对象,第1张

概述原文参考:https://gocn.io/article/319 在协议解析中,C++的模板有比较大的作用,有时候我们希望丢弃所有的包,只留下特定类型的包。参考SRS的代码SrsRtmpClient::connect_app2: 类型系统的设计, SrsConnectAppResPacket继承自SrsPacket: class SrsPacket;class SrsConnectAppResP

原文参考:https://gocn.io/article/319

在协议解析中,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 *Protocolfor {    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 *Protocolvar 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 *Protocolvar res *ConnectAppResPacket_ = protocol.ExpectPacket(&res)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中传递接口的引用本身是很奇怪的;这个参数只能是interface{}。不过用法也很简单,只是需要注意参数的传递。

var res *ConnectAppResPacket// 这是正确的做法,传递res指针的地址,相当于指针的指针。_ = protocol.ExpectPacket(&res)// 这是错误的做法,会在ExpectPacket检查返回错误,没有实现Packet接口_ = protocol.ExpectPacket(res)

用起来还不错。

总结

以上是内存溢出为你收集整理的GOLANG实现类似C++模板,返回符合类型的对象全部内容,希望文章能够帮你解决GOLANG实现类似C++模板,返回符合类型的对象所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1278009.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-09
下一篇2022-06-09

发表评论

登录后才能评论

评论列表(0条)

    保存