redis 使用GETRANGE 来获取一组bitmap状态

luw2007 发表了文章 • 0 个评论 • 21 次浏览 • 12 小时前 • 来自相关话题

在使用redis 的 bitmap 来存储业务状态。经常需要顺序获取一个范围内bitmap。 业务代码里,一般会使用pipeline来优化查询逻辑。伪代码如下

查看全部
					

在使用redis 的 bitmap 来存储业务状态。经常需要顺序获取一个范围内bitmap。
业务代码里,一般会使用pipeline来优化查询逻辑。伪代码如下


def get_bits_pipe(pipe, key, cur, num=10):
""" 使用pipeline gitbit"""
for i in xrange(num + 1):
pipe.getbit(key, cur + i)
return [cur + i for i, v in enumerate(pipe.execute()) if v]

这里减少了和redis的数据交换。提高了查询性能,但是随着查询数量的增加,性能急剧下降。


其实 redis 有更高效的方式来获取顺序的bitmap。就是通过 getrange来获取bitmap所在的字符串,然后计算出每位的值。需要注意:由于redis 的 bit 并非按照自然二进制位增加,
比如:'\x01' 对应的ascii 为1。其二进制表示'1', 在redis中表示offset为 7。感兴趣可以看看redis的实现逻辑。


以下提供golang 和python版本的样例。
实现代码: https://gist.github.com/luw2007/692d4a615dd71aa2bfa42190ad6a12e3

golang 白痴求各位大神指点迷津

haohongfan 回复了问题 • 6 人关注 • 6 个回复 • 408 次浏览 • 18 小时前 • 来自相关话题

GOLANG使用Context管理关联goroutine

winlin 发表了文章 • 6 个评论 • 153 次浏览 • 3 天前 • 来自相关话题

一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如http.Server.ListenAndServe这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否... 查看全部

一般一个业务很少不用到goroutine的,因为很多方法是需要等待的,例如http.Server.ListenAndServe这个就是等待的,除非关闭了Server或Listener,否则是不会返回的。除非是一个API服务器,否则肯定需要另外起goroutine发起其他的服务,而且对于API服务器来说,在http.Handler的处理函数中一般也需要起goroutine,如何管理这些goroutine,在GOLANG1.7提供context.Context


先看一个简单的,如果启动两个goroutine,一个是HTTP,还有个信号处理的收到退出信号做清理:


wg := sync.WaitGroup{}
defer wg.Wait()

wg.Add(1)
go func() {
defer wg.Done()

ss := make(os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
for s := ss {
fmt.Println("Got signal", s)
break
}
}()

wg.Add(1)
go func() {
defer wg.Done()

svr := &http.Server{ Addr:":8080", Handler:nil, }
fmt.Println(svr.ListenAndServe())
}

很清楚,起了两个goroutine,然后用WaitGroup等待它们退出。如果它们之间没有交互,不互相影响,那真的是蛮简单的,可惜这样是不行的,因为信号的goroutine收到退出信号后,应该通知server退出。暴力一点的是直接调用svr.Close(),但是如果有些请求还需要取消怎么办呢?最好用Context了:


wg := sync.WaitGroup{}
defer wg.Wait()

ctx,cancel := context.WithCancel(context.Background())

wg.Add(1)
go func() {
defer wg.Done()

ss := make(chan os.Signal, 0)
signal.Notify(ss, syscall.SIGINT, syscall.SIGTERM)
select {
case <- ctx.Done():
return
case s := <- ss:
fmt.Println("Got signal", s)
cancel() // 取消请求,通知用到ctx的所有goroutine
return
}
}()

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

svr := &http.Server{ Addr:":8080", Handler:nil, }

go func(){
select {
case <- ctx.Done():
svr.Close()
}
}

fmt.Println(svr.ListenAndServe())
}

这个方式可以在新开goroutine时继续使用,譬如新加一个goroutine,里面读写了UDPConn:


wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

var conn *net.UDPConn
if conn,err = net.Dial("udp", "127.0.0.1:1935"); err != nil {
fmt.Println("Dial UDP server failed, err is", err)
return
}

