学习过程
由于在用虚拟机体验过程中出现了未知的错误之后,打算使用wsl又遇到了安装错误,各种办法解决无果,于是我打算跳过体验的这一部分,直接先进行这个例子中的grpc调用部分的梳理分析,等有空了再去解决一下wsl安装不了的问题。
proto文件
这个例子中只有一个proto文件,位于ot-ns-main/visualize/grpc/pb
下,里面的service也只定义了两个rpc方法:
1
2
3
4
5
|
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流。两个消息定义如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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
,两个消息体定义如下:
1
2
3
|
message CommandRequest { string command = 1 ; } |
1
2
3
|
message CommandResponse { repeated string output = 1 ; } |
CommandResponse
中的output
在go中会声明为string[]
visualize/grpc/replay目录下的文件
- grpcField(未包含pb)
定义了一个结构grpcField
,里面包含了节点信息、当前时间与速度、标题信息、网络信息、及其设置。
1
2
3
4
5
6
7
8
|
type grpcField struct { nodes map [NodeId]*grpcNode curTime uint64 curSpeed float64 speed float64 titleInfo visualize.TitleInfo networkInfo visualize.NetworkInfo } |
- grpcNode(未包含pb)
定义了节点结构grpcNode
,包含各种信息,还有一个new这个结构的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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,包含信息如下
1
2
3
4
5
6
|
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)
其中自定义了一个结构:
1
2
3
4
5
6
7
8
|
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
定义如下:
1
2
3
|
type SimulationController interface { Command(cmd string ) ([] string , error ) } |
大概就是命令的入口。
cmd/otns-replay目录下的文件
grpc_Service(包含pb)
-
定义了
grpcService
结构,并且实现了Visualize
和Command
两个方法
1
2
3
|
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()
函数:
首先依然是解析和配置参数和环境:
1
2
3
4
5
6
7
8
9
|
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
实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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
:
1
2
3
|
sim := createSimulation(ctx) rt := cli.NewCmdRunner(ctx, sim) sim.SetVisualizer(vis) |
启动一个协程运行模拟:
1
|
go sim.Run() |
启动客户命令行协程:
1
2
3
4
|
go func () { err := cli.Run(rt, cliOptions) ctx.Cancel(errors.Wrapf(err, "console exit" )) }() |
设置并打开网页:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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
启动:
1
|
vis.Run() // visualize must run in the main thread |
simulation目录下的文件
simulation
是grpcVisualizer
和cmdRunner
通信的桥梁。
type.go
定义了CmdRunner
接口:
1
2
3
|
type CmdRunner interface { RunCommand(cmd string , output io.Writer) error } |
simulationController.go
-
定义了
simulationController
类,这个类实现了visualize.SimulationController
接口,也就是grpcVisualizer
里有的字段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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 (), "\n" ) 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
结构定义:
1
2
3
4
5
6
7
8
9
10
|
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
结构:
1
2
3
4
5
|
type CmdRunner struct { sim *simulation.Simulation ctx *progctx.ProgCtx contextNodeId NodeId } |
-
实现
simulation/CmdRunner
接口的RunCommand
方法:
1
2
3
4
5
6
7
8
9
10
11
12
|
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\n" , err); err != nil { return err } } else { rt.execute(&cmd, output) } return nil } |
-
在
RunCommand
方法中解析配置好命令后,有各种execute...()
函数来执行相应的命令,而在这些函数中又是通过调用simulation.Simulation
中对应的增删改查函数来实现操作的
总结
以上就是Go语言学习otns示例分析的详细内容,更多关于Go语言otns示例的资料请关注服务器之家其它相关文章!
原文链接:https://juejin.cn/post/7224765991896055865