zoukankan      html  css  js  c++  java
  • golang类型断言的使用(Type Assertion)

    第一部分

    首先,转自https://studygolang.com/articles/3314对断言的基本介绍

    golang的语言中提供了断言的功能。golang中的所有程序都实现了interface{}的接口,这意味着,所有的类型如string,int,int64甚至是自定义的struct类型都就此拥有了interface{}的接口,这种做法和java中的Object类型比较类似。那么在一个数据通过func funcName(interface{})的方式传进来的时候,也就意味着这个参数被自动的转为interface{}的类型。

    如以下的代码:

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

    编译器将会返回:

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

    此时,意味着整个转化的过程需要类型断言。类型断言有以下几种形式:

    1)直接断言使用

    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 ", t) // t has type bool
    break case int: fmt.Printf("integer %d ", t) // t has type int
    break case *bool: fmt.Printf("pointer to boolean %t ", *t) // t has type *bool
    break case *int: fmt.Printf("pointer to integer %d ", *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-falconfalcon-plusmodules ransfer eceiver pc pc.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 
    ", 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-falconfalcon-plusmodules ransfer eceiver pc pc_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 
    ", 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 }
  • 相关阅读:
    Nginx
    Nginx & AWStats 安装、配置、使用
    Nginx
    linux
    工作中的 Vim 和 git
    后端
    django
    django
    awk流程控制
    linux系统内置函数
  • 原文地址:https://www.cnblogs.com/qxxnxxFight/p/11008060.html
Copyright © 2011-2022 走看看