我有以下http客户端/服务器代码:
服务器
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("Req: ", r.URL)
w.Write([]byte("OK")) // <== PROBLEMATIC LINE
// w.WriteHeader(200) // Works as expected
})
log.Fatal(http.ListenAndServe(":5008", nil))
}
客户端
func main() {
client := &http.Client{}
for i := 0; i < 500; i++ {
url := fmt.Sprintf("http://localhost:5008/%02d", i)
req, _ := http.NewRequest("GET", url, nil)
_, err := client.Do(req)
if err != nil {
fmt.Println("error: ", err)
} else {
fmt.Println("success: ", i)
}
time.Sleep(10 * time.Millisecond)
}
}
当我在服务器上面运行客户端时,在250个连接之后,我从client.Do得到以下错误:
error: Get http://localhost:5008/250: dial tcp: lookup localhost: no such host
并且没有更多的连接会成功。
如果我从w.Write([]byte("OK"))
==&gt;更改服务器中的行w.WriteHeader(200)
然而,连接数量没有限制,它可以按预期工作。
我在这里缺少什么?
答案 0 :(得分:6)
你没有关闭身体。当您从服务器进行任何写入时,连接将保持打开状态,因为尚未读取响应。当你只是WriteHeader时,响应完成,连接可以重用或关闭。
说实话,我不知道为什么离开开放连接会导致域查找失败。基于250非常接近第256轮的事实,我猜想你正在击中的操作系统存在人为限制。也许允许的最大FD是256?看起来很低,但它可以解释这个问题。
func main() {
client := &http.Client{}
for i := 0; i < 500; i++ {
url := fmt.Sprintf("http://localhost:5008/%02d", i)
req, _ := http.NewRequest("GET", url, nil)
resp, err := client.Do(req)
if err != nil {
fmt.Println("error: ", err)
} else {
fmt.Println("success: ", i)
resp.Body.Close()
}
time.Sleep(10 * time.Millisecond)
}
}
答案 1 :(得分:6)
应用程序必须按照net/http package docmentation开头所述关闭客户端上的响应正文。
func main() {
client := &http.Client{}
for i := 0; i < 500; i++ {
url := fmt.Sprintf("http://localhost:5008/%02d", i)
req, _ := http.NewRequest("GET", url, nil)
resp, err := client.Do(req)
if err != nil {
fmt.Println("error: ", err)
} else {
resp.Body.Close() // <---- close is required
fmt.Println("success: ", i)
}
time.Sleep(10 * time.Millisecond)
}
}
如果应用程序未关闭响应正文,则可能无法关闭底层网络连接或将其返回到客户端的连接池。在这种情况下,每个新请求都会创建一个新的网络连接。该进程最终命中file descriptor limit,任何需要文件描述符的内容都将失败。这包括名称查找和打开新连接。
OS X上打开文件描述符数量的默认限制为256.我希望客户端应用程序在此限制之内失败。
因为服务器的每个连接都使用服务器上的文件描述符,所以服务器也可能已达到其文件描述符限制。
从服务器代码中删除w.Write([]byte("OK"))
时,响应正文的长度为零。这将触发客户端中零长度响应主体的优化,其中连接在应用程序关闭响应主体之前关闭或返回到池中。
答案 2 :(得分:1)
同时使用MAC OSX时也会遇到同样的问题:
250次请求后会出现该错误;
使用go1.8.3。
我的问题的解决方法是关闭req和res body:
for i := 0; i < 10; i++ {
res, err := client.Do(req)
if err == nil {
globalCounter.Add(1)
res.Body.Close()
req.Body.Close()
break
} else {
log.Println("Error:", err, "retrying...", i)
}
}
答案 3 :(得分:0)
我正在使用go版本go1.9.4 linux / amd64 我尝试了不同的方法来解决此问题。什么都没有帮助 http://craigwickesser.com/2015/01/golang-http-to-many-open-files/
与resp.Body.Close()一起,我不得不添加req.Header.Set(“ Connection”,“ close”)
func PrettyPrint(v interface{}) (err error) {
b, err := json.MarshalIndent(v, "", " ")
if err == nil {
fmt.Println(string(b))
}
return
}
func Json(body []byte, v interface{}) error {
return json.Unmarshal(body, v)
}
func GetRequests(hostname string, path string) []map[string]interface{} {
transport := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
Dial: (&net.Dialer{
Timeout: 0,
KeepAlive: 0,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
}
httpClient := &http.Client{Transport: transport}
req, reqErr := http.NewRequest("GET", "https://"+hostname+path, nil)
req.SetBasicAuth("user", "pwd")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Connection", "close")
if reqErr != nil {
fmt.Println("error in making request", reqErr)
}
resp, err := httpClient.Do(req)
if err != nil {
fmt.Println("error in calling request", err)
}
defer resp.Body.Close()
content, err := ioutil.ReadAll(resp.Body)
// fmt.Println(string(content))
var json_resp_l []map[string]interface{}
Json(content, &json_resp_l)
if len(json_resp_l) == 0 {
var json_resp map[string]interface{}
Json(content, &json_resp)
if json_resp != nil {
json_resp_l = append(json_resp_l, json_resp)
}
}
PrettyPrint(json_resp_l)
return json_resp_l
}
func main() {
GetRequests("server.com", "/path")
}
答案 4 :(得分:0)
我认为关键原因是您应该为每个http.Transport
使用共享http.Client
,http.Transport
将池化连接并重用它们以提高性能。
答案 5 :(得分:0)
我遇到相同的no such host
错误,尽管这不是对本地主机的请求。它发生在大约250个请求之后。当然,我当然也写了defer resp.Body.Close()
,但是错误不断发生。
我终于找到了一种在HTTP / 1.1请求中指定req.Header.Set("Connection", "close")
的方法。这意味着连接在HTTP / 1.1中是短暂的。
通常,连接后最好保持活动状态,因此不建议使用Connection: close
。但是,它可能是一个有效的选项,例如,如果您要发出许多负载验证请求。