How to Efficiently Manage TCP Connection Deadlines with the Ping Pong Method in Go

package main
import (
"context"
"errors"
"fmt"
"io"
"log"
"net"
"time"
)
// creating a default interval
const defInterval = 1 * time.Second
// pinger periodically writes "ping" to the Writer.
// The interval can be changed by sending a new duration into the reset channel.
func pinger(ctx context.Context, w io.Writer, reset chan time.Duration) {
var interval time.Duration
// Try to get initial interval from reset channel
select {
case <-ctx.Done():
return
case interval = <-reset:
default:
}
if interval == 0 {
interval = defInterval
}
timer := time.NewTimer(interval)
defer func() {
if !timer.Stop() {
<-timer.C
}
}()
for {
select {
case <-ctx.Done():
return
case newInterval := <-reset:
if !timer.Stop() {
<-timer.C
}
if newInterval >= 0 {
if newInterval == 0 {
interval = defInterval
} else {
interval = newInterval
}
}
_ = timer.Reset(interval)
case <-timer.C:
if _, err := w.Write([]byte("ping\n")); err != nil {
return
}
_ = timer.Reset(interval)
}
}
}
func main() {
done := make(chan struct{})
listener, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal("unable to listen on port ", listener.Addr().String())
}
fmt.Printf("[server]: started listening on (%s)\n", listener.Addr().String())
defer listener.Close()
begin := time.Now()
go func() {
defer close(done)
resetTimer := make(chan time.Duration, 1)
resetTimer <- time.Second
for {
conn, err := listener.Accept()
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Printf("[server]: failed to connect with client\n")
}
return
}
defer conn.Close()
fmt.Printf("[server]: connected to client\n")
err = conn.SetDeadline(time.Now().Add(15 * time.Second))
if err != nil {
fmt.Printf("[server]: unable to set deadline")
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go pinger(ctx, conn, resetTimer)
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
fmt.Printf("[server]: failed to read\n")
return
}
fmt.Printf("[server]: received %q\n", buffer[:n])
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
fmt.Println("[server] SetDeadline error:", err)
return
}
}
}
}()
conn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Printf("[client]: connection failed\n")
}
return
}
defer conn.Close()
buffer := make([]byte, 1024)
for i := 0; i < 4; i++ {
n, err := conn.Read(buffer)
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Printf("[client]: unable to read\n")
}
return
}
fmt.Printf("[client]: received %q\n", buffer[:n])
}
_, err = conn.Write([]byte("Pong!"))
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Printf("[client]: unable to write\n")
}
return
}
for i := 0; i < 4; i++ {
n, err := conn.Read(buffer)
if err != nil {
if errors.Is(err, io.EOF) {
fmt.Printf("[client]: unable to read\n")
}
return
}
fmt.Printf("[client]: received %q\n", buffer[:n])
}
<-done
fmt.Printf("[main %s] done\n", time.Since(begin).Truncate(time.Second))
}
1. Default setup
defInterval = 1s
→ Default ping interval if none is set.Purpose: Server sends
"ping\n"
messages to the client periodically.
2. pinger
function
Takes a context, an io.Writer (connection), and a reset channel for changing the interval.
Starts with:
Try to get initial interval from
reset
channel.If none, use
defInterval
.
Uses a
time.Timer
to send"ping\n"
every interval.Can reset interval if a new value is sent via
reset
channel.Stops if
ctx.Done()
is signaled.
3. Main server setup
Creates a TCP listener on
127.0.0.1:8080
.Starts goroutine for server logic.
4. Server goroutine logic
Makes
resetTimer
channel and sets it initially to1 second
.Loops over
listener.Accept()
to accept new clients.For each client:
Sets deadline (timeout) for 15 seconds initially.
Starts a
pinger
goroutine to send"ping"
periodically.Reads incoming data in a loop:
Prints received data.
Resets read deadline to 5 seconds after each message.
5. Client logic
Connects to the server.
Reads 4
"ping"
messages from the server.Sends
"Pong!"
back to the server.Reads another 4
"ping"
messages.Ends when server closes.
6. Key behaviors
Server: Sends
"ping"
every second until the connection closes or context is cancelled.Client: Reads a few
"ping"
messages, sends"Pong!"
, and reads more pings.Deadlines: Prevent hangs if one side stops responding.
Context cancel: Stops
pinger
goroutine when connection closes.
Subscribe to my newsletter
Read articles from Priyansh Sao directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
