GOLANGの便利なところ

28001 ワード

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には構造体が1つ少なくなっており、MessageHeaderとは逆にMessageTypeで具体的な関数を定義することができる.
  • GOのmessageTypeは強いタイプで、C++はマクロ定義で、注釈でしか説明できません(もちろん列挙できますが、列挙はint型です).
  • GOは、必要に応じて定義された基本タイプに直接変換することができ、例えばmsg.messageType = int(9)は問題ありません.

  • 確かに使いやすい.
    AMF0
    このカスタムタイプのシステムは、CとC++の中で本当に煩わしくて、1つのuint8(msg.messageType)の実現を比較します.
    //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中amf0 stringは複数の値を伝えることができ、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(pkt)) {
        return identify_create(dynamic_cast(pkt), stream_id, type, stream_name, duration);
    }
    if (dynamic_cast(pkt)) {
        return identify_fmle(dynamic_cast(pkt), type, stream_name);
    }
    if (dynamic_cast(pkt)) {
        return identify_play(dynamic_cast(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(xxx)

    一方、GOでは再利用可能であり、...を考慮して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)
        }
    }

    この全編は1つのpしかなく、最初はpがstringで、それからint 64で、最後に戻ったときにintに変換されました.
    関数変数
    関数変数は1つの構造を定義するのを省いて、関数を加えて、変数を構築して、呼び出して、このような面倒な過程は1つの変数定義だけでいいです.RtmpRequestのRTMP URLの解析を考慮して、2つの関数変数を定義します.
    // 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を参考にすることができます.