go net http 笔记 连接复用

  目前使用net/http 长连接 连接池复用时,出现连接一直都在关闭重连,

strace 后发现没有read respone 只读取了head,但是body没有读取,

google后看到官方问题,确实记录需要代码实现读取body逻辑来清空socket内核缓存,以便后面复用。

需要读取respone里面的body

io.Copy(ioutil.Discard, resp.Body)

也就是默认只是读取了header,但并未读取完response body数据。需要自己手动读取body,后面才可以复用这个链接。

没有执行 ioutil.ReadAll(resp.Body)。也就是说http请求响应的结果并没有被读取的情况下,net/http库会怎么处理。

上面的代码最终输出3,分别是main goroutine,read goroutine 以及write goroutine。也就是说长连接没有断开,那长连接是会在下一次http请求中被复用吗?先说答案,不会复用。

我们可以看代码。resp.Body.Close() 会执行到 func (es * bodyEOFSignal) Close() error 中,并执行到es.earlyCloseFn()中。

earlyCloseFn的逻辑也非常简单,就是将一个false传入到waitForBodyRead的channel中。那写入通道后的数据会在另外一个地方被读取,我们来看下读取的地方。

bodyEOF为false, 也就不需要执行 tryPutIdleConn()方法。

tryPutIdleConn会将连接放到长连接池中备用)。

最终就是alive=bodyEOF ,也就是false,字面意思就是该连接不再存活。因此该长连接并不会复用,而是会释放。

func (es *bodyEOFSignal) Close() error {
	es.mu.Lock()
	defer es.mu.Unlock()
	if es.closed {
		return nil
	}
	es.closed = true
    //如果close body的时候,没有读取body 里面的数据,就会调用如下逻辑
//最后 会关闭内核层面socket
	if es.earlyCloseFn != nil && es.rerr != io.EOF {
		return es.earlyCloseFn()
	}
	err := es.body.Close()
	return es.condfn(err)
}

同时http的连接复用是基于transport的。

比如get/post 请求的client使用相同的transport,就会链接复用

如下代码调用"http://127.0.0.1:8090/xxx 会链接复用

但是调用http://127.0.0.1:8080/xxx 不会链接复用,每次get都会创建一个tcp 连接

复制代码
package main

import (
    "crypto/tls"
    "io"
    "log"
    "net/http"
    "time"
)

func call_baidu() {
    tr := &http.Transport{
        //TLSClientConfig:     &tls.Config{InsecureSkipVerify: true},
        MaxIdleConnsPerHost: 0,
        MaxConnsPerHost:     0,
        MaxIdleConns:        0,
    }
    netClient := &http.Client{
        Transport: tr,
        Timeout:   time.Second * 120,
    }

    urlStr := "http://127.0.0.1:8080/xxx"
    resp, err := netClient.Get(urlStr)
    if err != nil {
        log.Printf("http get url:%s failed, err:%s", urlStr, err)
        return
    }
    defer resp.Body.Close()

    // 可以根据需要在这里处理响应,例如读取响应体
    log.Printf("Successfully accessed %s, status code: %d", urlStr, resp.StatusCode)
    _, err = io.Copy(io.Discard, resp.Body)
}

func main() {

    ticker := time.NewTicker(1 * time.Second)
    defer ticker.Stop()
    for range ticker.C {
        call_baidu()
        call_baidu()
        call_baidu()
    }
}

var tr1 *http.Transport = &http.Transport{
    TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}

func call_baidu1() {

    var netClient = &http.Client{Transport: tr1, Timeout: time.Second * 120}
    urlStr := "http://127.0.0.1:8090/xxx"
    resp, err := netClient.Get(urlStr)
    if err != nil {
        log.Printf("http get url:%s failed, err:%s", urlStr, err)
        return
    }
    defer resp.Body.Close()

    // 可以根据需要在这里处理响应,例如读取响应体
    log.Printf("Successfully accessed %s, status code: %d", urlStr, resp.StatusCode)
    _, err = io.Copy(io.Discard, resp.Body)
}
复制代码

 

posted @   codestacklinuxer  阅读(136)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
历史上的今天:
2021-07-13 引擎限速-慢速攻击的思考
点击右上角即可分享
微信分享提示