fmt.Println(UDPRead(ctx, conn))
}()

UDPRead = func(ctx context.Context, conn *net.UDPConn) (err error) {
wg := sync.WaitGroup{}
defer wg.Wait()

ctx, cancel := context.WithCancel(ctx)

wg.Add(1)
go func() {
defer wg.Done()
defer cancel()

for {
b := make([]byte, core.MTUSize)
size, _, err := conn.ReadFromUDP(b)
// 处理UDP包 b[:size]
}
}()

select {
case <-ctx.Done():
conn.Close()
}
return
}

如果只是用到HTTP Server,可以这么写:


func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}
go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
})

fmt.Println(server.ListenAndServe())
}

如果需要提供一个API来让服务器退出,可以这么写:


func run(ctx contex.Context) {
server := &http.Server{Addr: addr, Handler: nil}

ctx, cancel := context.WithCancel(ctx)
http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
cancel() // 使用局部的ctx和cancel
})

go func() {
select {
case <-ctx.Done():
server.Close()
}
}()

fmt.Println(server.ListenAndServe())
}

使用局部的ctx和cancel,可以避免cancel传入的ctx,只是影响当前的ctx。

GOLANG使用嵌入结构实现接口

winlin 发表了文章 • 0 个评论 • 97 次浏览 • 3 天前 • 来自相关话题

考虑一个Packet接口,一般会返回一个Header,例如:

