golang net/dial.go 閱讀筆記

golang net/dial.go

實際上dial.go這個文件中并沒有實際發(fā)起連接的部分,基本上是在為真正發(fā)起連接做一系列的準備筛峭,比如:解析網絡類型、從addr解析ip地址。猜揪。阶界。實際發(fā)起連接的函數在tcpsock_posix.go虹钮、udpsock_posix.go。膘融。芜抒。

首先看一下最主要的類型:

type Dialer struct {
    Timeout time.Duration
  
    Deadline time.Time

    LocalAddr Addr //真正dial時的本地地址,兼容各種類型(TCP托启、UDP...),如果為nil宅倒,則系統(tǒng)自動選擇一個地址

    DualStack bool // 雙協(xié)議棧,即是否同時支持ipv4和ipv6.當network值為tcp時屯耸,dial函數會向host主機的v4和v6地址都發(fā)起連接

    FallbackDelay time.Duration // 當DualStack為真拐迁,ipv6會延后于ipv4發(fā)起,此字段即為延遲時間疗绣,默認為300ms

    KeepAlive time.Duration 

    Resolver *Resolver

    Cancel <-chan struct{} // 用于取消dial
}

Dial是最主要的函數线召,看一下源碼注釋:

// Dial connects to the address on the named network.
//
// Known networks are "tcp", "tcp4" (IPv4-only), "tcp6" (IPv6-only),
// "udp", "udp4" (IPv4-only), "udp6" (IPv6-only), "ip", "ip4"
// (IPv4-only), "ip6" (IPv6-only), "unix", "unixgram" and
// "unixpacket".
//
// For TCP and UDP networks, the address has the form "host:port".
// The host must be a literal IP address, or a host name that can be
// resolved to IP addresses.
// The port must be a literal port number or a service name.
// If the host is a literal IPv6 address it must be enclosed in square
// brackets, as in "[2001:db8::1]:80" or "[fe80::1%zone]:80".
// The zone specifies the scope of the literal IPv6 address as defined
// in RFC 4007.
// The functions JoinHostPort and SplitHostPort manipulate a pair of
// host and port in this form.
// When using TCP, and the host resolves to multiple IP addresses,
// Dial will try each IP address in order until one succeeds.
//
// Examples:
//  Dial("tcp", "golang.org:http")
//  Dial("tcp", "192.0.2.1:http")
//  Dial("tcp", "198.51.100.1:80")
//  Dial("udp", "[2001:db8::1]:domain")
//  Dial("udp", "[fe80::1%lo0]:53")
//  Dial("tcp", ":80")
//
// For IP networks, the network must be "ip", "ip4" or "ip6" followed
// by a colon and a literal protocol number or a protocol name, and
// the address has the form "host". The host must be a literal IP
// address or a literal IPv6 address with zone.
// It depends on each operating system how the operating system
// behaves with a non-well known protocol number such as "0" or "255".
//
// Examples:
//  Dial("ip4:1", "192.0.2.1")
//  Dial("ip6:ipv6-icmp", "2001:db8::1")
//  Dial("ip6:58", "fe80::1%lo0")
//
// For TCP, UDP and IP networks, if the host is empty or a literal
// unspecified IP address, as in ":80", "0.0.0.0:80" or "[::]:80" for
// TCP and UDP, "", "0.0.0.0" or "::" for IP, the local system is
// assumed.
//
// For Unix networks, the address must be a file system path.

從注釋可以看出,Dial 支持多種網絡類型多矮;支持ipv4缓淹、ipv6哈打;還支持用host名代替ip地址。

func Dial(network, address string) (Conn, error) {
    var d Dialer
    return d.Dial(network, address)
}

func DialTimeout(network, address string, timeout time.Duration) (Conn, error) {
    d := Dialer{Timeout: timeout}
    return d.Dial(network, address)
}

func (d *Dialer) Dial(network, address string) (Conn, error) {
    return d.DialContext(context.Background(), network, address)
}

以上前兩個是導出的主要函數讯壶,都調用了d.dial()料仗,d.DialContext()。d.DialContext()可以傳入一個context伏蚊,如果context的生命周期在connect完成之前結束立轧,那么會立即返回錯誤。如果context在連接建立完成之后結束躏吊,則不會影響連接氛改。另外如果addr是一組ip地址的話,會把當前剩下的所有時間均分到每個ip上去嘗試連接比伏。只要有一個成功胜卤,就會立即返回成功的連接并取消其他嘗試。具體看代碼(有刪減):

