1、调用 例子

conn, err := net.Dial("tcp", "google.com:80")
if err != nil {// handle error
}
fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
status, err := bufio.NewReader(conn).ReadString('\n')
// ...

源码dial.go文件中没有实际发起连接,主要是对一些参数进行预处理,比如:解析网络类型、从addr解析ip地址,而实际发起连接的函数在tcpsock_posix.go、udpsock_posix.go。
2、net.Dial的源码解读

// net.Dial函数解读
//  实际是对Dialer.Dial的一个封装, 封装后,可以直接调用Dial拨号,而不需要再去定义一个Dialer结构体对象,利用对象拨号,省去了定义结构体对象,封装时用的同名方法,便于记忆
func Dial(network, address string) (Conn, error) {var d Dialer      // 定义了一个 Dialer结构体对象,使用该对象的Dial方法去拨号,所以net.Dial 实际是对Dialer.Dial的一个封装return d.Dial(network, address)
}

3、Dialer.DialContext 解读
d.DialContext()可以传入一个context,如果context的生命周期在connect完成之前结束,那么会立即返回错误。如果context在连接建立完成之后结束,则不会影响连接。另外如果addr是一组ip地址的话,会把当前剩下的所有时间均分到每个ip上去尝试连接。只要有一个成功,就会立即返回成功的连接并取消其他尝试

// Dialer.DialContext 解读
// DialContext是结构体Dialer对象的原始拨号方法,入参三个(ctx上下文用于设置拨号对象的上下文截止时间、)
// DialContext使用提供的上下文连接到指定网络上的地址, 上下文必须非0且不能过期
// 主机的每个ip链接时间 是timeout/n,n是多少个ip,
func (d *Dialer) DialContext(ctx context.Context, network, address string) (Conn, error) {// 1、首先判断参数,上下文为空报错if ctx == nil {panic("nil context")}// 2、获取dialer和上下文的最早的deadline, (ctx上下文用于设置拨号对象的上下文截止时间、time.now()用于计算设置timeout字段时候的截止时间)deadline := d.deadline(ctx, time.Now())if !deadline.IsZero() {// 当计算出了截止时间:如果上下文没有截止时间、或者最终截止时间早于上下文截止时间if d, ok := ctx.Deadline(); !ok || deadline.Before(d) {// 就将最终截止时间 设置成 上下文的截止时间处理, 还会返回一个cancel函数,在最后调用subCtx, cancel := context.WithDeadline(ctx, deadline)defer cancel()// 构建上下文:返参 subCtx就是父上下文的一个副本,现在赋值回去ctx = subCtx}}// 3、拨号对象 调用取消通道读值,如果有取消值if oldCancel := d.Cancel; oldCancel != nil {// 就对上下文取消操作:WithCancel 返回具有新cancel通道的父级的副本subCtx, cancel := context.WithCancel(ctx)defer cancel()   // 一旦在此上下文中运行的操作完成,就调用cancel// 开启协程判断 拨号对象的取消通道读值,和上下文完成的工作应取消时,Done返回一个关闭的通道读值,任何一个先完成,都会进行取消操作go func() {select {case <-oldCancel:cancel()case <-subCtx.Done():}}()// 将副本 赋值给 父上下文ctx = subCtx}// 4、在解析期间对nettrace(如果有的话)进行阴影处理,这样就不会为DNS查找触发连接事件// 调用  import "internal/nettrace",TraceKey{}是一个上下文得键key, 关联值应该是*Trace结构, 取出值后类型断言成*nettrace.Trace类型resolveCtx := ctxif trace, _ := ctx.Value(nettrace.TraceKey{}).(*nettrace.Trace); trace != nil {shadow := *trace// 设置在拨号之前之后都不调用该上下文的值shadow.ConnectStart = nilshadow.ConnectDone = nil// 返回父级的副本,提供的键必须可比较、用户需要自己定义类型resolveCtx = context.WithValue(resolveCtx, nettrace.TraceKey{}, &shadow)}// 5、 设置解析器, 并根据解析器返回地址列表,// d.resolver()方法:如果设置了解析器就返回,没设置就返回默认解析器// resolveAddrList() 方法: 调用parseNetwork解析出string的网络协议,根据不同网络类型分别调用ResolveUnixAddr,和internetAddrList方法解析出地址(文字IP地址或DNS名称).addrs, err := d.resolver().resolveAddrList(resolveCtx, "dial", network, address, d.LocalAddr)if err != nil {return nil, &OpError{Op: "dial", Net: network, Source: nil, Addr: nil, Err: err}}// 6、定义一个带有配置的 系统拨号对象(配置由参数传递)sd := &sysDialer{Dialer:  *d,network: network,address: address,}var primaries, fallbacks addrList// 7、如果tcp拨号对象有设置 快速回退等待时长,则将地址列表分为两类(每个地址有一个布尔标签。第一个地址和任何具有匹配标签的地址将作为主地址返回,而具有相反标签的地址将作为回退返回)// dualStack()是d.FallbackDelay >= 0时的封装, 返回bool, FallbackDelay是快速回退等待的时间长if d.dualStack() && network == "tcp" {primaries, fallbacks = addrs.partition(isIPv4)     // 两类地址} else {// 如果没有设置,则直接将地址列表传给自定义primaries初选变量primaries = addrs}var c Conn// 8、如果 回退地址有值,传入回退地址表然后使用系统拨号对象开始拨号: dialParallel(),与dialSerial的两个副本进行竞争,返回第一个建立的连接并关闭其他连接if len(fallbacks) > 0 {c, err = sd.dialParallel(ctx, primaries, fallbacks)} else {// 9、否则如果没有设置,dialSerial()方法 按顺序连接到地址列表,返回第一个成功连接或第一个错误c, err = sd.dialSerial(ctx, primaries)}if err != nil {return nil, err}// 10、Conn转TCPConn,如果拨号且处于连接状态:调用setKeepAlive()将 包装网络调用、标记网络文件描述符参数可访问、得到一个新的系统错误if tc, ok := c.(*TCPConn); ok && d.KeepAlive >= 0 {// tc.fd,连接里的唯一字段:网络文件描述符结构体setKeepAlive(tc.fd, true)// setKeepAlive() 是SetsockoptInt(用int参数包装setsockopt网络调用)、KeepAlive将其参数fd 标记为当前可访问、//  wrapSyscallError(接受一个错误和一个syscall名称,返回一个新的SyscallError,其中包含给定的系统调用名和错误详细信息)// 将连接时长传给ka, 如果没设置则默认值15ska := d.KeepAliveif d.KeepAlive == 0 {ka = defaultTCPKeepAlive}// 根据 网络文件描述符 设置保持连接时间setKeepAlivePeriod(tc.fd, ka)// 测试testHookSetKeepAlive(ka)}return c, nil
}