type PacketHeader struct {
    ID uint32
    Timesta... 			查看全部
					

考虑一个Packet接口,一般会返回一个Header,例如:


type PacketHeader struct {
ID uint32
Timestamp uint64
}

type Packet interface {
encoding.BinaryMarshaler
encoding.BinaryUnmarshaler
Header() *PacketHeader
}

如果是OO的语言,一般会有一个基类,里面包含了Header和实现这个Header:


class BasePacket : public Packet {
protected:
PacketHeader h;
public:
virtual Header() *PacketHeader;
};

class HandshakePacket : public BasePacket {
};

在子类中就都实现了这个Header()方法了,在GOLANG同样可以做到,通过在Header中定义方法,在Packet中包含Header就可以。


func (v *PacketHeader) Header() *PakcetHeader {
return v
}

type HandshakePacket struct {
PacketHeader
}

看起来还差不多的,都可以实现,golang只是代码少一点,清晰一点点而已。考虑要添加一些辅助函数,譬如给Packet添加是否是紧急类型的包,那OO语言得做一次代理:


type Packet interface {
IsErgency() bool
}

class BasePacketHeader {
public:
bool IsErgency() {
return realtime < 3;
}
}

class BasePacket {
public:
bool IsErgency() {
return h.IsErgency();
}
}

而在GOLANG中,只需要在Header实现就好了:


func (v *PacketHeader) IsErgency() bool {
return v.realtime < 3
}

更高级的可以直接嵌入接口。譬如context.Context的实现,cancelCtx直接嵌入了一个接口:


type cancelCtx struct {
Context

通过指定类型,或者初始化的顺序初始化struct


func newCancelCtx(parent Context) cancelCtx {
return cancelCtx{
Context: parent,
done: make(chan struct{}),
}
}

结构嵌套的方式,让组合实现起来非常便捷,避免频繁的代理。

大家说说看都用啥写Go

lmw 回复了问题 • 62 人关注 • 78 个回复 • 3849 次浏览 • 4 天前 • 来自相关话题

怎么获取beego查询的的结果,Students这个里面的结果为啥是初始化的?

astaxie 回复了问题 • 3 人关注 • 3 个回复 • 106 次浏览 • 4 天前 • 来自相关话题

使用两个context实现CLOSE包的超时等待

winlin 发表了文章 • 0 个评论 • 90 次浏览 • 4 天前 • 来自相关话题

在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。查看全部

在UDP中,一般发送者发送包后,如果一定的时间对方没有收到,就需要重传。例如UDP实现握手的过程,如果握手的包,比如RTMFP协议的IHELLO,发送给对方后,如果一定1秒没有收到,就应该重发一次,然后等3秒、6秒、9秒,如果最后没有收到就是超时了。


最后一个Close包,发送者不能等待这么长的时间,所以需要设置一个较短的时间做超时退出。一般收发都是一个context,在最后这个Close包时,收到ctx.Done也不能立刻退出,因为还需要稍微等待,譬如600毫秒如果没有收到响应才能退出。


一个可能的实现是这样:


in := make(chan []byte)

func Close(ctx context.Context) (err error) {
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}

但是这个问题在于,在程序退出时,一般都会cancel ctx然后调用Close方法,这个地方就不会等待任何的超时,就打印"Program quit"然后返回了。解决方案是用另外一个context。但是如何处理之前的ctx的done呢?可以再起一个goroutine做同步:


in := make(chan []byte)

func Close(ctx context.Context) (err error) {
ctxRead,cancelRead := context.WithCancel(context.Background())
go func(){ // sync ctx with ctxRead
select {
case <-ctxRead.Done():
case <-ctx.Done():
select {
case <-ctxRead.Done():
case <-time.After(600*time.Milliseconds):
cancelRead()
}
}
}()

ctx = ctxRead // 下面直接用ctxRead。
timeous := ... // 1s,3s,6s,9s...
for _, to := range timeouts {
// 发送给对方WriteToUDP("CLOSE", peer)
// 另外一个goroutine读取UDP包到in

select {
case <- time.After(to):
case <- in:
fmt.Println("Close ok")
return
case <- ctx.Done():
fmt.Println("Program quit")
return
}
}
return
}

这样在主要的逻辑中,还是只需要处理ctx,但是这个ctx已经是新的context了。不过在实际的过程中,这个sync的goroutine需要确定起来后,才能继续,否则会造成执行顺序不确定:


sc := make(chan bool, 1)
go func(){ // sync ctx with ctxRead
sc <- true
select {
......
}
<- sc

使用context,来控制多个goroutine的执行和取消,是非常好用的,关键可以完全关注业务的逻辑,而不会引入因为ctx取消或者超时机制而造成的特殊逻辑。

最后抛个砖 构想:在Goroutine上实现Actor?

liangdas 回复了问题 • 5 人关注 • 4 个回复 • 310 次浏览 • 4 天前 • 来自相关话题

一起来总结一下 golang 的一些最佳实践

chenhui7373 回复了问题 • 6 人关注 • 3 个回复 • 714 次浏览 • 4 天前 • 来自相关话题

GOLANG实现超时对象检测的最好理解的方式

winlin 发表了文章 • 0 个评论 • 139 次浏览 • 6 天前 • 来自相关话题

依赖于心跳的系统,都需要超时检测。比如P2P系统中客户端每隔120秒向数据服务器发送一次数据汇总,服务器就需要维护一个超时时间。比如一个UDP服务器,在和客户端之间创建Session之后,如果没有数据包,一般会有Ping包,说明这个Session是存活的... 查看全部

依赖于心跳的系统,都需要超时检测。比如P2P系统中客户端每隔120秒向数据服务器发送一次数据汇总,服务器就需要维护一个超时时间。比如一个UDP服务器,在和客户端之间创建Session之后,如果没有数据包,一般会有Ping包,说明这个Session是存活的,服务器在发现Session超时后也需要清理。


首先,服务器一般需要维护一个列表,以Peer为例:


type Peer struct {
id uint64
heartbeat time.Time
}

type Server struct {
peers map[uint64]*Peer
lock sync.Mutex
}

创建Peer,同时在收到Ping消息后,更新Peer的心跳时间:


func (v *Server) Create(id uint64) *Peer {
v.lock.Lock()
defer v.lock.UnLock()

p = &Peer { id:id, heartbeat: time.Now(), }
v.peers[id] = p
return p
}

func (v *Server) OnPing(id uint64) {
v.lock.Lock()
defer v.lock.UnLock()

if p,ok := v.peers[id]; ok {
p.heatbeat = time.Now()
}
}

当然,需要起一个goroutine定期扫描这个列表, 假设300秒超时:


go func(v *Server) {
for {
func(){
v.lock.Lock()
defer v.lock.UnLock()

now := time.Now()
for id,p := range v.peers {
if p.heartbeat.Add(300 * time.Second).Before(now) {
delete(v.peers, id)
}
}
}()
time.Sleep(30 * time.Second)
}
}(server)

如果Peers的数目非常多,那么扫描时每次都需要锁定v.peers,会导致其他的业务都无法进行。特别是清理Peer这个过程如果比较复杂,譬如需要发起io请求,是一个费时的操作时,就会造成系统的等待。


一般来说,超时的Peer不会很多,因此可以用chan放一个超时的peer,每个peer专门起一个goroutine来看什么时候超时,这样就可以在检测超时时避免用锁了:


timeout := make(chan *Peer)

func (v *Server) Create(id uint64) *Peer {
v.lock.Lock()
defer v.lock.UnLock()

p = &Peer { id:id, heartbeat: time.Now(), }
v.peers[id] = p
return p

go func(p *Peer) {
for {
tm := p.heartbeat
<- time.After(300 * time.Second)
if tm.Equal(p.heartbeat) {
timeout <- p
break
}
}
}(p)
}

go func(v *Server){
for gw := range timeout {
func(){
lgateways.Lock()
defer lgateways.Unlock()

delete(gateways, gw.port)
}()

// Do something cleanup about the gateway.
}
}(server)

这样就只有在有Peer超时时,才真正锁住Server.peers

GOLANG接口适配,组合方式的灵活接口演化

winlin 发表了文章 • 0 个评论 • 171 次浏览 • 2017-05-15 20:49 • 来自相关话题

在OO(Object Oriented)原则中,有一条叫做:优先使用组合,而不是继承。虽然GOLANG并不是OO的语言(没有继承和多态),但是不妨碍GOLANG使用这条原则,而GOLANG的作者就强调过这一点,在GOLANG中是使用组合而非继承来扩展。<... 查看全部

在OO(Object Oriented)原则中,有一条叫做:优先使用组合,而不是继承。虽然GOLANG并不是OO的语言(没有继承和多态),但是不妨碍GOLANG使用这条原则,而GOLANG的作者就强调过这一点,在GOLANG中是使用组合而非继承来扩展。


装逼的说来,继承是一种名词化的语言体系,先进行业务抽象然后设计类体系和继承关系。而组合,强制使用接口,因为组合中使用的总是另外一个对象的接口,通过动词的组合,实现目标,比如不管是什么只要有Write([]byte)(int,error)这个动作,就实现了这个接口,其他对象组合这个接口后,对外也看起来就是个io.Writer的接口。


比如,GOALNG1.8支持了writev,一般在面向对象会这么的搞:


class Socket {
int Write(void*, int);
int Writev(const iovec*, int);
};

对的吧?一个Socket可以写数据,也可以用writev写iovec向量,就是一次性写入多个内存块。



Note: 有时候内存块是不连续的,比如一个Video帧,发送给不同的客户端时,Header是需要修改的,但是Payload都一样,那么可以针对每个客户端只创建一个header,然后公用payload,但是这时候两个内存指针是不连续的,特别是需要同时写入多个视频帧时,writev就很神奇的避免了内存拷贝writev(header+payload),具体参考下writev的资料哈。



这样有个问题,并非所有系统都支持Writev的,并非所有Socket都支持Writev的,如果是自己写个代码,当然是可以随便这么搞的,但是作为标准库,GOLANG当然是不能这么做的。GOLANG就加了一个接口(一个新动作)叫做net.buffersWriter,如果实现了这个接口就用writev。先看用法:


    conn,err := net.Dial("tcp", "127.0.0.1:1935")

buffers := Buffers{
[]byte("once upon a time in "),
[]byte("Gopherland ... "),
}

buffers.WriteTo(conn)

在Buffers的WriteTo方法会判断是否是writev的接口,如果是则用writev写,否则就一个个的写:


func (v *Buffers) WriteTo(w io.Writer) (n int64, err error) {
if wv, ok := w.(buffersWriter); ok {
return wv.writeBuffers(v)
}

实际上conn是net.TcpConn,里面有个fd *net.netFD,它实现了net.buffersWriter接口,所以最后调用的就是(fd *netFD) writeBuffers(v *Buffers)


func (c *conn) writeBuffers(v *Buffers) (int64, error) {
n, err := c.fd.writeBuffers(v)

func (fd *netFD) writeBuffers(v *Buffers) (n int64, err error) {
iovecs = append(iovecs, syscall.Iovec{Base: &chunk[0]})
wrote, _, e0 := syscall.Syscall(syscall.SYS_WRITEV,
uintptr(fd.sysfd),
uintptr(unsafe.Pointer(&iovecs[0])),
uintptr(len(iovecs)))

对于其他没有实现这个接口的对象,就每个向量循环的写。


在看一个例子http.Get(url string),客户端发起一个HTTP请求:


http.Get("http://localhost:1985/api/v1/versions")
// 实际上调用的是:
func (c *Client) Get(url string)
// 然后调用:
(c *Client) Do(req *Request)

在GOLANG1.7中引入了context的概念,用来支持cancel,怎么用的:


ctx,cancel := context.WithCancel(context.Background())

select {
case <- ctx.Done():
// Cancelled.
case <- time.After(...):
// Timeout
case <- other events:
// Other events.
}

如何支持取消的HTTP请求呢?给http.Get加个ctx参数?例如http.Get(ctx, url)这样?那改动得多大啊,而且还不能兼容之前的API,泪奔~看看GOLANG的解决:


ctx,cancel := context.WithCancel(context.Background())
go func(){
req,err := http.NewRequest("http://...")
res,err := http.DefaultClient.Do(req.WithContext(ctx))
defer res.Body.Close()
// 读取res响应结果。
}()

select {
case <- ctx.Done():
case <- time.After(3 * time.Second):
cancel() // Timeout to cancel all requests.
}

使用组合,通过req.WithContext再返回一个*http.Request,实现同样的目的。

go调试的问题。

winlin 回复了问题 • 5 人关注 • 5 个回复 • 188 次浏览 • 2017-05-12 10:20 • 来自相关话题

GOLANG使用简单类型,在协议解析的妙用

winlin 发表了文章 • 0 个评论 • 645 次浏览 • 2017-05-11 15:41 • 来自相关话题

在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512, 11025, 22050, 44100。而在AAC中有16种采样率96000, 8... 查看全部

在协议解析中,经常需要用到转换不同的含义,比如声音的采样率,在FLV中定义和AAC中定义是不同的。在FLV中只有4中采样率5512, 11025, 22050, 44100。而在AAC中有16种采样率96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350(还有4个是保留的)。也就是说,1在FLV中标识11025Hz,而在AAC中表示的是88200Hz。如何实现这个转换呢?


C++当然先得定义枚举:


enum SrsAudioSampleRate
{
SrsAudioSampleRate5512 = 0,
SrsAudioSampleRate11025,
SrsAudioSampleRate22050,
SrsAudioSampleRate44100,
SrsAudioSampleRateForbidden,
};

C++当然是用函数了:


SrsAudioSampleRate aac_to_flv(int v) {
if (v >= 0 && v <=5) {
return SrsAudioSampleRate44100;
} else if (v >=6 && v <= 8) {
return SrsAudioSampleRate22050;
} else if (v >= 9 && v <= 11) {
return SrsAudioSampleRate11025;
} else if (v == 12) {
return SrsAudioSampleRate5512;
} else {
return SrsAudioSampleRateForbidden;
}
}

看起来还是挺简单的。慢着,还有的时候需要打印出采样率来,所以还得搞个函数:


string srs_audio_sample_rate2str(SrsAudioSampleRate v)
{
switch (v) {
case SrsAudioSampleRate5512: return "5512";
case SrsAudioSampleRate11025: return "11025";
case SrsAudioSampleRate22050: return "22050";
case SrsAudioSampleRate44100: return "44100";
default: return "Forbidden";
}
}

拿到一个AAC的采样率,然后转换成FLV的,并打印出来,是这么使用的:


// 从文件或者流中读取出AAC的采样率的值。
int samplingFrequencyIndex = ...;
// 转换成FLV的采样率。
SrsAudioSampleRate sampleRate = aac_to_flv(samplingFrequencyIndex);
// 转换成字符串格式。
string sSampleRate = srs_audio_sample_rate2str(sampleRate);
// 打印采样率。
printf("SampleRate=%d/%sHz\n", sampleRate, sSampleRate);

有什么麻烦的呢?



  1. 函数和类型之间没有关系,每次使用的时候都得去翻手册啊翻手册。

  2. 如果定义成一个struct,那转换的时候又太麻烦了。


还能不能愉快的玩耍呢?用GOLANG吧!先看用法:


var sampleRate AudioSamplingRate
sampleRate.From(samplingFrequencyIndex)
fmt.Printf("SampleRate=%d/%v\n", sampleRate, sampleRate)

就是这么简单(此处应该有掌声)~


其实实现起来也非常自然:


type AudioSamplingRate uint8

const (
AudioSamplingRate5kHz AudioSamplingRate = iota // 0 = 5.5 kHz
AudioSamplingRate11kHz // 1 = 11 kHz
AudioSamplingRate22kHz // 2 = 22 kHz
AudioSamplingRate44kHz // 3 = 44 kHz
AudioSamplingRateForbidden
)

func (v AudioSamplingRate) String() string {
switch v {
case AudioSamplingRate5kHz:
return "5.5kHz"
case AudioSamplingRate11kHz:
return "11kHz"
case AudioSamplingRate22kHz:
return "22kHz"
case AudioSamplingRate44kHz:
return "44kHz"
default:
return "Forbidden"
}
}

func (v *AudioSamplingRate) From(a int) {
switch a {
case 0, 1, 2, 3, 4, 5:
*v = AudioSamplingRate44kHz
case 6, 7, 8:
*v = AudioSamplingRate22kHz
case 9, 10, 11:
*v = AudioSamplingRate11kHz
case 12:
*v = AudioSamplingRate5kHz
default:
*v = AudioSamplingRateForbidden
}
}


Remark: 代码参考go-oryx-lib flv.



有几个地方非常不同:



  1. 虽然GOLANG只是在uint8上面加了函数,但是使用起来方便很多了,以前在C++中用这两个枚举,每次都要跳到枚举的定义来看对应的函数是什么。

  2. GOLANG的switch比较强大,可以case好几个值,和C++的if有点想,但是GOLANG的case更直观,知道这几个值会被转换成另外的值,而if读起来像是将一个范围的值转换,不好懂。

  3. GOLANG的枚举使用const实现,也可以带类型,而且有个iota很强大,特别是在定义那些移位的枚举时就很好用。


好吧,这只是几个小的改进,虽然用起来很方便。来看看在AMF0中基本类型的妙用,AMF0是一种传输格式,和JSON很像,不过JSON是文本的,而AMF0是字节的,都是用来在网络中传输对象的。因此,AMF0定义了几个基本的类型:String, Number, Boolean, Object,其中Object的属性定义为String的属性名和值,值可以是其他的类型。


先看看C++的实现,首先定义一个AMF0Any对象,可以转换成具体的String或者Object等对象:


class SrsAmf0Any {
// 提供转换的函数,获取实际的值。
virtual std::string to_str();
virtual bool to_boolean();
virtual double to_number();
virtual SrsAmf0Object* to_object();
// 当然还得提供判断的函数,得知道是什么类型才能转。
virtual bool is_string();
virtual bool is_boolean();
virtual bool is_number();
virtual bool is_object();
// 提供创建基本类型的函数。
static SrsAmf0Any* str(const char* value = NULL);
static SrsAmf0Any* boolean(bool value = false);
static SrsAmf0Any* number(double value = 0.0);
static SrsAmf0Object* object();
};

在实现时,String和Number等基本类型可以隐藏起来(在cpp中实现):


namespace _srs_internal {
class SrsAmf0String : public SrsAmf0Any {
public:
std::string value;
// 当然它必须实现编码和解码的函数。
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
};
}

AMF0Object当然得暴露出来的:


class SrsAmf0Object : public SrsAmf0Any {
public:
virtual int total_size();
virtual int read(SrsBuffer* stream);
virtual int write(SrsBuffer* stream);
// 提供设置和读取属性的方法。
virtual void set(std::string key, SrsAmf0Any* value);
virtual SrsAmf0Any* get_property(std::string name);
};

用起来是这样:


// 设置Object的属性,并发送给服务器。
SrsConnectAppPacket* pkt = NULL;
pkt->command_object->set("app", SrsAmf0Any::str(app.c_str()));
pkt->command_object->set("tcUrl", SrsAmf0Any::str(tcUrl.c_str()));

// 读取服务器的响应,取出服务器的IP等信息。
SrsConnectAppResPacket* pkt = NULL;
SrsAmf0Any* data = pkt->info->get_property("data");
if (si && data && data->is_object()) {
SrsAmf0Object* obj = data->to_objet();

SrsAmf0Any* prop = obj->get_property("srs_server_ip");
if (prop && prop->is_string()) {
printf("Server IP: %s\n", prop->to_str().c_str());
}

prop = obj->get_property("srs_pid");
if (prop && prop->is_number()) {
printf("Server PID: %d\n, prop->to_number());
}
}

看起来巨繁琐吧?快用GOLANG,如果换成GOLANG,可以用基本类型定义AMF0的基本类型,这样使用起来是这样:


pkt := or.NewConnectAppPacket()
pkt.CommandObject.Set("tcUrl", amf0.NewString(tcUrl))
pkt.CommandObject.Set("app", amf0.NewString(app))

var res *or.ConnectAppResPacket
if data, ok := res.Args.Get("data").(*amf0.Object); ok {
if data, ok := data.Get("srs_server_ip").(*amf0.String); ok {
fmt.Printf("Server IP: %s\n", string(*data))
}
if data, ok := data.Get("srs_pid").(*amf0.Number); ok {
fmt.Printf("Server PID: %d\n, int(*data))
}
}

区别在于:



  1. C++由于不能在基本类型上定义方法,导致必须创建struct或者class类型,有比较繁琐的类型转换和判断。

  2. GOLANG的类型判断,提供了ok的方式,一句话就能把类型转换弄好,而且接口和实现struct的对象可以重用变量名。

  3. 不必加很多类型判断,没有多余的变量,干净利索,需要维护的信息比较少。


实现起来更舒服,基本类型不用定义struct:


type String string
func (v *String) Size() int {}
func (v *String) UnmarshalBinary(data []byte) (err error) {}
func (v *String) MarshalBinary() (data []byte, err error) {}

type Object struct {}
func (v *Object) Size() int {}
func (v *Object) UnmarshalBinary(data []byte) (err error) {}
func (v *Object) MarshalBinary() (data []byte, err error) {}


Remark:代码参考go-oryx-lib amf0.



更神奇的是,因为Object、EcmaArray和StrictArray都是类似的结构,但是有些细微的差异,因此使用GOLANG的结构体嵌套可以很直接的解决问题:


type Object struct {
objectBase
eof objectEOF
}
type EcmaArray struct {
objectBase
count uint32
eof objectEOF
}
type StrictArray struct {
objectBase
count uint32
}

可以对比下SRS的实现,C++可以采用继承,而GOLANG直接组合那些基本的单元。


爱生活,爱够浪(此处可以响起掌声了)~

protobuf的一个问题

lrita 回复了问题 • 1 人关注 • 1 个回复 • 153 次浏览 • 2017-05-11 11:30 • 来自相关话题

关于统一格式 json 返回 map 的 value 定义

PureWhite 回复了问题 • 5 人关注 • 4 个回复 • 177 次浏览 • 2017-05-10 23:40 • 来自相关话题