Go net/dial.go 阅读笔记(二)

Go net/dial.go 阅读笔记(二)

上一篇文章 我们大致分析了dial.go中的代码,起主要的功能就是为真正发起连接做一些准备,起到了应用层的作用(DNS解析等)。但是一个连接完整的连接还需要更深层次的网络协议来完成协作,所以我们接着上篇来分析,由于篇(懒)幅原因,只将dialTcp作为传输层的例子。。。话不多说,上代码:

func dialTCP(ctx context.Context, net string, laddr, raddr *TCPAddr) (*TCPConn, error) {
    if testHookDialTCP != nil { //testHookDialTCP 是语言开发者为了测试留的钩子函数,不用管
        return testHookDialTCP(ctx, net, laddr, raddr)
    }
    return doDialTCP(ctx, net, laddr, raddr)
}

注意现在所在文件是在tcpsock_posix.go 这部分是传输层的内容了。

来看doDialTCP:

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

    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
}

参数里的ctx自然不言而喻了,是为了控制请求超时取消请求释放资源的;laddr是 local address , raddr是指 remote address;返回值这里会得到 TCPConn。代码不长,就是调用了 internetSocket得到一个文件描述符,并用其新建一个conn返回。但这里我想多说几句,因为不难发现, internetSocket可能会被调用多次,为什么呢?

首先我们需要知道 Tcp 有一个极少使用的机制,叫simultaneous connection(同时连接)。正常的连接是:A主机 dial B主机,B主机 listen。 而同时连接则是: A 向 B dial 同时 B 向 A dial,那么 A 和 B 都不需要监听。

我们知道,当 传入 dial 函数的参数laddr==raddr时,内核会拒绝dial。但如果传入的laddr为nil,kernel 会自动选择一个本机端口,这时候有可能会使得新的laddr==raddr,这个时候,kernel不会拒绝dial,并且这个dial会成功,原因是就simultaneous connection,这可能是kernel的bug。所以会判断是否是 selfConnect或者spuriousENOTAVAIL(spurious error not avail)来判断上一次调用internetSocket返回的 err 类型,在特定的情况下重新尝试internetSocket.关于这个问题的讨论参见这里

好了,我们接下来看看internetSocket,该函数在ipsock_posix.go文件,到了网络层的范围了。

func internetSocket(ctx context.Context, net string, laddr, raddr sockaddr, sotype, proto int, mode string) (fd *netFD, err error) {
    if (runtime.GOOS == "windows" || runtime.GOOS == "openbsd" || runtime.GOOS == "nacl") && mode == "dial" && raddr.isWildcard() {
        raddr = raddr.toLocal(net) 
      // 如果 raddr 是零地址,把它转化成当前系统对应的零地址格式(local system address 127.0.0.1 or ::1)
    }
    family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
    return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr)
}

(sotype 和 proto 是生成 socket 文件d的系统调用时用的)首先判断了运行系统的类型,favoriteAddrFamily返回了当前 dial 最合适的地址族,主要是判断应该用ipv4还是ipv6或者都用,其返回值 family 有两种可能值:AF_INETAF_INET6,都是int类型,感兴趣的朋友可以参见这里

让我们接着关注socket,该函数在sock_posix.go文件,意味着接下来将是更加底层的系统调用了。

// socket returns a network file descriptor that is ready for
// asynchronous I/O using the network poller.
func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr) (fd *netFD, err error) {
    s, err := sysSocket(family, sotype, proto)
    if err != nil {
        return nil, err
    }
    if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil {
        poll.CloseFunc(s)
        return nil, err
    }
    if fd, err = newFD(s, family, sotype, net); err != nil {
        poll.CloseFunc(s)
        return nil, err
    }

    // This function makes a network file descriptor for the
    // following applications:
    //
    // - An endpoint holder that opens a passive stream
    //   connection, known as a stream listener
    //
    // - An endpoint holder that opens a destination-unspecific
    //   datagram connection, known as a datagram listener
    //
    // - An endpoint holder that opens an active stream or a
    //   destination-specific datagram connection, known as a
    //   dialer
    //
    // - An endpoint holder that opens the other connection, such
    //   as talking to the protocol stack inside the kernel
    //
    // For stream and datagram listeners, they will only require
    // named sockets, so we can assume that it's just a request
    // from stream or datagram listeners when laddr is not nil but
    // raddr is nil. Otherwise we assume it's just for dialers or
    // the other connection holders.

    if laddr != nil && raddr == nil {
        switch sotype {
        case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:
            if err := fd.listenStream(laddr, listenerBacklog); err != nil {
                fd.Close()
                return nil, err
            }
            return fd, nil
        case syscall.SOCK_DGRAM:
            if err := fd.listenDatagram(laddr); err != nil {
                fd.Close()
                return nil, err
            }
            return fd, nil
        }
    }
    if err := fd.dial(ctx, laddr, raddr); err != nil {
        fd.Close()
        return nil, err
    }
    return fd, nil
}

这段代码隐含了大量细节,首先看最上面函数的注释,返回值是一个使用了network poller异步I/O的文件描述符。前面三个 if 里,先创建了一个 socket,然后设置基本参数,再 new 一个文件描述符,其中包含了大量的系统调用和底层细节,这里先跳过。我想说的在下面。

socket 这个函数可以为一下几种应用创建一个文件描述符:

  • 一个打开了 被动的、流式的 连接的终端,通常叫stream listener
  • 一个打开了 没有具体目的地的、数据报格式的 连接的终端,通常叫datagram listener
  • 一个打开了 主动的、有明确目的地的、数据报格式的 连接的终端,通常叫dialer
  • 一个打开了其他连接的终端,比如与内核中的协议栈通信

通常可以认为当 laddr不为空但raddr为空时的 request 是来自stream or datagram listeners。否则就是来自 dialers 或者其他系统连接。

所以一个dialer和listener的区别就是 laddr, 也就是dialer在一定情况下可以当做listener,到这里就可以解释之前tcp的simultaneous connection同时连接了。

接下来调用了fd的dial函数,这里才真正通过socket开始发送连接请求。

(待续)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容