DialContext最终调用的是dialParallel和dialSerial,先看dialParallel,该函数将v4地址和v6地址分开,先尝试v4地址组,在dialer.fallbackDelay 时间后开始尝试v6地址组,每一组都是调用dialSerial(),让两组竞争:

4、dialParallel()源码解读

// dialParallel 竞赛dialSerial的两个副本,给第一个先机。它返回第一个建立的连接并关闭其他连接。 否则,它将从第一个主地址返回一个错误。
func (sd *sysDialer) dialParallel(ctx context.Context, primaries, fallbacks addrList) (Conn, error) {// 如果回退地址为空,还是调用dialSerial()按顺序连接地址if len(fallbacks) == 0 {return sd.dialSerial(ctx, primaries)}returned := make(chan struct{})defer close(returned)// 拨号结果对象type dialResult struct {Connerrorprimary booldone    bool}results := make(chan dialResult) // unbuffered// 跟踪处理函数,根据传入的primary决定使用哪个地址拨号// ras: remote addressesstartRacer := func(ctx context.Context, primary bool) {ras := primariesif !primary {ras = fallbacks}// 无论哪列地址,都调用按顺序拨号,写入结果通道c, err := sd.dialSerial(ctx, ras)select {case results <- dialResult{Conn: c, error: err, primary: primary, done: true}:case <-returned:// 当返回通道能读出数据,且有连接对象返回,关闭连接if c != nil {c.Close()}}}var primary, fallback dialResult// Start the main racer.// 使用传入的上下文 新建一个具有新完成通道的父级(ctx)的副本primaryCtx, primaryCancel := context.WithCancel(ctx)defer primaryCancel()// 协程,调用处理函数,根据具有取消通道的上下文,此时primary为真,则不使用会提add,使用第一个addgo startRacer(primaryCtx, true)// 启动计时器,回退时间为周期,使用完成后defer关闭这个计时器fallbackTimer := time.NewTimer(sd.fallbackDelay())defer fallbackTimer.Stop()for {select {// ipv6延迟时间到,开始尝试ipv6地址组case <-fallbackTimer.C: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() {//如果我们能够停止计时器,这意味着它正在运行(尚未启动回退),但是我们在主路径上遇到了一个错误,所以立即启动回退fallbackTimer.Reset(0)}}}
}

