golang類型斷言的使用(Type Assertion)

第一部分java

首先,轉自https://studygolang.com/articles/3314對斷言的基本介紹git

golang的語言中提供了斷言的功能。golang中的全部程序都實現了interface{}的接口,這意味着,全部的類型如string,int,int64甚至是自定義的struct類型都就此擁有了interface{}的接口,這種作法和java中的Object類型比較相似。那麼在一個數據經過func funcName(interface{})的方式傳進來的時候,也就意味着這個參數被自動的轉爲interface{}的類型。github

如如下的代碼:golang

func funcName(a interface{}) string {
     return string(a)
}

編譯器將會返回:json

cannot convert a (type interface{}) to type string: need type assertionruby

此時,意味着整個轉化的過程須要類型斷言。類型斷言有如下幾種形式:服務器

1)直接斷言使用tcp

var a interface{}函數

fmt.Println("Where are you,Jonny?", a.(string))編碼

可是若是斷言失敗通常會致使panic的發生。因此爲了防止panic的發生,咱們須要在斷言前進行必定的判斷

value, ok := a.(string)

若是斷言失敗,那麼ok的值將會是false,可是若是斷言成功ok的值將會是true,同時value將會獲得所期待的正確的值。示例:

value, ok := a.(string)
if !ok {
    fmt.Println("It's not ok for type string")
    return
}
fmt.Println("The value is ", value)


另外也能夠配合switch語句進行判斷:

var t interface{}
t = functionOfSomeType()
switch t := t.(type) {
default:
    fmt.Printf("unexpected type %T", t)       // %T prints whatever type t has
break case bool: fmt.Printf("boolean %t\n", t) // t has type bool
break case int: fmt.Printf("integer %d\n", t) // t has type int
break case *bool: fmt.Printf("pointer to boolean %t\n", *t) // t has type *bool
break case *int: fmt.Printf("pointer to integer %d\n", *t) // t has type *int
break }


第二部分

net/jsonrpc增長get_client_ip功能

問題描述:falcon-agent沒法監測到宿主機ip的變更,致使初始化安裝時寫在配置裏的IP地址與當前真實IP地址不符,產生錯誤的上報數據(將數據以舊IP上報,新IP查不到任何監控項)。

解決思路:因爲agent已大規模部署在服務器上,更新比較麻煩,考慮修改transfer端,從rpc鏈接中直接獲取到agent的IP。

 

來自open-falcon/falcon-plus/transfer組件中的rpc相關代碼:

初始創建RPC server:open-falcon\falcon-plus\modules\transfer\receiver\rpc\rpc.go

package rpc

