Please purchase the course to watch this video.

Full Course
The ability to interact with network resources is crucial for building robust CLI applications, and the Go standard library offers effective tools for this purpose through its net
package. This package facilitates the creation of TCP servers, enabling developers to establish and manage connections seamlessly. By utilizing functions like net.Listen
and net.Dial
, one can create a simple echo server that accepts client connections and responds by echoing back any received data. The lesson highlights essential practices like error handling, utilizing goroutines for concurrent connection management, and leveraging the io
and bufio
packages for efficient data transfer and logging. It also touches on more advanced functionalities available for fine-tuning connections, such as ListenTCP
for tailored server settings, underscoring the flexibility and power of Go's networking capabilities.
No links available for this lesson.
Throughout this course, we've been working with data from the file system and user input. In addition to these, another common source of data for CLI applications is the network. Go's standard library provides several packages for network interaction, with the net
package offering basic primitives for networking in Go.
Introduction to Go's Net Package
The net
package provides a wide range of networking functionality, including:
- TCP and UDP connections
- IP address handling
- DNS resolution
- Unix domain sockets
In this lesson, we'll focus on creating a simple TCP echo server using this package.
Building a TCP Echo Server
Let's start by creating a basic TCP server that listens for connections:
package main
import (
"fmt"
"log"
"net"
)
func main() {
// Listen for incoming connections on port 8080
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("Failed to listen on TCP 8080:", err)
}
fmt.Println("Server listening on port 8080")
// Accept connections in an infinite loop
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println("Failed to accept connection:", err)
continue
}
// Close the connection when we're done with it
defer conn.Close()
// Print client connection information
fmt.Println("Client connected from:", conn.RemoteAddr().String())
}
}
When we run this code, the server will listen on port 8080 and print a message whenever a client connects. You can test this with the telnet
command:
$ telnet localhost 8080
Adding Echo Functionality
Now, let's add the actual "echo" functionality to our server. We'll use a goroutine to handle each connection concurrently:
package main
import (
"fmt"
"io"
"log"
"net"
"os"
)
func main() {
// Listen for incoming connections on port 8080
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("Failed to listen on TCP 8080:", err)
}
fmt.Println("Server listening on port 8080")
// Accept connections in an infinite loop
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println("Failed to accept connection:", err)
continue
}
fmt.Println("Client connected from:", conn.RemoteAddr().String())
// Handle each connection in a separate goroutine
go func(c net.Conn) {
defer c.Close()
// Copy data from the connection back to the connection
io.Copy(c, c)
}(conn)
}
}
The net.Conn
type implements both io.Reader
and io.Writer
interfaces, which makes it perfect for use with io.Copy
. With this simple setup, any data sent by the client will be echoed back.
Enhancing the Echo Server
Let's enhance our server to log messages and add an "echo" prefix to responses:
package main
import (
"bufio"
"fmt"
"log"
"net"
"os"
)
func main() {
// Listen for incoming connections on port 8080
ln, err := net.Listen("tcp", ":8080")
if err != nil {
log.Fatal("Failed to listen on TCP 8080:", err)
}
fmt.Println("Server listening on port 8080")
// Accept connections in an infinite loop
for {
conn, err := ln.Accept()
if err != nil {
fmt.Println("Failed to accept connection:", err)
continue
}
fmt.Println("Client connected from:", conn.RemoteAddr().String())
// Handle each connection in a separate goroutine
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
// Create a scanner to read from the connection
scanner := bufio.NewScanner(conn)
// Process each line
for scanner.Scan() {
text := scanner.Text()
// Log the received message
fmt.Println("Received:", text)
// Echo back with prefix
fmt.Fprintf(conn, "echo: %s\n", text)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading from connection:", err)
}
}
This version:
- Uses a separate function to handle connections
- Employs a
bufio.Scanner
to read input line by line - Logs received messages to the server console
- Adds the "echo:" prefix to responses
Using Specialized TCP Functions
The net
package provides specialized functions for different protocols. Let's modify our server to use the TCP-specific functions:
package main
import (
"bufio"
"fmt"
"log"
"net"
"time"
)
func main() {
// Create a TCP address to listen on
addr := &net.TCPAddr{
IP: net.ParseIP("127.0.0.1"),
Port: 8080,
}
// Listen for TCP connections specifically
ln, err := net.ListenTCP("tcp", addr)
if err != nil {
log.Fatal("Failed to listen on TCP 8080:", err)
}
fmt.Println("Server listening on port 8080")
// Accept connections in an infinite loop
for {
// Accept TCP connections specifically
conn, err := ln.AcceptTCP()
if err != nil {
fmt.Println("Failed to accept connection:", err)
continue
}
fmt.Println("Client connected from:", conn.RemoteAddr().String())
// Configure TCP connection
conn.SetKeepAlive(true)
conn.SetKeepAlivePeriod(30 * time.Second)
// Handle each connection in a separate goroutine
go handleTCPConnection(conn)
}
}
func handleTCPConnection(conn *net.TCPConn) {
defer conn.Close()
// Set a deadline for reading/writing
conn.SetDeadline(time.Now().Add(5 * time.Minute))
// Create a scanner to read from the connection
scanner := bufio.NewScanner(conn)
// Process each line
for scanner.Scan() {
text := scanner.Text()
// Log the received message
fmt.Println("Received:", text)
// Echo back with prefix
fmt.Fprintf(conn, "echo: %s\n", text)
// Reset the deadline for each message
conn.SetDeadline(time.Now().Add(5 * time.Minute))
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading from connection:", err)
}
}
Using the TCP-specific functions (net.ListenTCP
, net.TCPAddr
, net.TCPConn
) gives us access to additional configuration options:
SetKeepAlive
: Enables keep-alive for the connectionSetKeepAlivePeriod
: Sets the keep-alive periodSetDeadline
: Sets a deadline for read/write operationsSetLinger
: Controls how long the connection lingers after CloseSetNoDelay
: Controls whether the Nagle algorithm is used
Other Net Package Capabilities
The net
package also provides:
-
DNS Resolution:
ips, err := net.LookupIP("example.com")
-
Interface Enumeration:
interfaces, err := net.Interfaces()
-
Connection Dialing:
conn, err := net.Dial("tcp", "example.com:80")
-
UDP Services:
udpAddr, _ := net.ResolveUDPAddr("udp", ":8081") conn, _ := net.ListenUDP("udp", udpAddr)
Summary
In this lesson, we've explored:
- Creating a basic TCP server using
net.Listen
- Handling client connections concurrently using goroutines
- Using
io.Copy
for simple echo functionality - Enhancing the server with
bufio.Scanner
to process input line by line - Using specialized TCP functions for additional configuration options
The net
package provides powerful, low-level networking primitives that serve as the foundation for higher-level networking in Go. In the next lesson, we'll build on this knowledge to work with HTTP, which is implemented on top of TCP.
Understanding these network fundamentals is crucial for building CLI applications that interact with remote services, implement client-server architectures, or provide network-based functionality.