新手问题 nsq客户端go-nsq有段代码看不懂

kekemuyu · 2017年11月05日 · 最后由 kekemuyu 回复于 2017年11月06日 · 532 次阅读

代码来源:go-nsq 中的 conn.go 中的 Connect() Connect() 方法源码:

// Connect dials and bootstraps the nsqd connection
// (including IDENTIFY) and returns the IdentifyResponse
func (c *Conn) Connect() (*IdentifyResponse, error) {
    dialer := &net.Dialer{
        LocalAddr: c.config.LocalAddr,
        Timeout:   c.config.DialTimeout,
    }

    conn, err := dialer.Dial("tcp", c.addr)
    if err != nil {
        return nil, err
    }
    c.conn = conn.(*net.TCPConn)
    c.r = conn
    c.w = conn

    _, err = c.Write(MagicV2)
    if err != nil {
        c.Close()
        return nil, fmt.Errorf("[%s] failed to write magic - %s", c.addr, err)
    }

    resp, err := c.identify()
    if err != nil {
        return nil, err
    }

    if resp != nil && resp.AuthRequired {
        if c.config.AuthSecret == "" {
            c.log(LogLevelError, "Auth Required")
            return nil, errors.New("Auth Required")
        }
        err := c.auth(c.config.AuthSecret)
        if err != nil {
            c.log(LogLevelError, "Auth Failed %s", err)
            return nil, err
        }
    }

    c.wg.Add(2)
    atomic.StoreInt32(&c.readLoopRunning, 1)
    go c.readLoop()
    go c.writeLoop()
    return resp, nil
}

这个方法中困惑的地方是 dialer.Dial 返回的 conn 类型是 type Conn interface{..}接口,但是为什么可以从 conn 中取出 (*net.TCPConn) 类型的数据

conn, err := dialer.Dial("tcp", c.addr)
    if err != nil {
        return nil, err
    }
    c.conn = conn.(*net.TCPConn)
更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio

接口值由两部分组成:某个具体类型和该具体类型的值,分别叫做该接口值的动态类型和动态值。

c.conn = conn.(*net.TCPConn) 这种表达式叫做 Type Assertions,类似于 x.(T)。 它有两种情况:

  • 如果 T 是某个具体类型,表达式检查该 x 的动态类型是否是 T,如果是,表达式的值就是接口值的动态值
  • 如果 T 是接口类型,表达式检查 x 的动态类型是否满足 T,如果满足,表达式的值是 接口类型 T 的值了。

在这里,dialer.Dial("tcp", c.addr) ,第一个参数 network 是 tcp,返回的接口值的动态类型就是 net.TCPConn 具体的用法你可以看下,《The Go Programming Language》 的 7.10 Type Assertions

type Conn interface {
    Read(b []byte) (n int, err error)
    Write(b []byte) (n int, err error)
    Close() error
    LocalAddr() Addr
    RemoteAddr() Addr
    SetDeadline(t time.Time) error
    SetReadDeadline(t time.Time) error
    SetWriteDeadline(t time.Time) error
}

只要结构体实现了这些方法,就能进行类型转换 鸭子类型

小伙子语法不熟悉, 看看这里: Go 语言圣经-7.10. 类型断言

看了一下 dialer.Dial("tcp", c.addr) 方法的内部实现,经过层层跳转,确实是返回了*net.Tcp 类型的数据。 最终跳到的函数是:

func dialTCP(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
    if testHookDialTCP != nil {
        return testHookDialTCP(ctx, net, laddr, raddr)
    }
    return doDialTCP(ctx, net, laddr, raddr)
}

func doDialTCP(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
    fd, err := internetSocket(ctx, net, laddr, raddr, syscall.SOCK_STREAM, 0, "dial")

    // TCP has a rarely used mechanism called a 'simultaneous connection' in
    // which Dial("tcp", addr1, addr2) run on the machine at addr1 can
    // connect to a simultaneous Dial("tcp", addr2, addr1) run on the machine
    // at addr2, without either machine executing Listen. If laddr == nil,
    // it means we want the kernel to pick an appropriate originating local
    // address. Some Linux kernels cycle blindly through a fixed range of
    // local ports, regardless of destination port. If a kernel happens to
    // pick local port 50001 as the source for a Dial("tcp", "", "localhost:50001"),
    // then the Dial will succeed, having simultaneously connected to itself.
    // This can only happen when we are letting the kernel pick a port (laddr == nil)
    // and when there is no listener for the destination address.
    // It's hard to argue this is anything other than a kernel bug. If we
    // see this happen, rather than expose the buggy effect to users, we
    // close the fd and try again. If it happens twice more, we relent and
    // use the result. See also:
    //  https://golang.org/issue/2690
    //  http://stackoverflow.com/questions/4949858/
    //
    // The opposite can also happen: if we ask the kernel to pick an appropriate
    // originating local address, sometimes it picks one that is already in use.
    // So if the error is EADDRNOTAVAIL, we have to try again too, just for
    // a different reason.
    //
    // The kernel socket code is no doubt enjoying watching us squirm.
    for i := 0; i < 2 && (laddr == nil || laddr.Port == 0) && (selfConnect(fd, err) || spuriousENOTAVAIL(err)); i++ {
        if err == nil {
            fd.Close()
        }
        fd, err = internetSocket(ctx, net, laddr, raddr, syscall.SOCK_STREAM, 0, "dial")
    }

    if err != nil {
        return nil, err
    }
    return newTCPConn(fd), nil
}

但我现在疑惑的是 Conn 接口是非空接口,*net.Tcp 要实现这个接口必须得有相同的方法吧,但是我好想没看到有相同的方法。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册