5、sd.dialSerial源码解读

// 按顺序连接到地址列表,返回第一个成功连接或第一个错误
func (sd *sysDialer) dialSerial(ctx context.Context, ras addrList) (Conn, error) {var firstErr error // 链接的第一个错误// 遍历 地址for i, ra := range ras {// 检测上下文是否取消,取消则返回select {case <-ctx.Done():return nil, &OpError{Op: "dial", Net: sd.network, Source: sd.LocalAddr, Addr: ra, Err: mapErr(ctx.Err())}default:}// 上下文截止时间处理dialCtx := ctx// 前 i 个IP地址的连接失败,然后将剩下的时间均分到剩余的IP地址if deadline, hasDeadline := ctx.Deadline(); hasDeadline {// 取出上下文的deadline, 当多个地址挂起时,partialDeadline()根据当前时间、截止时间、剩余地址数 返回用于单个地址的截止日期partialDeadline, err := partialDeadline(time.Now(), deadline, len(ras)-i)if err != nil {// Ran out of time.if firstErr == nil {firstErr = &OpError{Op: "dial", Net: sd.network, Source: sd.LocalAddr, Addr: ra, Err: err}}break}// 如果取出单个地址截止时间早于 求得的截止时间, 则将单个地址取得的截止时间设置成 新的上下文的截止时间if partialDeadline.Before(deadline) {var cancel context.CancelFuncdialCtx, cancel = context.WithDeadline(ctx, partialDeadline)defer cancel()}}// 使用新截止时间的上下文和 地址,建立单个连接,并返回c, err := sd.dialSingle(dialCtx, ra)if err == nil {return c, nil}if firstErr == nil {firstErr = err}}if firstErr == nil {firstErr = &OpError{Op: "dial", Net: sd.network, Source: nil, Addr: nil, Err: errMissingAddress}}return nil, firstErr
}

6、处理deadlien 的方法 partialDeadline源码

// 返回在多个地址挂起时用于单个地址的截止日期
// 使用 : partialDeadline(time.Now(), deadline, len(ras)-i)
func partialDeadline(now, deadline time.Time, addrsRemaining int) (time.Time, error) {// 如果时间是0,则返回if deadline.IsZero() {return deadline, nil}// 和当前时间的间距, 需要大于当前时间timeRemaining := deadline.Sub(now)if timeRemaining <= 0 {return time.Time{}, errTimeout}// timeout 为每个 剩余地址分配相等的时间timeout := timeRemaining / time.Duration(addrsRemaining)// 如果每个地址的时间太短,则从列表的末尾窃取const saneMinimum = 2 * time.Secondif timeout < saneMinimum {if timeRemaining < saneMinimum {timeout = timeRemaining  // 时间太短, 每个地址的截止时间为 剩余的时间} else {timeout = saneMinimum    // 否则,就是最短时间2s}}// 截止时间就是计算出的 此时+timeoutreturn now.Add(timeout), nil
}

7、上下文context设置截止时间的方法 WithDeadline

