首頁 > 軟體

Go語言學習otns範例分析

2023-12-07 14:00:14

學習過程

由於在用虛擬機器器體驗過程中出現了未知的錯誤之後,打算使用wsl又遇到了安裝錯誤,各種辦法解決無果,於是我打算跳過體驗的這一部分,直接先進行這個例子中的grpc呼叫部分的梳理分析,等有空了再去解決一下wsl安裝不了的問題。

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()方法,還自定義了其他的方法如runstopprepareStream等等,看名字就容易知道是什麼用途

  • 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結構,這個檔案裡面的函數大概都是新增、刪除節點或者修改什麼資訊之類的,基本是呼叫了grpcFieldgrpcServer檔案裡面的函數,但是在呼叫之前加了鎖。
  • 這個結構實現了visualize/types.go中的Visualizer介面
  • 並且,這個結構中包含了visualize.SimulationController介面的欄位,而visualize.SimulationController定義如下:
type SimulationController interface {
    Command(cmd string) ([]string, error)
}

大概就是命令的入口。

cmd/otns-replay目錄下的檔案

grpc_Service(包含pb)

  • 定義了grpcService結構,並且實現了VisualizeCommand兩個方法
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)
}

建立一個新模擬,並設定CmdRunnerVisualizer

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目錄下的檔案

simulationgrpcVisualizercmdRunner通訊的橋樑。

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(), "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結構定義:
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: %vn", err); err != nil {
         return err
      }
   } else {
      rt.execute(&cmd, output)
   }
   return nil
}
  • RunCommand方法中解析設定好命令後,有各種execute...()函數來執行相應的命令,而在這些函數中又是通過呼叫simulation.Simulation中對應的增刪改查函數來實現操作的

總結

以上就是Go語言學習otns範例分析的詳細內容,更多關於Go語言otns範例的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com