Network Programming: TCP, UDP, and Unix Sockets in Go
Introduction
Network programming is fundamental to distributed systems. This guide covers TCP, UDP, and Unix socket programming in Go.
Go’s net package provides powerful abstractions for network communication while maintaining performance.
TCP Programming
TCP Server
package main
import (
"bufio"
"fmt"
"log"
"net"
)
// TCPServer implements a TCP server
type TCPServer struct {
listener net.Listener
addr string
}
// NewTCPServer creates a new TCP server
func NewTCPServer(addr string) *TCPServer {
return &TCPServer{addr: addr}
}
// Start starts the server
func (ts *TCPServer) Start() error {
listener, err := net.Listen("tcp", ts.addr)
if err != nil {
return fmt.Errorf("listen failed: %w", err)
}
ts.listener = listener
log.Printf("Server listening on %s", ts.addr)
for {
conn, err := listener.Accept()
if err != nil {
return fmt.Errorf("accept failed: %w", err)
}
go ts.handleConnection(conn)
}
}
// handleConnection handles a client connection
func (ts *TCPServer) handleConnection(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
line, err := reader.ReadString('\n')
if err != nil {
return
}
response := fmt.Sprintf("Echo: %s", line)
conn.Write([]byte(response))
}
}
// Stop stops the server
func (ts *TCPServer) Stop() error {
return ts.listener.Close()
}
TCP Client
package main
import (
"bufio"
"fmt"
"net"
)
// TCPClient implements a TCP client
type TCPClient struct {
conn net.Conn
}
// NewTCPClient creates a new TCP client
func NewTCPClient(addr string) (*TCPClient, error) {
conn, err := net.Dial("tcp", addr)
if err != nil {
return nil, fmt.Errorf("dial failed: %w", err)
}
return &TCPClient{conn: conn}, nil
}
// Send sends data to server
func (tc *TCPClient) Send(data string) (string, error) {
_, err := tc.conn.Write([]byte(data + "\n"))
if err != nil {
return "", err
}
reader := bufio.NewReader(tc.conn)
response, err := reader.ReadString('\n')
if err != nil {
return "", err
}
return response, nil
}
// Close closes the connection
func (tc *TCPClient) Close() error {
return tc.conn.Close()
}
UDP Programming
UDP Server
package main
import (
"fmt"
"net"
)
// UDPServer implements a UDP server
type UDPServer struct {
conn *net.UDPConn
addr string
}
// NewUDPServer creates a new UDP server
func NewUDPServer(addr string) *UDPServer {
return &UDPServer{addr: addr}
}
// Start starts the server
func (us *UDPServer) Start() error {
udpAddr, err := net.ResolveUDPAddr("udp", us.addr)
if err != nil {
return fmt.Errorf("resolve failed: %w", err)
}
conn, err := net.ListenUDP("udp", udpAddr)
if err != nil {
return fmt.Errorf("listen failed: %w", err)
}
us.conn = conn
defer conn.Close()
buffer := make([]byte, 1024)
for {
n, remoteAddr, err := conn.ReadFromUDP(buffer)
if err != nil {
return fmt.Errorf("read failed: %w", err)
}
response := fmt.Sprintf("Echo: %s", string(buffer[:n]))
conn.WriteToUDP([]byte(response), remoteAddr)
}
}
// Stop stops the server
func (us *UDPServer) Stop() error {
return us.conn.Close()
}
UDP Client
package main
import (
"fmt"
"net"
)
// UDPClient implements a UDP client
type UDPClient struct {
conn *net.UDPConn
}
// NewUDPClient creates a new UDP client
func NewUDPClient(addr string) (*UDPClient, error) {
udpAddr, err := net.ResolveUDPAddr("udp", addr)
if err != nil {
return nil, err
}
conn, err := net.DialUDP("udp", nil, udpAddr)
if err != nil {
return nil, err
}
return &UDPClient{conn: conn}, nil
}
// Send sends data to server
func (uc *UDPClient) Send(data string) (string, error) {
_, err := uc.conn.Write([]byte(data))
if err != nil {
return "", err
}
buffer := make([]byte, 1024)
n, err := uc.conn.Read(buffer)
if err != nil {
return "", err
}
return string(buffer[:n]), nil
}
// Close closes the connection
func (uc *UDPClient) Close() error {
return uc.conn.Close()
}
Unix Sockets
Unix Socket Server
package main
import (
"fmt"
"net"
"os"
)
// UnixSocketServer implements a Unix socket server
type UnixSocketServer struct {
listener net.Listener
path string
}
// NewUnixSocketServer creates a new Unix socket server
func NewUnixSocketServer(path string) *UnixSocketServer {
return &UnixSocketServer{path: path}
}
// Start starts the server
func (uss *UnixSocketServer) Start() error {
// Remove existing socket file
os.Remove(uss.path)
listener, err := net.Listen("unix", uss.path)
if err != nil {
return fmt.Errorf("listen failed: %w", err)
}
uss.listener = listener
for {
conn, err := listener.Accept()
if err != nil {
return fmt.Errorf("accept failed: %w", err)
}
go uss.handleConnection(conn)
}
}
// handleConnection handles a client connection
func (uss *UnixSocketServer) handleConnection(conn net.Conn) {
defer conn.Close()
buffer := make([]byte, 1024)
for {
n, err := conn.Read(buffer)
if err != nil {
return
}
response := fmt.Sprintf("Echo: %s", string(buffer[:n]))
conn.Write([]byte(response))
}
}
// Stop stops the server
func (uss *UnixSocketServer) Stop() error {
os.Remove(uss.path)
return uss.listener.Close()
}
Best Practices
1. Set Timeouts
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
conn.SetWriteDeadline(time.Now().Add(30 * time.Second))
2. Handle Errors
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
3. Close Resources
defer conn.Close()
4. Use Buffering
reader := bufio.NewReader(conn)
Common Pitfalls
1. No Timeout
Always set timeouts.
2. Resource Leaks
Always close connections.
3. No Error Handling
Handle all errors.
4. Blocking Operations
Use goroutines for concurrent connections.
Resources
Summary
Network programming is essential. Key takeaways:
- Use TCP for reliable communication
- Use UDP for low-latency communication
- Use Unix sockets for local IPC
- Set appropriate timeouts
- Handle errors properly
- Close resources
- Use goroutines for concurrency
By mastering network programming, you can build distributed systems.
Comments