getConn()
想要进行数据传输首先就是要获取连接。
每一个 Request
都会提取出 协议 和目的地址形成 connectMethod
,然后生成一个 ConnMethodKey
用来表示与这台主机所有的连接,如果没有连接,那就新建一个连接。persistConn
相当于一个数据栈,每次取都会拿最后放上去的连接。
而 idelConnCh
每次有可用的连接都会第一时间拿给将要使用的 Request ,只有当获得连接的整个过程中都没有空闲的连接,才会使用 dialConn()
来新建连接。
如果这里面有连接那就简单多了,直接取用就可以了,特别的事persistConn
相当于一个数据栈,每次取都会拿最后放上去的连接。每次取用之后都会将连接从 map 中删除
if len(pconns) == 1 {
pconn = pconns[0]
delete(t.idleConn, key)
// 只有一个连接,直接删除 map
} else {
pconn = pconns[len(pconns)-1]
t.idleConn[key] = pconns[:len(pconns)-1]
// 拿取最后的 conn
}
DialConn()
在获取连接中,这个函数新建了 persistConn ,默认情况下上 Dial()
都是使用 net 包中普通的 Dial()
进行拨号,当然我们也可以自己给他指定要使用的 Dial()
和 DialTLS()
在进行拨号之后,还新建了两个 gorounite 分别作为读取和写入的入口。
persistconn.roundTrip()
获取连接之后,直接调用 persistconn.roundTrip()
获得 Response
readLoop()
在创建 persistconn 的时候, readLoop()
和 writeLoop()
就已经开始执行了,它会重复读取 reqch 中的内容,然后通过对应的 requestMethodKey 找到对应的 Response,最后通过 responseAndErr 这个 chanle 返回给主 gorountine。
writeLoop()
writeLoop() 则是通过 writech 获得 request 然后使用 早就创建好的 writer 进行传输,返回的错误会通过 writeErrCh 传回主 gorounite 。
主线程中也会通过 select 监听着 writeErrCh 还有 resc 两个 channel ,如果有 response 返回或者 err 不为空都会直接退出。
select {
case err := <-writeErrCh:
if debugRoundTrip {
req.logf("writeErrCh resv: %T/%#v", err, err)
}
if err != nil {
pc.close(fmt.Errorf("write error: %v", err))
return nil, pc.mapRoundTripError(req, startBytesWritten, err)
}
if d := pc.t.ResponseHeaderTimeout; d > 0 {
if debugRoundTrip {
req.logf("starting timer for %v", d)
}
timer := time.NewTimer(d)
defer timer.Stop() // prevent leaks
respHeaderTimer = timer.C
}
case re := <-resc:
if (re.res == nil) == (re.err == nil) {
panic(fmt.Sprintf("internal error: exactly one of res or err should be set; nil=%v", re.res == nil))
}
if debugRoundTrip {
req.logf("resc recv: %p, %T/%#v", re.res, re.err, re.err)
}
if re.err != nil {
return nil, pc.mapRoundTripError(req, startBytesWritten, re.err)
}
return re.res, nil
}