Подтвердить что ты не робот

Сокет не может идентифицировать протокол (утечка сокета)

У меня есть процесс/приложение Go1.5.1. Когда я запускаю /usr/sbin/lsof -p в этом процессе, я вижу, что многие "не могут идентифицировать протокол".

monitor_ 13105 root  101u  sock      0,6      0t0 16960100 can't identify protocol
monitor_ 13105 root  102u  sock      0,6      0t0 21552427 can't identify protocol
monitor_ 13105 root  103u  sock      0,6      0t0 17565091 can't identify protocol
monitor_ 13105 root  104u  sock      0,6      0t0 18476870 can't identify protocol

статус proc/limit/fd

[[email protected]_q ~]# cat /proc/13105/status 
Name:   monitor_client
State:  S (sleeping)
Tgid:   13105
Pid:    13105
PPid:   13104
TracerPid:  0
Uid:    0   0   0   0
Gid:    0   0   0   0
Utrace: 0
FDSize: 16384
Groups: 
...


[[email protected]_q ~]# cat /proc/13105/limits 
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            10485760             unlimited            bytes     
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             3870                 3870                 processes 
Max open files            9999                 9999                 files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes     
Max file locks            unlimited            unlimited            locks     
Max pending signals       3870                 3870                 signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us

[[email protected]_q ~]# ll /proc/13105/fd/
lrwx------ 1 root root 64 Dec  7 00:15 8382 -> socket:[52023221]
lrwx------ 1 root root 64 Dec  7 00:15 8383 -> socket:[51186627]
lrwx------ 1 root root 64 Dec  7 00:15 8384 -> socket:[51864232]
lrwx------ 1 root root 64 Dec  7 00:15 8385 -> socket:[52435453]
lrwx------ 1 root root 64 Dec  7 00:15 8386 -> socket:[51596071]
lrwx------ 1 root root 64 Dec  7 00:15 8387 -> socket:[52767667]
lrwx------ 1 root root 64 Dec  7 00:15 8388 -> socket:[52090632]
lrwx------ 1 root root 64 Dec  7 00:15 8389 -> socket:[51739068]
lrwx------ 1 root root 64 Dec  7 00:15 839 -> socket:[22963529]
lrwx------ 1 root root 64 Dec  7 00:15 8390 -> socket:[52023223]
lrwx------ 1 root root 64 Dec  7 00:15 8391 -> socket:[52560389]
lrwx------ 1 root root 64 Dec  7 00:15 8392 -> socket:[52402565]
...

но аналогичного вывода в netstat -a нет.

Каковы эти сокеты и как я могу узнать, что они делают?

monitor_client.go

package main

import (
    "crypto/tls"
    "encoding/json"
    "fmt"
    "log"
    "net"
    "net/http"
    nurl "net/url"
    "strconv"
    "strings"
    "syscall"
    "time"
)

type Result struct {
    Error      string        `json:"error"`
    HttpStatus int           `json:"http_status"`
    Stime      time.Duration `json:"http_time"`
}

//http://stackoverflow.com/info/20990332/golang-http-timeout-and-goroutines-accumulation
//http://3.3.3.3/http?host=3.2.4.2&servername=a.test&path=/&port=33&timeout=5&scheme=http
func MonitorHttp(w http.ResponseWriter, r *http.Request) {
    var host, servername, path, port, scheme string
    var timeout int
    u, err := nurl.Parse(r.RequestURI)
    if err != nil {
        log.Fatal(err)
        return
    }
    if host = u.Query().Get("host"); host == "" {
        host = "127.0.0.0"
    }
    if servername = u.Query().Get("servername"); servername == "" {
        servername = "localhost"
    }
    if path = u.Query().Get("path"); path == "" {
        path = "/"
    }
    if port = u.Query().Get("port"); port == "" {
        port = "80"
    }
    if scheme = u.Query().Get("scheme"); scheme == "" {
        scheme = "http"
    }

    if timeout, _ = strconv.Atoi(u.Query().Get("timeout")); timeout == 0 {
        timeout = 5
    }

    //log.Printf("(host)=%s (servername)=%s (path)=%s (port)=%s (timeout)=%d", host, servername, path, port, timeout)

    w.Header().Set("Content-Type", "application/json")

    res := httptool(host, port, servername, scheme, path, timeout)
    result, _ := json.Marshal(res)
    fmt.Fprintf(w, "%s", result)
}

