GOLANGの便利なところ
28001 ワード
GOLANGの便利なところ
GOはカスタムタイプ、配列、変数オーバーライド、関数変数、bufioなどの面で便利です.
IsXXX
GOでは基本タイプで新しいタイプを定義するのに便利ですが、これは小物ですが、時々とても使いやすいです.
RTMPのメッセージ
GOの作り方を見てみましょう.
最終的にはこうなります.
どんな違いがありますか.違いは以下の点ですGOには構造体が1つ少なくなっており、MessageHeaderとは逆にMessageTypeで具体的な関数を定義することができる. GOのmessageTypeは強いタイプで、C++はマクロ定義で、注釈でしか説明できません(もちろん列挙できますが、列挙はint型です). GOは、必要に応じて定義された基本タイプに直接変換することができ、例えば
確かに使いやすい.
AMF0
このカスタムタイプのシステムは、CとC++の中で本当に煩わしくて、1つの
GOのバージョンを比較:
Number、Boolean、その他の複雑なタイプを考慮すれば、GOのタイプシステムは大きく役立ちます.
はいれつ
GO中
RTMPパケットのフィールドを比較するには、C++の順に解析する必要があります.
GOで直接できます.
GOはこの関数を定義できます.
配列のみがサポートされていれば可能ですが、この
再定義変数
GOでは変数を再定義し、前の変数を上書きすることができます.特にベースクラスをサブクラスに変換する場合に便利です.C++のコードを参照:
GOでは、switchのタイプを変換するとサブクラスになります.
一般的なerrも、err変数を上書きして返す場合があります.
多くの戻り値、戻り値の名前、変数の上書き、これらを組み合わせると便利で、変数の役割ドメインを縮小します.
変数オーバーライド
変数の役割ドメインが小さいほどメンテナンスが難しくなります.最適なのは局所変数であり,次いで関数内の局所変数,次いでクラス変数,次いでモジュール変数である.ローカル変数は、役割ドメインを手動で縮小するために使用される場合があります.SRSのC++バージョンでは、このようなコードがよくあります.
GOでifでこの変数を定義できます.
変数のタイプ変換を考慮すると、例えば文字列から整数型に、インタフェースが具体的なタイプになると、C++で変数を再定義する必要があります.
一方、GOでは再利用可能であり、
この全編は1つのpしかなく、最初はpがstringで、それからint 64で、最後に戻ったときにintに変換されました.
関数変数
関数変数は1つの構造を定義するのを省いて、関数を加えて、変数を構築して、呼び出して、このような面倒な過程は1つの変数定義だけでいいです.
局所関数変数、使いやすい~
サブクラス変換
受け取ったパケットを処理する必要がある場合、異なるパケットが異なる処理を行う場合、GOは非常に簡単で、タイプ変換、変数オーバーライド、役割ドメインなどを組み合わせています.
以下はSRS処理FMLEのリリース開始前のパッケージです.
GOは簡単です.
点滴の便利さは、最終的には大きなメリットがあります.
bufio
RTMPを解析するとき、何バイトか読んで出てくることがありますが、使うとは限りません.次のパッケージに属するかもしれませんが、このときはbufioで使いやすいです.
bufioはバッファ付きioであり、送信時にも使用できますが、writevはbufioよりも効率的にメモリコピーを回避できることです.
writevについてはforkのプロジェクトgo-vectorioを参考にすることができます.
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()
どんな違いがありますか.違いは以下の点です
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を参考にすることができます.