Imagine someone has initialized a rpc client with client, _ := rpc.Dial('tcp', 'example.com:port')
and provides you only the rpc client instance. For some reason, you'd like to get the address example.com
from the client instance. However, as the client type is highly abstracted, you cannot directly get the net.Conn
instance which serves a RemoteAddr()
method. In this case, the reflect package might be helpful.
Lets start with the following attempt:
func getRemoteAddr(client *rpc.Client) net.Addr {
codec := reflect.Indirect(rpclient).FieldByName("codec")
connV := codec.FieldByName("rwc")
conn := connV.Interface().(net.Conn)
return conn.RemoteAddr()
}
However, an error jumped out immediately from the second line:
panic: reflect: call of reflect.Value.FieldByName on interface Value
This is reasonable as codec
is declared as type ClientCodec
which is just an interface with four methods' signatures. However, from the code, we know that the detailed type of codec
might be the unexported struct gobClientCodec
. Unfortunately, the Go compiler is not smart enough to trace down that deep and figure out the actual type. Therefore, is there a way with the reflect package to tell the compiler that I'm confident with the type of codec
to be the unexported gobClientCodec
? If this is not possible, I'll probably have to calculate the address offset and play with the unsafe pointers, which is the last choice.
P.S. The default Type()
method from the reflect method won't work as codec
is declared as ClientCodec
and initialized by passing the address of a gobClientCodec
struct in the NewClient
method as follow:
func NewClient(conn io.ReadWriteCloser) *Client {
encBuf := bufio.NewWriter(conn)
client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
return NewClientWithCodec(client)
}
Also, reflect.TypeOf(rpc.gobClientCodec)
won't work as well as the compiler refuses unexported type usages.
CodePudding user response:
Use .Elem()
to get the value contained in the interface.
Elem returns the value that the interface v contains or that the pointer v points to. It panics if v's Kind is not Interface or Ptr. It returns the zero Value if v is nil.
And for your specific use-case you'll also need to use the unsafe
package to get access to unexported fields. Make sure to read unsafe's documentation before you use it.
func getRemoteAddr(client *rpc.Client) net.Addr {
rv := reflect.ValueOf(client)
rv = rv.Elem() // deref *rpc.Client
rv = rv.FieldByName("codec") // get "codec" field from rpc.Client
rv = rv.Elem() // get the value that ClientCodec contains
rv = rv.Elem() // deref *gobClientCodec
rv = rv.FieldByName("rwc") // get "rwc" field from gobClientCodec
conn := *(*net.Conn)(unsafe.Pointer(rv.UnsafeAddr()))
return conn.RemoteAddr()
}