// 如果父上下文的截止时间小于d, 则此时还是等于父上下文
func WithDeadline(parent Context, d time.Time) (Context, CancelFunc) {if parent == nil {panic("cannot create context from nil parent")}if cur, ok := parent.Deadline(); ok && cur.Before(d) {// The current deadline is already sooner than the new one.return WithCancel(parent)}c := &timerCtx{cancelCtx: newCancelCtx(parent),deadline:  d,}propagateCancel(parent, c)dur := time.Until(d)if dur <= 0 {c.cancel(true, DeadlineExceeded) // deadline has already passedreturn c, func() { c.cancel(false, Canceled) }}c.mu.Lock()defer c.mu.Unlock()if c.err == nil {c.timer = time.AfterFunc(dur, func() {c.cancel(true, DeadlineExceeded)})}return c, func() { c.cancel(true, Canceled) }
}

8、上下文context 结构体源码解读

// 上下文包含截止日期、取消信号和其他值
// 多个goroutine可以同时调用Context的方法
type Context interface {// 返回代表此上下文完成的工作应取消的时间,没有设置返回falseDeadline() (deadline time.Time, ok bool)// 当代表此上下文完成的工作应取消时,Done返回一个关闭的通道。// 如果无法取消此上下文,则Done可能返回nil。// cancel函数返回后,Done通道的关闭可能会异步进行// WithCancel安排在调用cancel时关闭Done// WithTimeout安排在超时结束时关闭DoneDone() <-chan struct{}// 关闭了Done,Err将返回一个非nil错误Err() error// 仅对传输进程和API边界的请求范围的数据使用上下文值,而不是将可选参数传递给函数// 希望在上下文中存储值的函数通常在全局变量中分配一个键,然后将该键用作context.with值和Context.Value.// 客户端使用user.NewContext以及user.FromContext,NewContext返回一个携带值u的新上下文,// 其实是return context.WithValue(ctx, userKey, u)函数的封装; FromContext返回存储在ctx中的用户值,其实是u, ok := ctx.Value(userKey).(*User) 的封装Value(key interface{}) interface{}
}type CancelFunc func()

9、其中的 拨号对象 Dialer 解读

// 拨号对象解读
// 包含一些连接的选项,零值代表不带该选项
type Dialer struct {// 是拨号将等待的最大时间量// 如果没有设置,则使用操作系统的较早超时,tcp是3min左右Timeout time.Duration// 截止日期 是拨号失败的绝对时间点Deadline time.Time// 拨号时使用的本地地址,为0是自动选择本地地址// 真正dial时的本地地址,兼容各种类型(TCP、UDP...),如果为nil,则系统自动选择一个地址LocalAddr Addr       // Addr网络端点地址,是接口// 以前支持rfc6555快速回退支持,也被称为“快乐眼球”,如果IPv6被错误配置和挂起,IPv4将很快被尝试// 双协议栈,即是否同时支持ipv4和ipv6.当network值为tcp时,dial函数会向host主机的v4和v6地址都发起连接// 已弃用:默认情况下启用快速回退DualStack bool// 当DualStack为真,ipv6会延后于ipv4发起,此字段即为延迟时间,默认为300msFallbackDelay time.Duration// 指定活动网络连接的keep-alive探测之间的间隔,如果协议和操作系统支持,那默认15sKeepAlive time.Duration// 解析器Resolver *Resolver// 可选通道,其关闭指示应取消拨号// 这个取消不推荐使用,改用了DialContextCancel <-chan struct{}// 如果有传这个控制函数,创建网络连接后但在实际拨号之前调用它,传递给控制方法的网络和地址参数不一定是传递给拨号的参数。Control func(network, address string, c syscall.RawConn) error
}

10、拨号对象设置 截止日期的方法 (d *Dialer) deadline

// 拨号对象设置 截止日期的方法
// 截止日期返回以下最早日期:now+Timeout、d.Deadline、上下文的deadline, 如果没有设置则是0
func (d *Dialer) deadline(ctx context.Context, now time.Time) (earliest time.Time) {// 1、如果有设置超时,则将设置的超时传给最早结束时间if d.Timeout != 0 { // including negative, for historical reasonsearliest = now.Add(d.Timeout)}// 2、如果获取到上下文的截止时间,则取两者最早的哪个时间if d, ok := ctx.Deadline(); ok {earliest = minNonzeroTime(earliest, d)}// 3、最后对比结构体对象的字段;如果设置了字段-截止时间,则取截止时间和上述的对比结果时间return minNonzeroTime(earliest, d.Deadline)
}

11、自己在包内定义 最早时间对比方法

func minNonzeroTime(a, b time.Time) time.Time {// 如果有一方时间未设置,则舍弃if a.IsZero() {return b}// 调用time.Before运算():该方法主要是在不同条件时,对入参时间的的s数对比,或者ns数对比if b.IsZero() || a.Before(b) {return a}return b
}

目前,dial.go源码解读相关就是这些。源码也是调用了内置库的一些其他的处理函数,考虑到很多情况的判断,比如参数的是否零值、不同传值需要调用的处理函数。
总之,dial只是做了一些预设置,并且返回了一个连接对象。
在网络network上连接地址address,并返回一个Conn接口。可用的网络类型有:
“tcp”、“tcp4”、“tcp6”、“udp”、“udp4”、“udp6”、“ip”、“ip4”、“ip6”、“unix”、“unixgram”、“unixpacket”
对TCP和UDP网络,地址格式是host:port或[host]:port,参见函数JoinHostPort和SplitHostPort。

golang源码解读之 net.Dial相关推荐

  1. golang源码阅读-net.Dial

    1.基本使用 //获取链接 conn, err := net.Dial("tcp", "0.0.0.0:8080")if err != nil {fmt.Pri ...

  2. Bert系列(二)——源码解读之模型主体

    本篇文章主要是解读模型主体代码modeling.py.在阅读这篇文章之前希望读者们对bert的相关理论有一定的了解,尤其是transformer的结构原理,网上的资料很多,本文内容对原理部分就不做过多 ...

  3. Bert系列(三)——源码解读之Pre-train

    https://www.jianshu.com/p/22e462f01d8c pre-train是迁移学习的基础,虽然Google已经发布了各种预训练好的模型,而且因为资源消耗巨大,自己再预训练也不现 ...

  4. linux下free源码,linux命令free源码解读:Procps free.c

    linux命令free源码解读 linux命令free源码解读:Procps free.c 作者:isayme 发布时间:September 26, 2011 分类:Linux 我们讨论的是linux ...

  5. nodeJS之eventproxy源码解读

    1.源码缩影 !(function (name, definition) { var hasDefine = typeof define === 'function', //检查上下文环境是否为AMD ...

  6. golang源码分析-调度概述

    golang源码分析-调度过程概述 本文主要概述一下golang的调度器的大概工作的流程,众所周知golang是基于用户态的协程的调度来完成多任务的执行.在Linux操作系统中,以往的多线程执行都是通 ...

  7. golang源码分析-启动过程概述

    golang源码分析-启动过程概述 golang语言作为根据CSP模型实现的一种强类型的语言,本文主要就是通过简单的实例来分析一下golang语言的启动流程,为深入了解与学习做铺垫. golang代码 ...

  8. PyTorch 源码解读之即时编译篇

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 作者丨OpenMMLab 来源丨https://zhuanlan.zhihu.com/ ...

  9. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

最新文章

  1. 【怎样写代码】向现有类型“添加”方法 -- 扩展方法(三):为枚举类型创建方法
  2. Oracle的控制文件
  3. sorted set 数据类型的应用场景
  4. 计算机文字处理操作题33,2017年国家计算机等级考试试题【文字处理】.doc
  5. Leetcode - 142. Linked List Cycle II
  6. 多重共线性的解决方法之——岭回归与LASSO
  7. Html5 h5页面输入框失去焦点页面底部白板问题
  8. 如何在邮件中加入html代码,如何在电子邮件正文中嵌入HTML文件
  9. linux启动器编辑,linux下建立启动器
  10. dnf体验服显示服务器爆满,DNF:体验服刚更新就爆满,官方临时加频道,100级真那么好玩?...
  11. linux程序员笔记软件,作为合格的程序员,你得知道这8款工具软件
  12. 关于前端一个用于设置渐变色的css代码网站
  13. 如何用python画房子_用python画一个小房子
  14. 课程设计题四:LED彩灯控制器设计
  15. 初学Shader关于旗帜飘扬的一个简单shader的实现
  16. (原創) 如何將16進位的ACSII值轉成相對應的字元? (C/C++) (C)
  17. MOOS通信框架初探(一)
  18. 微信平台:数字藏品平台出现二级交易市场违规封号
  19. 高新技术企业认定条件及流程
  20. Contos7系统下无法查看ip地址的解决方法

热门文章

  1. CSS导航条制作心得
  2. java 反射setter_Java反射机制-调用setter及getter方法
  3. 怎么才能让电脑无法玩英雄联盟LOL?
  4. css3动画案例—太阳大海跳动
  5. matlab画雪花,使用C++ OpenGL,完成Konx 雪花绘图
  6. 读《包法利夫人》有感
  7. beautifulsoup爬取网页中的表格_python爬取哔哩哔哩网页弹幕内容,并将爬取的内容以五角星的形式显示出来...
  8. Python 打家劫舍
  9. 【博学谷学习记录】超强总结,用心分享丨人工智能 多场景实战 常用英文缩写概念总结
  10. #P07751. 龟兔赛跑