TCP Server
Accept TCP connections, handle clients concurrently, and set deadlines to bound stalled reads.
net.Listen creates a TCP listener, and Accept returns a connection for each client. A connection is a byte stream, so a protocol must define how messages are separated.
This line-based echo server handles each connection in its own goroutine and sets a deadline so a stalled client cannot wait forever.
package main
import (
"bufio"
"fmt"
"net"
"os"
"time"
)
func main() {
listener, err := net.Listen("tcp", "127.0.0.1:9000")
if err != nil {
panic(err)
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
go func() {
defer conn.Close()
if err := conn.SetDeadline(time.Now().Add(30 * time.Second)); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
conn.SetDeadline(time.Now().Add(30 * time.Second))
if _, err := fmt.Fprintln(conn, scanner.Text()); err != nil {
fmt.Fprintln(os.Stderr, err)
return
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, err)
}
}()
}
}In production
Limit concurrent connections when clients are untrusted, and check read and write errors in real handlers. A deadline is an absolute time; refresh it for long-lived active connections. Scanner's default token buffer is 64 KiB, including any line delimiter, so choose another framing approach for larger messages.
Enjoyed this? Get more software craft delivered to your inbox.