With the help of forum members I created this simple “echo” server with support of graceful stop: when stop requested, already opened connections correctly processed before exit.
I’m wondering if this implementation is good enough or there are some potential issues? Here is the code
package main
import (
"fmt"
"net"
"os"
"sync"
"time"
)
type Server struct {
listener *net.TCPListener
quit chan bool
waitGroup *sync.WaitGroup
}
func NewService() *Server {
addr, err := net.ResolveTCPAddr("tcp4", ":9999")
if err != nil {
fmt.Println("Failed to resolve address", err.Error())
os.Exit(1)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
fmt.Println("Failed to create listener", err.Error())
os.Exit(1)
}
srv := &Server{
listener: listener,
quit: make(chan bool),
waitGroup: &sync.WaitGroup{},
}
go srv.serve()
return srv
}
func (srv *Server) serve() {
for {
select {
case <-srv.quit:
fmt.Println("Stop listening for new clients")
srv.listener.Close()
return
default:
}
srv.listener.SetDeadline(time.Now().Add(1e9))
conn, err := srv.listener.AcceptTCP()
if err != nil {
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
continue
}
fmt.Println("Failed to accept connection:", err.Error())
}
srv.waitGroup.Add(1)
go srv.handleConnection(conn)
}
}
func (srv *Server) handleConnection(conn net.Conn) error {
fmt.Println("Accepted connection from", conn.RemoteAddr())
defer func() {
fmt.Println("Closing connection from", conn.RemoteAddr())
conn.Close()
srv.waitGroup.Done()
}()
buf := make([]byte, 1024)
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Read error", err.Error())
conn.Close()
}
_, err = conn.Write(buf)
if err != nil {
fmt.Println("Write error", err.Error())
conn.Close()
}
return nil
}
func (srv *Server) Stop() {
fmt.Println("Stop requested. Waiting for existing connections...")
close(srv.quit)
srv.waitGroup.Wait()
fmt.Println("Stopped successfully")
}
func main() {
srv := NewService()
time.Sleep(100 * time.Second)
srv.Stop()
}
Also I tried to to implement slightly different approach using workers reading from channel. As I understrand this is more idiomatic way of handling multiple connections. Is this a correct implementation too?
package main
import (
"fmt"
"net"
"os"
"sync"
"time"
)
var conns chan net.Conn
type Server struct {
listener *net.TCPListener
quit chan bool
waitGroup *sync.WaitGroup
}
func NewService() *Server {
addr, err := net.ResolveTCPAddr("tcp4", ":9999")
if err != nil {
fmt.Println("Failed to resolve address", err.Error())
os.Exit(1)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
fmt.Println("Failed to create listener", err.Error())
os.Exit(1)
}
srv := &Server{
listener: listener,
quit: make(chan bool),
waitGroup: &sync.WaitGroup{},
}
conns = make(chan net.Conn, 50)
for i := 0; i < 5; i ++ {
fmt.Println("Start worker", i)
srv.waitGroup.Add(1)
go handleConnection(srv.waitGroup)
}
go srv.serve()
return srv
}
func (srv *Server) serve() {
for {
select {
case <-srv.quit:
fmt.Println("Stop listening for new clients")
srv.listener.Close()
close(conns)
return
default:
}
srv.listener.SetDeadline(time.Now().Add(1e9))
conn, err := srv.listener.AcceptTCP()
if err != nil {
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
continue
}
fmt.Println("Failed to accept connection:", err.Error())
}
conns <- conn
}
}
func handleConnection(wg *sync.WaitGroup) {
defer wg.Done()
buf := make([]byte, 1024)
for conn := range conns {
fmt.Println("Accepted connection from", conn.RemoteAddr())
_, err := conn.Read(buf)
if err != nil {
fmt.Println("Read error", err.Error())
conn.Close()
}
_, err = conn.Write(buf)
if err != nil {
fmt.Println("Write error", err.Error())
conn.Close()
}
fmt.Println("Closing connection from", conn.RemoteAddr())
conn.Close()
}
}
func (srv *Server) Stop() {
fmt.Println("Stop requested. Waiting for existing connections...")
close(srv.quit)
srv.waitGroup.Wait()
fmt.Println("Stopped successfully")
}
func main() {
srv := NewService()
time.Sleep(100 * time.Second)
srv.Stop()
}