正文

Go语言学习otns示例分析(go语言编程入门与实战技巧)深度揭秘

文章摘要

这篇文章对OTNS协议的实现和分析进行了详细介绍,重点内容如下: 1. **协议分析**:文章详细解读了OTNS协议中使用的GRPC协议,定义了服务(grpcService)、消息体(visualizeEvent、CommandRequest、CommandResponse)以及相关的数据结构(如grpcNode、grpcServer、simulationController等)。 2. **Go语言实现**:展示了如何通过Go语言实现OTNS协议,包括服务端和客户端的通信机制。文章提到了模拟部分的设计,用于展示和操作节点信息,包括节点的创建、删除、修改等操作。 3. **模拟部分**:描述了如何通过模拟实现OTNS协议的客户端功能,包括命令的解析和执行,以及如何通过模拟控制节点的状态。 4. **代码结构**:文章提到了多个目录下的文件,如`cmd/otns-replay`、`simulator`、`grpc_service`等,展示了OTNS协议的完整实现结构。 5. **运行配置**:介绍了如何配置和运行OTNS工具,包括设置地址、端口、模拟器等。 文章通过代码结构和协议分析,展示了如何在Go语言中实现复杂的通信协议,并通过模拟实现客户端功能。


目录学习过程proto文件visualize/grpc/replay目录下的文件cmd/otns-replay目录下的文件grpc_Service(包含pb)otns_replay(包含pb)cmd/otns/otns.go文件simulation目录下的文件type.gosimulationController.gosimulation_config.gosimulation.gocli目录ast.goCmdRunner.go总结

由于在用虚拟机体验过程中出现了未知的错误之后,打算使用wsl又遇到了安装错误,各种办法解决无果,于是我打算跳过体验的这一部分,直接先进行这个例子中的grpc调用部分的梳理分析,等有空了再去解决一下wsl安装不了的问题。

这个例子中只有一个proto文件,位于下,里面的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里面使用关键字包含了很多的消息体,每个消息体封装了一个事件。

Command (CommandRequest) returns (CommandResponse)

这个方法接受并返回,两个消息体定义如下:

message CommandRequest { string command=1; }
message CommandResponse { repeated string output=1; }

中的在go中会声明为

grpcField(未包含pb)

定义了一个结构,里面包含了节点信息、当前时间与速度、标题信息、网络信息、及其设置。

type grpcField struct { nodes map[NodeId]*grpcNode curTime uint64 curSpeed float64 speed float64 titleInfo visualize.TitleInfo networkInfo visualize.NetworkInfo }
grpcNode(未包含pb)

定义了节点结构,包含各种信息,还有一个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{} }

同时按照接口要求实现了和方法,还自定义了其他的方法如、、等等,看名字就容易知道是什么用途

grpcStream(包含pb)

里面自定义了一个结构,使用这个文件中的可以将Visualize函数的服务端流赋到这个结构中

grpcVisualizer(包含pb)

其中自定义了一个结构:

type grpcVisualizer struct { simctrl visualize.SimulationController server *grpcServer f *grpcField showDemoLegendEvent *pb.VisualizeEvent replay *replay.Replay sync.Mutex }
需要注意的是这个结构继承了互斥锁,并且包含了上面的grpcServer、grpcServer结构,这个文件里面的函数大概都是添加、删除节点或者修改什么信息之类的,基本是调用了和文件里面的函数,但是在调用之前加了锁。这个结构实现了中的接口并且,这个结构中包含了接口的字段,而定义如下:
type SimulationController interface { Command(cmd string) ([]string, error) }

大概就是命令的入口。

grpc_Service(包含pb) 定义了结构,并且实现了和两个方法
type grpcService struct { replayFile string }

2. 结构下的函数

将的replay文件检验并打开,并且逐行读取内容,并解析到中,再通过stream将发送到服务的客户端

实现的方法:

启动协程,创建一个心跳事件,每隔一秒心跳一下,直到上面的读取完成

otns_replay(包含pb)

main()函数

一系列的校验和配置参数之后,用上面的结构注册服务端,在本机地址端口监听。然后就是配置和打开网页

cmd/otns/otns.go文件

调用了下的函数:

首先依然是解析和配置参数和环境:

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文件并创建实例:

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) }

创建一个新模拟,并设置和:

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) }

启动:

vis.Run() // visualize must run in the main thread

是和通信的桥梁。

type.go

定义了接口:

type CmdRunner interface { RunCommand(cmd string, output io.Writer) error }
simulationController.go 定义了类,这个类实现了接口,也就是里有的字段:
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 }
还定义了同样实现了接口的只读类,这里不展开说了。还有一个函数产生应该是一个介于Command和Simulation之间的中介,接收Command并操作CmdRunner更改Simulation,并且输出信息。 simulation_config.go

定义了配置和默认配置

simulation.go 结构定义:
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产生结构的函数各种增删改查操作,都是通过结构中的接口函数实现的

cli目录下定义了及各种指令结构

ast.go

定义了各种命令结构

CmdRunner.go 定义了结构:
type CmdRunner struct { sim *simulation.Simulation ctx *progctx.ProgCtx contextNodeId NodeId }
实现接口的方法:
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 }
在方法中解析配置好命令后,有各种函数来执行相应的命令,而在这些函数中又是通过调用中对应的增删改查函数来实现操作的

以上就是Go语言学习otns示例分析的详细内容,更多关于Go语言otns示例的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:Go语言开发kube-scheduler整体架构深度剖析Go语言kube-scheduler深度剖析开发之scheduler初始化Go语言kube-scheduler深度剖析与开发之pod调度深入string理解Golang是怎样实现的GoJs连线绘图模板Link使用示例详解