GOLANG比较方便的地方

原创 2015年11月20日 10:22:43

GOLANG比较方便的地方

GO在自定义类型,数组,变量覆盖,函数变量,bufio等方面比较方便。

IsXXX

GO中很方便在基本类型上定义新的类型,这个虽然是个小东西,但是有时候非常好用。

经常需要定义这样的函数,譬如RTMP的消息IsAudioIsVideo等等。如果是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()

有什么差异呢?差异有下面几点

  1. GO少了一个结构体,MessageHeader,相反直接MessageType就可以定义具体的函数。
  2. GO的messageType是强类型的,而C++用的是宏定义,只能通过注释说明(当然可以用枚举,但是枚举还是int型),如果使用msg.messageType = int(9)会报错的,因为类型不匹配。
  3. 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环境设置

关于go的特点,参考:http://blog.csdn.net/win_lin/article/details/18236737GO环境下载linux 64位的go的包:https://storage...

GOLANG使用Context实现传值、超时和取消

原文:https://gocn.io/article/373GO1.7之后,新增了context.Context这个package,实现goroutine的管理。Context基本的用法参考GOLAN...

RaspberryPi自动连接多个无线,并且自动汇报IP

如何让树莓派自动连接Wifi,并且将自己的IP汇报给公网(或者内网)服务器呢?每次都连接显示器,或者串口TTL去查IP,觉得太麻烦了。自动连接Wifi我写了个程序raspi-wlan,可以自动连接Wi...

Golang在视频直播平台的高性能实践(含PPT下载)

熊猫 TV 是一家视频直播平台,先介绍下我们系统运行的环境,下面这 6 大服务只是我们几十个服务中的一部分,由于并发量与重要性比较高,所以成为 golang 小试牛刀的首批高性能高并发服务。 ...

流媒体视频直播方案

流媒体视频直播方案   背景  在视频直播领域,有不同的商家提供各种的商业解决方案,包括软硬件设备,摄像机,编码器,流媒体服务器等。本文讲解如何使用一系列免费工具,打造一套视频直播方案。  ...

RTMP协议中文翻译(首发)

翻译:阿宝  更新:2016-09-11  来源:彩色世界(https://blog.hz601.org/2016/07/03/real-time-messaging-protocol/index...

使用wireshark抓取bilibili直播的真实地址

使用wireshark抓取bilibili直播的真实地址 一、 概述     我之前测试过bilibili的直播(主播使用OBS-studio推送),延迟是相当低,大概就是3s-4s延迟,...

强大不代表完美——C++几个不方便的地方。

说明:本文章纯属个人观点,不保证绝对正确,欢迎大家批评和指正,同时我自己也会对本文不断的更新和完善。 引言: 本文自工作以来使用过C++、Java、Python、Groovy、Objective ...

修改Eclipse的默认debugKeyStore为带签名的keyStore,方便调试需要验证MD5值的地方

修改Eclipse的默认debugKeyStore为带签名的keyStore文件,方便调试需要验证MD5值的地方....
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:GOLANG比较方便的地方
举报原因:
原因补充:

(最多只允许输入30个字)