2015-07-20 2 views
1

Я пытаюсь создать клиентскую программу http в go, которая сделает много HTTP-запросов GET. Я использую буферный канал для ограничения количества одновременных запросов.слишком много открытых файлов при создании http-запросов

Когда я запускаю программу, я получаю

Get http://198.18.96.213/: dial tcp 198.18.96.213:80: too many open files

Вот моя программа:

package main 

import (
    "fmt" 
    "net/http" 
    "time" 
) 

func HttpGets(numRequests int, numConcurrent int, url string) map[int]int { 
    // I want number of requests with each status code 
    responseStatuses := map[int]int { 
     100: 0, 101 : 0, 102 : 0, 200 : 0, 201 : 0, 202 : 0, 203 : 0, 204 : 0, 205 : 0, 
     206 : 0, 207 : 0, 208 : 0, 226 : 0, 300 : 0, 301 : 0, 302 : 0, 303 : 0, 304 : 0, 
     305 : 0, 306 : 0, 307 : 0, 308 : 0, 400 : 0, 401 : 0, 402 : 0, 403 : 0, 404 : 0, 
     405 : 0, 406 : 0, 407 : 0, 408 : 0, 409 : 0, 410 : 0, 411 : 0, 412 : 0, 413 : 0, 
     414 : 0, 415 : 0, 416 : 0, 417 : 0, 421 : 0, 422 : 0, 423 : 0, 424 : 0, 425 : 0, 
     426 : 0, 427 : 0, 428 : 0, 429 : 0, 430 : 0, 431 : 0, 500 : 0, 501 : 0, 502 : 0, 
     503 : 0, 504 : 0, 505 : 0, 506 : 0, 507 : 0, 508 : 0, 509 : 0, 510 : 0, 511 : 0, 
    } 

    reqsDone := 0 
    ch := make(chan *http.Response, numConcurrent) 
    for i := 0; i < numRequests; i++ { 
     go func(url string) { 
      client := &http.Client{} 
      req, reqErr := http.NewRequest("GET", url, nil) 
      if reqErr != nil { 
       fmt.Println(reqErr) 
      } 
      // adding connection:close header hoping to get rid 
      // of too many files open error. Found this in http://craigwickesser.com/2015/01/golang-http-to-many-open-files/   
      req.Header.Add("Connection", "close") 

      resp, err := client.Do(req) 
      if (err !=nil) { 
       fmt.Println(err) 
      } 
      ch <- resp 

     }(url) 
    } 

    for { 
     select { 
     case r := <-ch: 
      reqsDone += 1 // probably needs a lock? 
      status := r.StatusCode   
      if _, ok := responseStatuses[status]; ok { 
       responseStatuses[status] += 1   

      } else { 
       responseStatuses[status] = 1 
      } 
      r.Body.Close() // trying to close body hoping to get rid of too many files open error 
      if (reqsDone == numRequests) { 
       return responseStatuses 
      }  
     } 
    } 
    return responseStatuses 
} 

func main() { 
    var numRequests, numConcurrent = 500, 10 
    url := "http://198.18.96.213/" 
    beginTime := time.Now() 
    results := HttpGets(numRequests, numConcurrent, url) 
    endTime := time.Since(beginTime) 
    fmt.Printf("Total time elapsed: %s\n", endTime) 
    for k,v := range results { 
     if v!=0 { 
      fmt.Printf("%d : %d\n", k, v) 
     }  
    } 

} 

Как я могу гарантировать файлы/socets закрыты, так что я не получаю эту ошибку при выполнении нескольких запросов?

+0

@Бравада Задада, как мне сделать 'numRequests' с только 'numConcurrent' является одновременным, пока остальные ждут? Я думал, что это может быть достигнуто путем ограничения размера канала, чтобы при заполнении канала он блокировался до тех пор, пока он не станет пустым. – Bharat

+1

@Bharat проблема - ваш 'go func (строка url) {}' будет продолжать открывать соединения и блокировать при отправке resp, поэтому у вас будет несколько сотен открытых подключений за одно и то же время, пока ваш читатель не начнет их закрывать. – OneOfOne

+0

@OneOfOne, то как мне реализовать goroutine для запросов? Я новичок в том, чтобы идти и все еще не очень ясно о том, как работает одновременно. – Bharat

ответ

1

В основном вы были нерестилищем 100-летних горчуков, которые начнут соединение блока, пока не будут закрыты.

Вот быстрый (и очень некрасиво) рабочий код:

var (
    responseStatuses = make(map[int]int, 63) 
    reqsDone   = 0 

    urlCh = make(chan string, numConcurrent) 
    ch = make(chan *http.Response, numConcurrent) 
) 
log.Println(numConcurrent, numRequests, len(responseStatuses)) 
for i := 0; i < numConcurrent; i++ { 
    go func() { 
     for url := range urlCh { 
      client := &http.Client{} 
      req, reqErr := http.NewRequest("GET", url, nil) 
      if reqErr != nil { 
       fmt.Println(reqErr) 
      } 
      // adding connection:close header hoping to get rid 
      // of too many files open error. Found this in http://craigwickesser.com/2015/01/golang-http-to-many-open-files/ 
      req.Header.Add("Connection", "close") 

      resp, err := client.Do(req) 
      if err != nil { 
       fmt.Println(err) 
      } 
      ch <- resp 
     } 

    }() 
} 
go func() { 
    for i := 0; i < numRequests; i++ { 
     urlCh <- url 
    } 
    close(urlCh) 
}() 

playground

+0

Спасибо! Во 2-й goroutine вы пишете url в 'urlCh', а в 1-й goroutine вы читаете эти URL-адреса из' urlCh'. Размер 'urlCh' -' numConcurrent', который равен 10, но цикл for во второй итерации горутинга до 500. Таким образом, после каждых 10 URL-адресов, этот goroutine ничего не сделает, пока они не будут потреблены 1-го goroutine. И первый горутин будет работать до тех пор, пока этот канал не будет закрыт. Это правильно? – Bharat

+0

@Bharat 2-го goroutine будет блокироваться, пока не будет места для того, чтобы нажать другой url. – OneOfOne

+0

Спасибо @OneOfOne. – Bharat

Смежные вопросы