以太坊的事件框架,存在于目录go-ethereum/event
中,它有两种事件框架实现,一种叫TypeMux(go-ethereum/event/event.go
)
,现在已经基本弃用;另外一种叫Feed(go-ethereum/event/feed.go
)
, 它是目前主要使用的对象。取代了前面说的event.go内部的TypeMux。
以太坊的事件框架会把所有的订阅者放到一个集合中,每当事件框架收到某个事件的时候,就把通知所有订阅该事件的所有订阅者。由于TypeMux
有可能会阻塞,所以通知订阅者可能会延时很久,估计这就是为什么弃用的原因吧。而Feed基本不会
阻塞,一般会及时的把事件通知给订阅者,但需要给每种事件创建一个Feed,订阅或者发送事件则需要在不同的Feed上处理。下面主要介绍一下Feed的实现方式。
Feed是一对多的事件框架。
-
数据结构
Feed的主要函数以及数据结构如下:
Subscribe以及Send函数是包外可以调用的函数。
func (f *Feed) Subscribe(channel interface{}) Subscription
Subscribe函数添加一个订阅
func (f *Feed) Send(value interface{}) (nsent int)
Send函数发送一个订阅事件。
type Feed struct {
once sync.Once // ensures that init only runs once
sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases.
removeSub chan interface{} // interrupts Send
sendCases caseList // the active set of select cases used by Send
// The inbox holds newly subscribed channels until they are added to sendCases.
mu sync.Mutex
inbox caseList
etype reflect.Type
closed bool
}
once保证init函数只执行一次;sendLock是 “Send函数的锁。removeSub是“Send”以及“remove"之间“通讯”接口。sendCases是订阅者列表。inbox是刚刚加入的订阅者列表(Send函数会将inbox的订阅者移动到sendCases中);closed标识此订阅事件的状态。
type feedSub struct {
feed *Feed
channel reflect.Value
errOnce sync.Once
err chan error
}
feedSub是Subscription的一种实现。Feed是管理各种feedSub。
Subscription是个接口,定义在event/subscription.go文件中:
Subscription提供两个函数:Err函数负责出错处理,Unsubscribe函数停止订阅。
func (sub *feedSub) Unsubscribe()
Unsubscribe() 实现了订阅者退订某个事件;订阅事件的函数在上边已经实现了。
-
实现方式
实际运用中,我们主要的流程,就是订阅,发送事件,接收事件,取消订阅。
订阅,订阅时需要一个channel作为参数传入。订阅函数也会校验传入的参数是否为chan以及通道的方向;然后构造一个feedSub数据作为返回值; 使用传入的channel生成了SelectCase,放入inbox,即将新加入的订阅者放入inbox里。
取消订阅,一般注册完成后,订阅者会获取一个Subscription,
可以通过它取消订阅和读取通道错误。这时,订阅者会从inbox和sendCases中都被删除。
发布事件,调用Feed.Send(chan x)
,通过加锁确保本类型事件只有一个发送协程正在进行,然后校验事件类型是否匹配,Feed会尝试给每个订阅者发送事件,如果订阅者阻塞,Feed就继续尝试给下一个订阅者发送,直到给每个订阅者发送事件,返回发送该事件的数量。
接收事件,订阅者通过读取chan里的数据,实现事件的接收。
-
如何使用
package main
import (
"fmt"
"sync"
"github.com/ethereum/go-ethereum/event"
)
func main() {
type bigevent struct{ number int }
var f event.Feed
var wg sync.WaitGroup
ch := make(chan bitevent)
sub := feed.Subscribe(ch)
wg.Add(1)
go func() {
defer wg.Done()
for event := range ch {
fmt.Printf("Received: %#v\n", event.number)
}
sub.Unsubscribe()
}()
feed.Send(bigevent{5})
close(ch)
wg.Wait()
}
我们设置了事件bigevent,创建了feed,然后订阅feed;开启协程监听通道,看是否有事件推送,等到通多关闭后,取消订阅;