这篇“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 }
需要注意的是这个结构继承了互斥锁
sync.Mutex,并且包含了上面的grpcServer、grpcServer结构,这个文件里面的函数大概都是添加、删除节点或者修改什么信息之类的,基本是调用了
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
simulationController应该是一个介于Command和Simulation之间的中介,接收Command并操作CmdRunner更改Simulation,并且输出信息。
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中对应的增删改查函数来实现操作的