import (
    "github.com/open-falcon/falcon-plus/modules/transfer/g"
    "log"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

func StartRpc() {
    if !g.Config().Rpc.Enabled {
        return
    }

    addr := g.Config().Rpc.Listen
    tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
    if err != nil {
        log.Fatalf("net.ResolveTCPAddr fail: %s", err)
    }

    listener, err := net.ListenTCP("tcp", tcpAddr)
    if err != nil {
        log.Fatalf("listen %s fail: %s", addr, err)
    } else {
        log.Println("rpc listening", addr)
    }

    server := rpc.NewServer()
    server.Register(new(Transfer))

    for {
        conn, err := listener.Accept()
        if err != nil {
            log.Println("listener.Accept occur error:", err)
            continue
        }
        go server.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

這裏使用的是jsonrpc的編碼解碼器,其中會對conn中的數據使用json.Unmarshal解碼。

 

重要的步驟位於jsonrpc/server.go的下列函數中:

 1 type ArgsContext interface {  2     Value(key string) interface{}  3     SetValue(key string, value interface{})  4 }  5 
 6 func (c *serverCodec) ReadRequestBody(x interface{}) error {
 7     if x == nil {
 8         return nil
 9     }
10     if c.req.Params == nil {
11         return errMissingParams
12     }
13     if args, ok := x.(ArgsContext); ok { 14         args.SetValue("conn", c.c) 15  } 16     // JSON params is array value.
17     // RPC params is struct.
18     // Unmarshal into array containing struct for now.
19     // Should think about making RPC more general.
20     var params [1]interface{}
21     params[0] = x
22     return json.Unmarshal(*c.req.Params, &params)
23 }

1-4行和13-15行是新增的一個斷言判斷,目的是爲了給解析出來的args參數增長一些上下文信息,好比最重要的:將conn對象存入其中。

如此,即可以從rpc的callback函數中訪問到conn對象,從而拿到client IP【要求args的類型在定義時實現ArgsContext的接口】。

 

該思路源自https://github.com/club-codoon/rpcx/blob/630e53bff09759ba2a21644f318907504cfdd98a/_examples/context/server.go,應用方式以下:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "net"
 6 
 7     "github.com/smallnest/rpcx"
 8 )
 9 
10 type Args struct {
11     A   int `msg:"a"`
12     B   int `msg:"b"`
13     ctx map[string]interface{} 14 }
15 
16 type Reply struct {
17     C int `msg:"c"`
18 }
19 
20 func (a *Args) Value(key string) interface{} { 21     if a.ctx != nil { 22         return a.ctx[key] 23  } 24     return nil 25 } 26 
27 func (a *Args) SetValue(key string, value interface{}) { 28     if a.ctx == nil { 29         a.ctx = make(map[string]interface{}) 30  } 31     a.ctx[key] = value 32 } 33 
34 type Arith int
35 
36 func (t *Arith) Mul(args *Args, reply *Reply) error {
37     reply.C = args.A * args.B
38     conn := args.Value("conn").(net.Conn)
39     fmt.Printf("Client IP: %s \n", conn.RemoteAddr().String())
40     return nil
41 }
42 
43 func main() {
44     server := rpcx.NewServer()
45     server.RegisterName("Arith", new(Arith))
46     server.Serve("tcp", "127.0.0.1:8972")
47 }

 

可是該方法有一個侷限,以下的callback場景中,args參數爲[]*sometype,是一個slice。

若是是一個struct,則能夠方便的按照上述方法添加一個私有的ctx便可存放相關數據,但若是是一個slice,是沒辦法放一個ctx解決的,那樣的話會把slice改成一個struct,從而在json.Unmarshal時失敗。

RPC server的callback函數定義:open-falcon\falcon-plus\modules\transfer\receiver\rpc\rpc_transfer.go

import (
    "fmt"
    cmodel "github.com/open-falcon/falcon-plus/common/model"
    cutils "github.com/open-falcon/falcon-plus/common/utils"
    "github.com/open-falcon/falcon-plus/modules/transfer/g"
    "github.com/open-falcon/falcon-plus/modules/transfer/proc"
    "github.com/open-falcon/falcon-plus/modules/transfer/sender"
    "strconv"
    "time"
    "path/filepath"
    "crypto/md5"
    "io"
    "encoding/hex"
)

type Transfer int

type TransferResp struct {
    Msg        string
    Total      int
    ErrInvalid int
    Latency    int64
}

func (t *TransferResp) String() string {
    s := fmt.Sprintf("TransferResp total=%d, err_invalid=%d, latency=%dms",
        t.Total, t.ErrInvalid, t.Latency)
    if t.Msg != "" {
        s = fmt.Sprintf("%s, msg=%s", s, t.Msg)
    }
    return s
}

func (t *Transfer) Update(args []*cmodel.MetricValue, reply *cmodel.TransferResponse) error { return RecvMetricValues(args, reply, "rpc") }

 

一個workaround思路是,將jsonrpc單拿出來做爲一個私有依賴包,更改其中的邏輯,直接將args斷言爲slice指針類型,並遍歷其數據,將client IP放入Endpoint字段中。

【因爲transfer的rpc機制只有這裏用到了jsonrpc包,因此該workaround能夠不影響其餘rpc邏輯】:

 1 func (c *serverCodec) ReadRequestBody(x interface{}) error {
 2     if x == nil {
 3         return nil
 4     }
 5     if c.req.Params == nil {
 6         return errMissingParams
 7     }
 8     // JSON params is array value.
 9     // RPC params is struct.
10     // Unmarshal into array containing struct for now.
11     // Should think about making RPC more general.
12     var params [1]interface{}
13     params[0] = x
14     if err := json.Unmarshal(*c.req.Params, &params); err != nil {
15         return err
16     }
17     // fmt.Printf("[jsonrpc]x type is %T \n", x)
18     if args, ok := x.(*[]*cmodel.MetricValue); ok { 19         remote_addr := strings.Split(c.c.(net.Conn).RemoteAddr().String(), ":")[0] 20         if remote_addr != "" { 21             for _, v := range *args { 22                 v.Endpoint = remote_addr 23  } 24  } 25  } 26     return nil
27 }
相關文章
相關標籤/搜索