func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {
    ...
    deadline := d.deadline(ctx, time.Now()) 
    //d.deadline() 比較d.deadline赁项、ctx.deadline瑰艘、now+timeout,返回其中最小.如果都為空肤舞,返回0
    ...
    subCtx, cancel := context.WithDeadline(ctx, deadline) //設置新的超時context
    defer cancel()
    ...
    // Shadow the nettrace (if any) during resolve so Connect events don't fire for DNS lookups.
    resolveCtx := ctx
    ...//給resolveCtx帶上一些value

    addrs, err := d.resolver().resolveAddrList(resolveCtx, "dial", network, address, d.LocalAddr) // 解析IP地址紫新,返回值是一個切片

    dp := &dialParam{
        Dialer:  *d,
        network: network,
        address: address,
    }

    var primaries, fallbacks addrList
    if d.DualStack && network == "tcp" { //表示同時支持ipv4和ipv6
        primaries, fallbacks = addrs.partition(isIPv4) // 將addrs分成兩個切片,前者包含ipv4地址李剖,后者包含ipv6地址
    } else {
        primaries = addrs
    }

    var c Conn
    if len(fallbacks) > 0 {//有ipv6的情況芒率,v4和v6一起dial
        c, err = dialParallel(ctx, dp, primaries, fallbacks)
    } else {
        c, err = dialSerial(ctx, dp, primaries)
    }
    if err != nil {
        return nil, err
    }
    ...
    return c, nil
}

從上面代碼看到,DialContext最終調用的是dialParalleldialSerial,先看dialParallel篙顺,該函數將v4地址和v6地址分開偶芍,先嘗試v4地址組,在dialer.fallbackDelay 時間后開始嘗試v6地址組德玫,每一組都是調用dialSerial(),讓兩組競爭:

func dialParallel(ctx context.Context, dp *dialParam, primaries, fallbacks addrList) (Conn, error) {
    if len(fallbacks) == 0 {
        return dialSerial(ctx, dp, primaries)
    }

    type dialResult struct {
        Conn
        error
        primary bool
        done    bool
    }
    results := make(chan dialResult) // unbuffered

    startRacer := func(ctx context.Context, primary bool) {
        ras := primaries // ras 意思是 remote addresses
        if !primary {
            ras = fallbacks
        }
        c, err := dialSerial(ctx, dp, ras)
        ...
        results <- dialResult{Conn: c, error: err, primary: primary, done: true}
    }

    var primary, fallback dialResult

    // Start the main racer.
    primaryCtx, primaryCancel := context.WithCancel(ctx)
    defer primaryCancel()
    go startRacer(primaryCtx, true) //先嘗試ipv4地址組

    // Start the timer for the fallback racer.
    fallbackTimer := time.NewTimer(dp.fallbackDelay())
    defer fallbackTimer.Stop()

    for {
        select {
        case <-fallbackTimer.C: // ipv6延遲時間到匪蟀,開始嘗試ipv6地址組
            fallbackCtx, fallbackCancel := context.WithCancel(ctx)
            defer fallbackCancel()
            go startRacer(fallbackCtx, false)

        case res := <-results: //表示至少有一組已經建立連接
            if res.error == nil { //
                return res.Conn, nil
            }
            if res.primary {
                primary = res
            } else {
                fallback = res
            }
            if primary.done && fallback.done {//同時建立連接,拋棄
                return nil, primary.error
            }
            if res.primary && fallbackTimer.Stop() {
                // If we were able to stop the timer, that means it
                // was running (hadn't yet started the fallback), but
                // we just got an error on the primary path, so start
                // the fallback immediately (in 0 nanoseconds).
                fallbackTimer.Reset(0)
            }
        }
    }
}

繼續(xù)看dialSerial

func dialSerial(ctx context.Context, dp *dialParam, ras addrList) (Conn, error) {
    var firstErr error // The error from the first address is most relevant.

    for i, ra := range ras { // ra => remote address
        select {
        case <-ctx.Done(): //表示
            return nil, &OpError{Op: "dial", Net: dp.network, Source: dp.LocalAddr, Addr: ra, Err: mapErr(ctx.Err())}
        default:
        }

        deadline, _ := ctx.Deadline()
        partialDeadline, err := partialDeadline(time.Now(), deadline, len(ras)-i)
        // 這里表示前 i 個IP地址的連接失敗宰僧,然后將剩下的時間均分到剩余的IP地址
        ...//判斷是否超時并處理
      
        dialCtx := ctx
        dialCtx, cancel := context.WithDeadline(ctx, partialDeadline)
        defer cancel()

        c, err := dialSingle(dialCtx, dp, ra)// 對單個IP地址發(fā)起連接
        if err == nil {
            return c, nil
        }
        if firstErr == nil {
            firstErr = err
        }
    }

    if firstErr == nil {
        firstErr = &OpError{Op: "dial", Net: dp.network, Source: nil, Addr: nil, Err: errMissingAddress}
    }
    return nil, firstErr
}

