这篇“Go语言otns源码分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Go语言otns源码分析”文章吧。
proto文件
这个例子中只有一个proto文件,位于
ot-ns-main/visualize/grpc/pb
下,里面的service也只定义了两个rpc方法:service VisualizeGrpcService {
// rpc Echo (EchoRequest) returns (EchoResponse);
rpc Visualize (VisualizeRequest) returns (stream VisualizeEvent);
rpc Command (CommandRequest) returns (CommandResponse);
}
Visualize (VisualizeRequest) returns (stream VisualizeEvent)
这个方法接受一个VisualizeRequest,返回VisualizeEvent流。两个消息定义如下:
message VisualizeRequest {
}
message VisualizeEvent {
oneof type {
AddNodeEvent add_node = 1;
DeleteNodeEvent delete_node = 2;
SetNodeRloc16Event set_node_rloc16 = 3;
SetNodeRoleEvent set_node_role = 4;
SetNodePosEvent set_node_pos = 5;
SetNodePartitionIdEvent set_node_partition_id = 6;
OnNodeFailEvent on_node_fail = 7;
OnNodeRecoverEvent on_node_recover = 8;
SetParentEvent set_parent = 9;
CountDownEvent count_down = 10;
ShowDemoLegendEvent show_demo_legend = 11;
AdvanceTimeEvent advance_time = 12;
AddRouterTableEvent add_router_table = 13;
RemoveRouterTableEvent remove_router_table = 14;
AddChildTableEvent add_child_table = 15;
RemoveChildTableEvent remove_child_table = 16;
SendEvent send = 17;
SetSpeedEvent set_speed = 18;
HeartbeatEvent heartbeat = 19;
OnExtAddrChangeEvent on_ext_addr_change = 20;
SetTitleEvent set_title = 21;
SetNodeModeEvent set_node_mode = 22;
SetNetworkInfoEvent set_network_info = 23;
}
}
请求为空,而VisualizeEvent里面使用
oneof
关键字包含了很多的消息体,每个消息体封装了一个事件。Command (CommandRequest) returns (CommandResponse)
这个方法接受
CommandRequest
并返回CommandResponse
,两个消息体定义如下:message CommandRequest {
string command = 1;
}
message CommandResponse {
repeated string output = 1;
}
CommandResponse
中的output
在go中会声明为string[]
visualize/grpc/replay目录下的文件
grpcField(未包含pb)
定义了一个结构
grpcField
,里面包含了节点信息、当前时间与速度、标题信息、网络信息、及其设置。type grpcField struct {
nodes map[NodeId]*grpcNode
curTime uint64
curSpeed float64
speed float64
titleInfo visualize.TitleInfo
networkInfo visualize.NetworkInfo
}
grpcNode(未包含pb)
定义了节点结构
grpcNode
,包含各种信息,还有一个new这个结构的函数type grpcNode struct {
nodeid NodeId
extaddr uint64
x int
y int
radioRange int
mode NodeMode
rloc16 uint16
role OtDeviceRole
partitionId uint32
failed bool
parent uint64
routerTable map[uint64]struct{}
childTable map[uint64]struct{}
}
grpcServer(包含pb)
自定义了一个grpcServer,包含信息如下
type grpcServer struct {
vis *grpcVisualizer
server *grpc.Server
address string
visualizingStreams map[*grpcStream]struct{}
}
同时按照接口要求实现了
Visualize()
和Command()
方法,还自定义了其他的方法如run
、stop
、prepareStream
等等,看名字就容易知道是什么用途grpcStream(包含pb)
里面自定义了一个结构
grpcStream
,使用这个文件中的newGrpcStream
可以将Visualize函数的服务端流赋到这个结构中grpcVisualizer(包含pb)
其中自定义了一个结构:
type grpcVisualizer struct {
simctrl visualize.SimulationController
server *grpcServer
f *grpcField
showDemoLegendEvent *pb.VisualizeEvent
replay *replay.Replay
sync.Mutex
}
-
需要注意的是这个结构继承了互斥锁
,并且包含了上面的grpcServer、grpcServer结构,这个文件里面的函数大概都是添加、删除节点或者修改什么信息之类的,基本是调用了sync.Mutex
和grpcField
文件里面的函数,但是在调用之前加了锁。grpcServer
-
这个结构实现了
中的visualize/types.go
接口Visualizer
-
并且,这个结构中包含了
接口的字段,而visualize.SimulationController
定义如下:visualize.SimulationController
type SimulationController interface {
Command(cmd string) ([]string, error)
}
大概就是命令的入口。
cmd/otns-replay目录下的文件
grpc_Service(包含pb)
-
定义了
结构,并且实现了grpcService
和Visualize
两个方法Command
type grpcService struct {
replayFile string
}
2.
grpcService
结构下的visualizeStream()
函数将
grpcService
的replay文件检验并打开,并且逐行读取内容,并解析到var entry pb.ReplayEntry
中,再通过stream将entry.Event
发送到服务的客户端-
实现的
方法:Visualize
启动
visualizeStream()
协程,创建一个心跳事件,每隔一秒心跳一下,直到上面的visualizeStream()
读取完成otns_replay(包含pb)
main()函数
一系列的校验和配置参数之后,用上面的
grpcService
结构注册服务端,在本机地址8999
端口监听。然后就是配置和打开网页cmd/otns/otns.go文件
调用了
otns_main/otns_main.go
下的Main()
函数:首先依然是解析和配置参数和环境:
parseArgs()
simplelogger.SetLevel(simplelogger.ParseLevel(args.LogLevel))
parseListenAddr()
rand.Seed(time.Now().UnixNano())
// run console in the main goroutine
ctx.Defer(func() {
_ = os.Stdin.Close()
})
handleSignals(ctx)
然后是打开replay文件并创建
visualizer
实例:var vis visualize.Visualizer
if visualizerCreator != nil {
vis = visualizerCreator(ctx, &args)
}
visGrpcServerAddr := fmt.Sprintf("%s:%d", args.DispatcherHost, args.DispatcherPort-1)
replayFn := ""
if !args.NoReplay {
replayFn = fmt.Sprintf("otns_%s.replay", os.Getenv("PORT_OFFSET"))
}
if vis != nil {
vis = visualizeMulti.NewMultiVisualizer(
vis,
visualizeGrpc.NewGrpcVisualizer(visGrpcServerAddr, replayFn),
)
} else {
vis = visualizeGrpc.NewGrpcVisualizer(visGrpcServerAddr, replayFn)
}
创建一个新模拟,并设置
CmdRunner
和Visualizer
:sim := createSimulation(ctx)
rt := cli.NewCmdRunner(ctx, sim)
sim.SetVisualizer(vis)
启动一个协程运行模拟:
go sim.Run()
启动客户命令行协程:
go func() {
err := cli.Run(rt, cliOptions)
ctx.Cancel(errors.Wrapf(err, "console exit"))
}()
设置并打开网页:
go func() {
siteAddr := fmt.Sprintf("%s:%d", args.DispatcherHost, args.DispatcherPort-3)
err := webSite.Serve(siteAddr)
if err != nil {
simplelogger.Errorf("site quited: %+v, OTNS-Web won't be available!", err)
}
}()
if args.AutoGo {
go autoGo(ctx, sim)
}
web.ConfigWeb(args.DispatcherHost, args.DispatcherPort-2, args.DispatcherPort-1, args.DispatcherPort-3)
simplelogger.Debugf("open web: %v", args.OpenWeb)
if args.OpenWeb {
_ = web.OpenWeb(ctx)
}
Visualizer
启动:vis.Run() // visualize must run in the main thread
simulation目录下的文件
simulation
是grpcVisualizer
和cmdRunner
通信的桥梁。type.go
定义了
CmdRunner
接口:type CmdRunner interface {
RunCommand(cmd string, output io.Writer) error
}
simulationController.go
-
定义了
类,这个类实现了simulationController
接口,也就是visualize.SimulationController
里有的字段:grpcVisualizer
type simulationController struct {
sim *Simulation
}
func (sc *simulationController) Command(cmd string) ([]string, error) {
var outputBuilder strings.Builder
sim := sc.sim
err := sim.cmdRunner.RunCommand(cmd, &outputBuilder)
if err != nil {
return nil, err
}
output := strings.Split(outputBuilder.String(), "
")
if output[len(output)-1] == "" {
output = output[:len(output)-1]
}
return output, nil
}
-
还定义了同样实现了
接口的只读类,这里不展开说了。visualize.SimulationController
-
还有一个
函数产生NewSimulationController(sim *Simulation)
simulationController
-
应该是一个介于Command和Simulation之间的中介,接收Command并操作CmdRunner更改Simulation,并且输出信息。simulationController
simulation_config.go
定义了配置和默认配置
simulation.go
-
结构定义:simulation
type Simulation struct {
ctx *progctx.ProgCtx
cfg *Config
nodes map[NodeId]*Node
d *dispatcher.Dispatcher
vis visualize.Visualizer
cmdRunner CmdRunner
rawMode bool
networkInfo visualize.NetworkInfo
}
-
有一个new产生
结构的函数simulation
-
各种增删改查操作,都是通过
结构中的simulation
接口函数实现的visualize.Visualizer
cli目录
cli目录下定义了
CmdRunner
及各种指令结构ast.go
定义了各种命令结构
CmdRunner.go
-
定义了
结构:CmdRunner
type CmdRunner struct {
sim *simulation.Simulation
ctx *progctx.ProgCtx
contextNodeId NodeId
}
-
实现
接口的simulation/CmdRunner
方法:RunCommand
func (rt *CmdRunner) RunCommand(cmdline string, output io.Writer) error {
// run the OTNS-CLI command without node contexts
cmd := Command{}
if err := ParseBytes([]byte(cmdline), &cmd); err != nil {
if _, err := fmt.Fprintf(output, "Error: %v
", err); err != nil {
return err
}
} else {
rt.execute(&cmd, output)
}
return nil
}
-
在
方法中解析配置好命令后,有各种RunCommand
函数来执行相应的命令,而在这些函数中又是通过调用execute...()
中对应的增删改查函数来实现操作的simulation.Simulation