GOLANG比较方便的地方
2015-11-20 10:22
525 查看
GOLANG比较方便的地方
GO在自定义类型,数组,变量覆盖,函数变量,bufio等方面比较方便。IsXXX
GO中很方便在基本类型上定义新的类型,这个虽然是个小东西,但是有时候非常好用。经常需要定义这样的函数,譬如RTMP的消息
IsAudio,
IsVideo等等。如果是C++就需要定义个结构体,然后加函数:
class SrsCommonMessage { public: SrsMessageHeader header; }; class SrsMessageHeader { public: int8_t message_type; public: bool is_audio(); bool is_video(); }; #define RTMP_MSG_AudioMessage 8 // 0x08 #define RTMP_MSG_VideoMessage 9 // 0x09 bool SrsMessageHeader::is_audio() { return message_type == RTMP_MSG_AudioMessage; } bool SrsMessageHeader::is_video() { return message_type == RTMP_MSG_VideoMessage; }
来看看GO中的做法:
type RtmpMessageType uint8 type RtmpMessage struct { messageType RtmpMessageType } const ( RtmpMsgAudioMessage RtmpMessageType = 8 // 0x08 RtmpMsgVideoMessage RtmpMessageType = 9 // 0x09 ) func (v RtmpMessageType) isAudio() bool { return v == RtmpMsgAudioMessage } func (v RtmpMessageType) is_video() { return v == RtmpMsgVideoMessage }
最终用起来会是这样:
// c++ msg.header.message_type = RTMP_MSG_AudioMessage msg.header.is_audio() // go msg.messageType = RtmpMsgAudioMessage msg.messageType.isAudio()
有什么差异呢?差异有下面几点
GO少了一个结构体,MessageHeader,相反直接MessageType就可以定义具体的函数。
GO的messageType是强类型的,而C++用的是宏定义,只能通过注释说明(当然可以用枚举,但是枚举还是int型),如果使用
msg.messageType = int(9)会报错的,因为类型不匹配。
GO如果有必要可以直接转成定义的基本类型,譬如
uint8(msg.messageType)是没有问题的。
用起来确实方便。
AMF0
这种自定义类型系统,在C和C++中真的很烦,比较一个amf0 string的实现。
//c++ class SrsAmf0Any { public: char marker; virtual bool is_string(); virtual std::string to_str(); static SrsAmf0Any* str(const char* value = NULL); }; class SrsAmf0String : public SrsAmf0Any { public: std::string value; }; // usage SrsAmf0Any* str = SrsAmf0Any::str("oryx"); if (str->is_str()) { cout << str->to_str(); }
比较GO的版本:
// go type Amf0Any interface { } type Amf0String string func NewAmf0String(v string) *Amf0String { } // usage, value str := Amf0String("oryx") fmt.Println(str) // usage, pointer Amf0Any str = NewAmf0String("oryx") if v,ok := str.(*Amf0String); ok { fmt.Println(*v) }
如果再考虑Number、Boolean,还有其他复杂的类型,GO的类型系统会有很大的帮助。
数组
GO中...可以传多个值,和C中的printf很像,不过这个在函数中实际上是数组,就非常方便了,可以传给其他的函数。
比较下RTMP包中的字段需要依次解析,C++这么做:
int SrsCreateStreamPacket::encode_packet(SrsBuffer* stream) { int ret = ERROR_SUCCESS; if ((ret = srs_amf0_write_string(stream, command_name)) != ERROR_SUCCESS) { return ret; } if ((ret = srs_amf0_write_number(stream, transaction_id)) != ERROR_SUCCESS) { return ret; } if ((ret = srs_amf0_write_null(stream)) != ERROR_SUCCESS) { return ret; } return ret; }
在GO中可以直接这么做:
func (v *RtmpCreateStreamPacket) UnmarshalBinary(data []byte) (err error) { return core.Unmarshals(bytes.NewBuffer(data), &v.Name, &v.TransactionId, &v.Command) }
GO可以定义这个函数:
func Unmarshals(b *bytes.Buffer, o ...UnmarshalSizer) (err error) { for _, e := range o { if b.Len() == 0 { break } if e == nil { continue } if rv := reflect.ValueOf(e); rv.IsNil() { continue } if err = e.UnmarshalBinary(b.Bytes()); err != nil { return } b.Next(e.Size()) } return }
如果只支持数组,也可以,但是这种
...确实非常非常方便。
再定义变量
GO中可以再定义变量,覆盖之前的变量。特别是基类转换成子类时很方便。参考C++的代码:if (dynamic_cast<SrsCreateStreamPacket*>(pkt)) { return identify_create(dynamic_cast<SrsCreateStreamPacket*>(pkt), stream_id, type, stream_name, duration); } if (dynamic_cast<SrsFMLEStartPacket*>(pkt)) { return identify_fmle(dynamic_cast<SrsFMLEStartPacket*>(pkt), type, stream_name); } if (dynamic_cast<SrsPlayPacket*>(pkt)) { return identify_play(dynamic_cast<SrsPlayPacket*>(pkt), type, stream_name, duration); }
在GO中,switch的类型转换后就是子类了:
switch p := p.(type) { case RtmpCreateStreamPacket: return v.identifyCreateStream(sid, p) case RtmpFMLEStartPacket: return v.identifyFmlePublish(sid, p) case RtmpPlayPacket: return v.identifyPlay(sid, p) }
常见的err也是,有时候可以覆盖err变量后返回:
func parse(b []byte) (err error) { if vb,err := xxx(); err != nil { return err } else if len(vb) > 0 { _ = vb // use vb } return }
多返回值,返回值命名,变量覆盖,这几个组合起来很方便,缩小变量作用域。
变量覆盖
变量的作用域越小越好,越大越难维护。最好的是局部变量,其次是函数内局部变量,然后是类变量,接着是模块变量。局部变量有时候用起来得手动缩小作用域,在SRS的C++版本中经常会有这样代码:
if (true) { variable xxx; }
在GO里面可以在if上定义这个变量:
if xxx; xxx { }
如果在考虑变量的类型变换,譬如从字符串变成整型,接口变具体类型,在C++中就需要再定义变量:
variable xxx yyy = dynamic_cast<T*>(xxx)
而在GO中可以重用,考虑
RtmpRequest获取Port,从URL中解析的代码:
func (v *RtmpRequest) Port() int { if _,p,err := net.SplitHostPort(v.Url.Host); err != nil { return core.RtmpListen } else if p,err := strconv.ParseInt(p,10,32); err != nil { return core.RtmpListen } else if p <= 0 { return core.RtmpListen } else { return int(p) } }
这通篇就只有一个p,最开始p是个string,然后是个int64,最后返回时转换成了int。
函数变量
函数变量省去了定义一个结构,加函数,构造变量,调用,这么麻烦的过程只需要一个变量定义就可以。考虑go-oryx中的RTMP URL的解析,定义了两个函数变量:
// parse the rtmp request object from tcUrl/stream?params // to finger it out the vhost and url. func (r *RtmpRequest) Reparse() (err error) { // convert app...pn0...pv0...pn1...pv1...pnn...pvn // to (without space): // app ? pn0=pv0 && pn1=pv1 && pnn=pvn // where ... can replaced by ___ or ? or && or & mfn := func(s string) string { r := s matchs := []string{"...", "___", "?", "&&", "&"} for _, m := range matchs { r = strings.Replace(r, m, "...", -1) } return r } ffn := func(s string) string { r := mfn(s) for first := true; ; first = false { if !strings.Contains(r, "...") { break } if first { r = strings.Replace(r, "...", "?", 1) } else { r = strings.Replace(r, "...", "&&", 1) } if !strings.Contains(r, "...") { break } r = strings.Replace(r, "...", "=", 1) } return r } // format the app and stream. r.TcUrl = ffn(r.TcUrl) r.Stream = ffn(r.Stream)
局部函数变量,很好用~
子类转换
如果需要处理收到的包,不同的包做不同的处理,GO做出来的就是会简单非常多,结合了类型变换、变量覆盖、作用域等等。下面是SRS处理FMLE开始发布前的包:
int SrsRtmpServer::start_fmle_publish(int stream_id) { int ret = ERROR_SUCCESS; double fc_publish_tid = 0; if (true) { SrsCommonMessage* msg = NULL; SrsFMLEStartPacket* pkt = NULL; if ((ret = expect_message<SrsFMLEStartPacket>(&msg, &pkt)) != ERROR_SUCCESS) { return ret; } SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsFMLEStartPacket, pkt); fc_publish_tid = pkt->transaction_id; } if (true) { SrsFMLEStartResPacket* pkt = new SrsFMLEStartResPacket(fc_publish_tid); if ((ret = protocol->send_and_free_packet(pkt, 0)) != ERROR_SUCCESS) { return ret; } } double create_stream_tid = 0; if (true) { SrsCommonMessage* msg = NULL; SrsCreateStreamPacket* pkt = NULL; if ((ret = expect_message<SrsCreateStreamPacket>(&msg, &pkt)) != ERROR_SUCCESS) { return ret; } SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsCreateStreamPacket, pkt); create_stream_tid = pkt->transaction_id; } if (true) { SrsCreateStreamResPacket* pkt = new SrsCreateStreamResPacket(create_stream_tid, stream_id); if ((ret = protocol->send_and_free_packet(pkt, 0)) != ERROR_SUCCESS) { return ret; } } if (true) { SrsCommonMessage* msg = NULL; SrsPublishPacket* pkt = NULL; if ((ret = expect_message<SrsPublishPacket>(&msg, &pkt)) != ERROR_SUCCESS) { return ret; } SrsAutoFree(SrsCommonMessage, msg); SrsAutoFree(SrsPublishPacket, pkt); } if (true) { SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->command_name = RTMP_AMF0_COMMAND_ON_FC_PUBLISH; pkt->data->set(StatusCode, SrsAmf0Any::str(StatusCodePublishStart)); pkt->data->set(StatusDescription, SrsAmf0Any::str("Started publishing stream.")); if ((ret = protocol->send_and_free_packet(pkt, stream_id)) != ERROR_SUCCESS) { return ret; } } if (true) { SrsOnStatusCallPacket* pkt = new SrsOnStatusCallPacket(); pkt->data->set(StatusLevel, SrsAmf0Any::str(StatusLevelStatus)); pkt->data->set(StatusCode, SrsAmf0Any::str(StatusCodePublishStart)); pkt->data->set(StatusDescription, SrsAmf0Any::str("Started publishing stream.")); pkt->data->set(StatusClientId, SrsAmf0Any::str(RTMP_SIG_CLIENT_ID)); if ((ret = protocol->send_and_free_packet(pkt, stream_id)) != ERROR_SUCCESS) { return ret; } } return ret; }
GO就是会很简单:
func (v *RtmpConnection) FmleStartPublish() (err error) { return v.read(FmlePublishTimeout, func(m *RtmpMessage) (loop bool, err error) { var p RtmpPacket if p, err = v.stack.DecodeMessage(m); err != nil { return } switch p := p.(type) { case *RtmpFMLEStartPacket: res := NewRtmpFMLEStartResPacket().(*RtmpFMLEStartResPacket) res.TransactionId = p.TransactionId if err = v.write(FmlePublishTimeout, res, 0); err != nil { return } return true, nil case *RtmpCreateStreamPacket: // increasing the stream id. v.sid++ res := NewRtmpCreateStreamResPacket().(*RtmpCreateStreamResPacket) res.TransactionId = p.TransactionId res.StreamId = Amf0Number(v.sid) if err = v.write(FmlePublishTimeout, res, 0); err != nil { return } return true, nil case *RtmpPublishPacket: res := NewRtmpOnStatusCallPacket().(*RtmpOnStatusCallPacket) res.Name = Amf0String(Amf0CommandFcPublish) res.Data.Set(StatusCode, NewAmf0String(StatusCodePublishStart)) res.Data.Set(StatusDescription, NewAmf0String("Started publishing stream.")) if err = v.write(FmlePublishTimeout, res, v.sid); err != nil { return } res = NewRtmpOnStatusCallPacket().(*RtmpOnStatusCallPacket) res.Data.Set(StatusLevel, NewAmf0String(StatusLevelStatus)) res.Data.Set(StatusCode, NewAmf0String(StatusCodePublishStart)) res.Data.Set(StatusDescription, NewAmf0String("Started publishing stream.")) res.Data.Set(StatusClientId, NewAmf0String(RtmpSigClientId)) if err = v.write(FmlePublishTimeout, res, v.sid); err != nil { return } core.Trace.Println("FMLE start publish ok.") return false, nil default: return true, nil } }) }
点滴的方便,最终会有大的好处。
bufio
解析RTMP时,有时候会读几个字节出来,但是不见得是要用的,可能属于下一个包的,这个时候用bufio就很好用了。bufio就是带缓冲区的io,发送时也可以使用,不过writev比bufio更高效在于可以避免内存拷贝。
关于writev可以参考我fork的一个项目go-vectorio。
相关文章推荐
- GO方便的类型系统
- 继续完成昨天的第一个点:更改DJANGO的ADMIN后台的表单显示
- HOTPOWER.【专注游戏界面外包】/接游戏界面外包/logo外包/icon
- GO语言零基础入门资料整理
- google三大论文
- Golang的语法
- POJ 2000 Gold Coins
- svg绘制logo
- <5> go 上下文传递context
- Learning ROS for Robotics Programming Second Edition学习笔记(三) indigo rplidar rviz slam
- Learning ROS for Robotics Programming Second Edition学习笔记(三) indigo rplidar rviz slam
- Learning ROS for Robotics Programming Second Edition学习笔记(三) indigo rplidar rviz slam
- Django Web部署平台 推荐
- <4> go 工厂
- <3> go 枚举
- cpongo
- django template 自定义filter的使用
- Mongoose6.0源码分析(3)-重要结构体
- golang 大文件分割
- Google breakpad