最終所有的對單個IP地址發(fā)起鏈接的任務是由dialSingle分配的(此處簡單看下就好)材彪,該函數解決了兼容不同網絡類型的問題:

func dialSingle(ctx context.Context, dp *dialParam, ra Addr) (c Conn, err error) {
    trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace)
    if trace != nil {
        raStr := ra.String()
        if trace.ConnectStart != nil {
            trace.ConnectStart(dp.network, raStr)
        }
        if trace.ConnectDone != nil {
            defer func() { trace.ConnectDone(dp.network, raStr, err) }()
        }
    }
    la := dp.LocalAddr
    switch ra := ra.(type) {
    case *TCPAddr:
        la, _ := la.(*TCPAddr)
        c, err = dialTCP(ctx, dp.network, la, ra)
    case *UDPAddr:
        la, _ := la.(*UDPAddr)
        c, err = dialUDP(ctx, dp.network, la, ra)
    case *IPAddr:
        la, _ := la.(*IPAddr)
        c, err = dialIP(ctx, dp.network, la, ra)
    case *UnixAddr:
        la, _ := la.(*UnixAddr)
        c, err = dialUnix(ctx, dp.network, la, ra)
    default:
        return nil, &OpError{Op: "dial", Net: dp.network, Source: la, Addr: ra, Err: &AddrError{Err: "unexpected address type", Addr: dp.address}}
    }
    if err != nil {
        return nil, &OpError{Op: "dial", Net: dp.network, Source: la, Addr: ra, Err: err} // c is non-nil interface containing nil pointer
    }
    return c, nil
}

到此,dial.go基本就這么多內容琴儿,真正通過socket建立連接的部分下篇再寫吧(其實是偷懶)段化。

(待續(xù))

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市造成,隨后出現(xiàn)的幾起案子显熏,更是在濱河造成了極大的恐慌,老刑警劉巖晒屎,帶你破解...
    沈念sama閱讀 216,919評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喘蟆,死亡現(xiàn)場離奇詭異缓升,居然都是意外死亡,警方通過查閱死者的電腦和手機蕴轨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,567評論 3 392
  • 文/潘曉璐 我一進店門港谊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尺棋,你說我怎么就攤上這事∶圊危” “怎么了膘螟?”我有些...
    開封第一講書人閱讀 163,316評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長碾局。 經常有香客問我荆残,道長,這世上最難降的妖魔是什么净当? 我笑而不...
    開封第一講書人閱讀 58,294評論 1 292
  • 正文 為了忘掉前任内斯,我火速辦了婚禮,結果婚禮上像啼,老公的妹妹穿的比我還像新娘俘闯。我一直安慰自己,他們只是感情好忽冻,可當我...
    茶點故事閱讀 67,318評論 6 390
  • 文/花漫 我一把揭開白布真朗。 她就那樣靜靜地躺著,像睡著了一般僧诚。 火紅的嫁衣襯著肌膚如雪遮婶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,245評論 1 299
  • 那天湖笨,我揣著相機與錄音旗扑,去河邊找鬼。 笑死慈省,一個胖子當著我的面吹牛臀防,可吹牛的內容都是我干的。 我是一名探鬼主播边败,決...
    沈念sama閱讀 40,120評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼清钥,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了放闺?” 一聲冷哼從身側響起祟昭,我...
    開封第一講書人閱讀 38,964評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎怖侦,沒想到半個月后篡悟,有當地人在樹林里發(fā)現(xiàn)了一具尸體谜叹,經...
    沈念sama閱讀 45,376評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,592評論 2 333
  • 正文 我和宋清朗相戀三年搬葬,在試婚紗的時候發(fā)現(xiàn)自己被綠了荷腊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,764評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡急凰,死狀恐怖女仰,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情抡锈,我是刑警寧澤疾忍,帶...
    沈念sama閱讀 35,460評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站床三,受9級特大地震影響一罩,放射性物質發(fā)生泄漏。R本人自食惡果不足惜撇簿,卻給世界環(huán)境...
    茶點故事閱讀 41,070評論 3 327
  • 文/蒙蒙 一聂渊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧四瘫,春花似錦汉嗽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,697評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锹杈,卻和暖如春撵孤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背竭望。 一陣腳步聲響...
    開封第一講書人閱讀 32,846評論 1 269
  • 我被黑心中介騙來泰國打工邪码, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咬清。 一個月前我還...
    沈念sama閱讀 47,819評論 2 370
  • 正文 我出身青樓闭专,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旧烧。 傳聞我的和親對象是個殘疾皇子影钉,可洞房花燭夜當晚...
    茶點故事閱讀 44,665評論 2 354

推薦閱讀更多精彩內容