Go

How do you build a TCP server with net.Listen? Handle per-connection goroutines, read/write deadlines, and graceful shutdown of the listener.

Question 541HardGo 1.22 to 1.25

The pattern:

  1. Call Accept in a loop.
  2. Start one goroutine per connection.
  3. Set deadlines on every read and write so that slow or idle clients cannot pin goroutines forever.
  4. To shut down, close the listener. This unblocks Accept with net.ErrClosed. Then wait for the handlers with a WaitGroup.
func serve(ctx context.Context, addr string) error {
	ln, err := net.Listen("tcp", addr)
	if err != nil {
		return err
	}
	stop := context.AfterFunc(ctx, func() { ln.Close() }) // Go 1.21
	defer stop()

	var wg sync.WaitGroup
	for {
		conn, err := ln.Accept()
		if err != nil {
			if errors.Is(err, net.ErrClosed) {
				break
			}
			log.Printf("accept: %v", err)
			time.Sleep(50 * time.Millisecond) // back off on EMFILE etc.
			continue
		}
		wg.Add(1)
		go func() { // conn is per-iteration in Go 1.22+
			defer wg.Done()
			handle(ctx, conn)
		}()
	}
	wg.Wait() // Go 1.25 alternative: wg.Go(func() { handle(ctx, conn) })
	return nil
}

func handle(ctx context.Context, c net.Conn) {
	defer c.Close()
	// Wake a Read blocked on an idle client when shutdown starts.
	stop := context.AfterFunc(ctx, func() { c.SetDeadline(time.Now()) })
	defer stop()

	sc := bufio.NewScanner(c)
	for ctx.Err() == nil {
		c.SetReadDeadline(time.Now().Add(30 * time.Second))
		if !sc.Scan() {
			return // EOF, timeout or error
		}
		c.SetWriteDeadline(time.Now().Add(5 * time.Second))
		if _, err := fmt.Fprintf(c, "echo: %s\n", sc.Text()); err != nil {
			return
		}
	}
}

Gotchas: a deadline is an absolute time, not a timeout, so reset it for every operation. A timeout shows up as an error where errors.Is(err, os.ErrDeadlineExceeded) is true. Also, TCP is a byte stream, so you must frame messages yourself (lines, length prefixes) because one Read is not one message. Limit the number of concurrent connections, for example with a semaphore channel.

More on More Standard Library Essentials

All 16 More Standard Library Essentials questions