func httptool(ip, port, servername, scheme, path string, timeout int) Result {

    var result Result
    startTime := time.Now()
    host := ip + ":" + port

    transport := &http.Transport{
        TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
        DisableKeepAlives: true,
    }

    dialer := net.Dialer{
        Timeout:   time.Duration(timeout) * time.Second,
        KeepAlive: 0 * time.Second,
    }
    transport.Dial = func(network, address string) (net.Conn, error) {
        return dialer.Dial(network, address)
    }

    client := &http.Client{
        Transport: transport,
    }
    rawquery := ""
    url := fmt.Sprintf("%s://%s%s%s", scheme, host, path, rawquery)
    req, err := http.NewRequest("GET", url, nil)
    if err != nil {
        result.HttpStatus = -1
        errs := strings.Split(err.Error(), ": ")
        result.Error = errs[len(errs)-1]
        result.Stime = time.Now().Sub(startTime) / time.Millisecond
        return result
    }
    req.Header.Set("User-Agent", "monitor worker")
    req.Header.Set("Connection", "close")
    req.Host = servername
    resp, err := client.Do(req)
    //https://github.com/Basiclytics/neverdown/blob/master/check.go
    if err != nil {
        nerr, ok := err.(*nurl.Error)
        if ok {
            switch cerr := nerr.Err.(type) {
            case *net.OpError:
                switch cerr.Err.(type) {
                case *net.DNSError:
                    errs := strings.Split(cerr.Error(), ": ")
                    result.Error = "dns: " + errs[len(errs)-1]
                default:
                    errs := strings.Split(cerr.Error(), ": ")
                    result.Error = "server: " + errs[len(errs)-1]
                }
            default:
                switch nerr.Err.Error() {
                case "net/http: request canceled while waiting for connection":
                    errs := strings.Split(cerr.Error(), ": ")
                    result.Error = "timeout: " + errs[len(errs)-1]

                default:
                    errs := strings.Split(cerr.Error(), ": ")
                    result.Error = "unknown: " + errs[len(errs)-1]
                }
            }

        } else {
            result.Error = "unknown: " + err.Error()
        }
        result.HttpStatus = -2
        result.Stime = time.Now().Sub(startTime) / time.Millisecond
        return result
    }
    resp.Body.Close()
    result.HttpStatus = resp.StatusCode
    result.Error = "noerror"
    result.Stime = time.Now().Sub(startTime) / time.Millisecond //spend time (ms)
    return result
}

func setRlimit() {
    var rLimit syscall.Rlimit
    err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit)
    if err != nil {
        log.Printf("Unable to obtain rLimit", err)
    }
    if rLimit.Cur < rLimit.Max {
        rLimit.Max = 9999
        rLimit.Cur = 9999
        err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit)
        if err != nil {
            log.Printf("Unable to increase number of open files limit", err)
        }
    }
}

func main() {
    setRlimit()
    s := &http.Server{
        Addr:         ":59059",
        ReadTimeout:  7 * time.Second,
        WriteTimeout: 7 * time.Second,
    }
    http.HandleFunc("/http", MonitorHttp)

    log.Fatal(s.ListenAndServe())
}
4b9b3361

Ответ 1

Возможно, вы можете попробовать с wirehark или tcpdump.

Ответ 2

Здесь есть пара пунктов.

Я не смог воспроизвести ваше поведение, во всяком случае, can't identify protocol обычно привязывается к закрытым сокетам.

Некоторые комментаторы предложили вам не создавать http-клиент внутри каждого обработчика - это правда. Просто создайте его один раз и повторно используйте.

Во-вторых, я не уверен, почему вы создаете собственную структуру http.Client и почему вы отключите keepalives. Не можете ли вы просто пойти с http.Get? Упрощенный код легче отлаживать.

В-третьих, не знаете, почему вы переписываете функцию transport.Dial. Даже если вы это сделаете, в документации (для Go 1.9.2) говорится:

% go doc http.transport.dial
type Transport struct {
    // Dial specifies the dial function for creating unencrypted TCP
    connections.
    //
    // Deprecated: Use DialContext instead, which allows the transport
    // to cancel dials as soon as they are no longer needed.
    // If both are set, DialContext takes priority.
    Dial func(network, addr string) (net.Conn, error)

Этот комментарий об устаревании и отсутствии повторного использования набора номера может указывать на источник ваших проблем.

Подводя итог, когда на вашем месте, я бы сделал две вещи: * переместить создание клиента в код, который выполняется один раз, или просто использовать клиент по умолчанию с http.Get * Я бы очистил эту вещь, перезаписав по умолчанию транспортные поля, если вы это сделаете, я бы использовал DialContext, как было предложено.

Удачи.