RoundTrip 会调用到 roundTrip 方法中:
func (t *Transport) roundTrip(req *Request) (*Response, error) { t.nextProtoOnce.Do(t.onceSetNextProtoDefaults) ctx := req.Context() trace := httptrace.ContextClientTrace(ctx) ... for { select { case <-ctx.Done(): req.closeBody() return nil, ctx.Err() default: } // 封装请求 treq := &transportRequest{Request: req, trace: trace, cancelKey: cancelKey} cm, err := t.connectMethodForRequest(treq) if err != nil { req.closeBody() return nil, err } // 获取连接 pconn, err := t.getConn(treq, cm) if err != nil { t.setReqCanceler(cancelKey, nil) req.closeBody() return nil, err } // 等待响应结果 var resp *Response if pconn.alt != nil { // HTTP/2 path. t.setReqCanceler(cancelKey, nil) // not cancelable with CancelRequest resp, err = pconn.alt.RoundTrip(req) } else { resp, err = pconn.roundTrip(treq) } if err == nil { resp.Request = origReq return resp, nil } ... } }roundTrip 方法会做两件事情:
调用 Transport 的 getConn 方法获取连接;
在获取到连接后,调用 persistConn 的 roundTrip 方法等待请求响应结果;
获取连接 getConngetConn 有两个阶段:
调用 queueForIdleConn 获取空闲 connection;
调用 queueForDial 等待创建新的 connection;
成功获取到空闲 connection:
成功获取 connection 分为如下几步:
根据当前的请求的地址去空闲 connection 字典中查看存不存在空闲的 connection 列表;
如果能获取到空闲的 connection 列表,那么获取到列表的最后一个 connection;
返回;
获取不到空闲 connection:
当获取不到空闲 connection 时:
根据当前的请求的地址去空闲 connection 字典中查看存不存在空闲的 connection 列表;
不存在该请求的 connection 列表,那么将该 wantConn 加入到 等待获取空闲 connection 字典中;
从上面的图解应该就很能看出这一步会怎么操作了,这里简要的分析一下代码,让大家更清楚里面的逻辑:
func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) { if t.DisableKeepAlives { return false } t.idleMu.Lock() defer t.idleMu.Unlock() t.closeIdle = false if w == nil { return false } // 计算空闲连接超时时间 var oldTime time.Time if t.IdleConnTimeout > 0 { oldTime = time.Now().Add(-t.IdleConnTimeout) } // Look for most recently-used idle connection. // 找到key相同的 connection 列表 if list, ok := t.idleConn[w.key]; ok { stop := false delivered := false for len(list) > 0 && !stop { // 找到connection列表最后一个 pconn := list[len(list)-1] // 检查这个 connection 是不是等待太久了 tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime) if tooOld { go pconn.closeConnIfStillIdle() } // 该 connection 被标记为 broken 或 闲置太久 continue if pconn.isBroken() || tooOld { list = list[:len(list)-1] continue } // 尝试将该 connection 写入到 w 中 delivered = w.tryDeliver(pconn, nil) if delivered { // 操作成功,需要将 connection 从空闲列表中移除 if pconn.alt != nil { } else { t.idleLRU.remove(pconn) list = list[:len(list)-1] } } stop = true } if len(list) > 0 { t.idleConn[w.key] = list } else { // 如果该 key 对应的空闲列表不存在,那么将该key从字典中移除 delete(t.idleConn, w.key) } if stop { return delivered } } // 如果找不到空闲的 connection if t.idleConnWait == nil { t.idleConnWait = make(map[connectMethodKey]wantConnQueue) } // 将该 wantConn 加入到 等待获取空闲 connection 字典中 q := t.idleConnWait[w.key] q.cleanFront() q.pushBack(w) t.idleConnWait[w.key] = q return false}上面的注释已经很清楚了,我这里就不再解释了。
建立连接 queueForDial在获取不到空闲连接之后,会尝试去建立连接,从上面的图大致可以看到,总共分为以下几个步骤:
在调用 queueForDial 方法的时候会校验 MaxConnsPerHost 是否未设置或已达上限;
检验不通过则将当前的请求放入到 connsPerHostWait 等待字典中;
如果校验通过那么会异步的调用 dialConnFor 方法创建连接;
dialConnFor 方法首先会调用 dialConn 方法创建 TCP 连接,然后启动两个异步线程来处理读写数据,然后调用 tryDeliver 将连接绑定到 wantConn 上面。