2012-11-16 22 views
17

Próbowałem znaleźć sposób, aby zatrzymać serwer nasłuchujący w Go z wdziękiem. Ponieważ listen.Accept blokuje, konieczne jest zamknięcie gniazda nasłuchu, aby zasygnalizować koniec, ale nie mogę stwierdzić tego błędu z wyjątkiem innych błędów, ponieważ odpowiedni błąd nie jest eksportowany.Jak zatrzymać serwer nasłuchujący w Go

Czy mogę lepiej niż to? Zobacz FIXME w poniższym kodzie w serve()

package main 

import (
    "io" 
    "log" 
    "net" 
    "time" 
) 

// Echo server struct 
type EchoServer struct { 
    listen net.Listener 
    done chan bool 
} 

// Respond to incoming connection 
// 
// Write the address connected to then echo 
func (es *EchoServer) respond(remote *net.TCPConn) { 
    defer remote.Close() 
    _, err := io.Copy(remote, remote) 
    if err != nil { 
     log.Printf("Error: %s", err) 
    } 
} 

// Listen for incoming connections 
func (es *EchoServer) serve() { 
    for { 
     conn, err := es.listen.Accept() 
     // FIXME I'd like to detect "use of closed network connection" here 
     // FIXME but it isn't exported from net 
     if err != nil { 
      log.Printf("Accept failed: %v", err) 
      break 
     } 
     go es.respond(conn.(*net.TCPConn)) 
    } 
    es.done <- true 
} 

// Stop the server by closing the listening listen 
func (es *EchoServer) stop() { 
    es.listen.Close() 
    <-es.done 
} 

// Make a new echo server 
func NewEchoServer(address string) *EchoServer { 
    listen, err := net.Listen("tcp", address) 
    if err != nil { 
     log.Fatalf("Failed to open listening socket: %s", err) 
    } 
    es := &EchoServer{ 
     listen: listen, 
     done: make(chan bool), 
    } 
    go es.serve() 
    return es 
} 

// Main 
func main() { 
    log.Println("Starting echo server") 
    es := NewEchoServer("127.0.0.1:18081") 
    // Run the server for 1 second 
    time.Sleep(1 * time.Second) 
    // Close the server 
    log.Println("Stopping echo server") 
    es.stop() 
} 

Drukuje

2012/11/16 12:53:35 Starting echo server 
2012/11/16 12:53:36 Stopping echo server 
2012/11/16 12:53:36 Accept failed: accept tcp 127.0.0.1:18081: use of closed network connection 

Chciałbym, aby ukryć komunikat Accept failed, ale oczywiście nie chcę, aby zamaskować inne błędy Accept może zgłosić. Mógłbym oczywiście zajrzeć do testu błędu dla use of closed network connection, ale to byłoby naprawdę brzydkie. Mogłabym ustawić flagę mówiącą, że mam zamiar zamknąć i zignorować błędy, jeśli to było ustawione. Przypuszczam - Czy istnieje lepszy sposób?

Odpowiedz

4

Sprawdź niektóre „jest to czas, aby zatrzymać” flagę w pętli tuż po wywołaniu accept(), następnie odwrócić go od swojej main, następnie połączyć do portu słuchanie dostać gniazdo serwera „un-Stuck”. Jest to bardzo podobne do starego "self-pipe trick".

+0

To zgrabny pomysł! Istnieje warunek wyścigowy z rzeczywistymi połączeniami, które jednak trzeba obejść. –

+0

Tak, prawda. Możesz spróbować odwrotności tego - zawsze mieć to pojedyncze wewnętrzne połączenie od początku i używać go jako kanału kontrolnego. –

1

Coś wśród tych linii może działać w tym przypadku, mam nadzieję:

// Listen for incoming connections 
func (es *EchoServer) serve() { 
     for { 
       conn, err := es.listen.Accept() 
       if err != nil { 
        if x, ok := err.(*net.OpError); ok && x.Op == "accept" { // We're done 
          log.Print("Stoping") 
          break 
        } 

        log.Printf("Accept failed: %v", err) 
        continue 
       } 
       go es.respond(conn.(*net.TCPConn)) 
     } 
     es.done <- true 
} 
+0

Dobry pomysł, dziękuję! Nie jestem pewien, czy odróżniłoby to oficjalne zatrzymanie od 'Syscall.Accept()' zwracającego błąd (ostatni przykład, który widziałem, to proces, w którym kończą się gniazda), prawda? –

+0

Dunno, musiałbym spróbować/eksperymentować z tym. – zzzz

10

bym sobie z tym poradzić stosując es.done wysłać sygnał zanim zamyka połączenie. Oprócz poniższego kodu musisz utworzyć es.done z make (chan bool, 1), abyśmy mogli umieścić w nim jedną wartość bez blokowania.

+0

Hmm, fajny pomysł. Myślę, że 'bool' zrobiłby tak samo dobrze, jak użycie kanału, ale jest to rozwiązanie, które wymyśliłem. Nadal potrzebujesz kanału, aby zsynchronizować 'stop' z' serve', abyś wiedział, kiedy się zatrzyma. –

+4

Nie przejmuj się buforowaniem kanału lub wysyłaniem wiadomości do kanału. Zamknij to. – Dustin

-3

Oto prosty sposób, który wystarczy do rozwoju lokalnego.

http://www.sergiotapia.me/how-to-stop-your-go-http-server/


package main 

import ( 
    "net/http" 
    "os" 

    "github.com/bmizerany/pat" 
) 

var mux = pat.New() 

func main() { 
    mux.Get("/kill", http.HandlerFunc(kill)) 
    http.Handle("/", mux) 
    http.ListenAndServe(":8080", nil) 
} 

func kill(w http.ResponseWriter, r *http.Request) { 
    os.Exit(0) 
} 
+1

Zamknięcie całego procesu nie jest prawidłowym sposobem zamknięcia serwera! – Setomidor