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 := ctx
if trace, _ := ctx.Value(nettrace.TraceKey{
}).(*nettrace.Trace); trace != nil {
shadow := *trace
// 设置在拨号之前之后都不调用该上下文的值
shadow.ConnectStart = nil
shadow.ConnectDone = nil
// 返回父级的副本,提供的键必须可比较、用户需要自己定义类型
resolveCtx = context.WithValue(resolveCtx, nettrace.TraceKey{
}, &shadow)
}
// 5、 设置解析器, 并根据解析器返回地址列表,
// d.resolver()方法:如果设置了解析器就返回,没设置就返回默认解析器
// resolveAddrList() 方法: 调用parseNetwork解析出string的网络协议,根据不同网络类型分别调用ResolveUnixAddr,和internetAddrList方法解析出地址(文字IP地址或DNS名称).</