net
Before You Start
- Applies to: Go 1.21+ (examples verified with Go 1.22) · Prerequisites: Advanced: Concurrency · Read time: about 15 min
Go's net standard library is a very powerful library that provides functionality for handling network communication, IP addresses, DNS resolution, TCP/UDP protocols, HTTP protocols, and other common tasks. Due to Go's inherent concurrency characteristics, it handles network I/O very concisely and efficiently.
Address Resolution
Go provides four functions to parse network addresses, which will be explained one by one below.
MAC Address
Signature:
func ParseMAC(s string) (hw HardwareAddr, err error)Example:
package main
import (
"fmt"
"net"
)
func main() {
hw, err := net.ParseMAC("00:1A:2B:3C:4D:5E")
if err != nil {
panic(err)
}
fmt.Println(hw)
}CIDR
Signature:
func ParseCIDR(s string) (IP, *IPNet, error)Example:
package main
import (
"fmt"
"log"
"net"
)
func main() {
ipv4Addr, ipv4Net, err := net.ParseCIDR("192.0.2.1/24")
if err != nil {
log.Fatal(err)
}
fmt.Println(ipv4Addr)
fmt.Println(ipv4Net)
}IP Address
IP addresses support parsing ipv4 and ipv6. The function signature is as follows:
func ResolveIPAddr(network, address string) (*IPAddr, error)Usage example:
package main
import (
"fmt"
"net"
)
func main() {
ipv4Addr, err := net.ResolveIPAddr("ip4", "192.168.2.1")
if err != nil {
panic(err)
}
fmt.Println(ipv4Addr)
ipv6Addr, err := net.ResolveIPAddr("ip6", "2001:0db8:85a3:0000:0000:8a2e:0370:7334")
if err != nil {
panic(err)
}
fmt.Println(ipv6Addr)
}TCP Address
TCP addresses support tcp4 and tcp6. Signature:
func ResolveTCPAddr(network, address string) (*TCPAddr, error)Usage example:
package main
import (
"fmt"
"net"
)
func main() {
tcp4Addr, err := net.ResolveTCPAddr("tcp4", "0.0.0.0:2020")
if err != nil {
panic(err)
}
fmt.Println(tcp4Addr)
tcp6Addr, err := net.ResolveTCPAddr("tcp6", "[::1]:8080")
if err != nil {
panic(err)
}
fmt.Println(tcp6Addr)
}UDP Address
UDP addresses support udp4 and udp6. Signature:
func ResolveUDPAddr(network, address string) (*UDPAddr, error)Usage example:
package main
import (
"fmt"
"net"
)
func main() {
udp4Addr, err := net.ResolveUDPAddr("udp4", "0.0.0.0:2020")
if err != nil {
panic(err)
}
fmt.Println(udp4Addr)
udp6Addr, err := net.ResolveUDPAddr("udp6", "[::1]:8080")
if err != nil {
panic(err)
}
fmt.Println(udp6Addr)
}Unix Address
Unix addresses support unix, unixgram, and unixpacket. Signature:
func ResolveUnixAddr(network, address string) (*UnixAddr, error)Usage example:
package main
import (
"fmt"
"net"
)
func main() {
unixAddr, err := net.ResolveUnixAddr("unix", "/tmp/mysocket")
if err != nil {
panic(err)
}
fmt.Println(unixAddr)
}DNS
Go also provides many functions for DNS queries. The following is an example of resolving a domain name's IP address:
package main
import (
"fmt"
"net"
)
func main() {
addrs, err := net.LookupHost("github.com")
if err != nil {
panic(err)
}
fmt.Println(addrs)
}Query MX records:
package main
import (
"fmt"
"net"
)
func main() {
mxs, err := net.LookupMX("github.com")
if err != nil {
panic(err)
}
fmt.Println(mxs)
}Network Programming
The logic of TCP programming is very simple. For the client, it is:
- Establish connection
- Send data or read data
- Exit
For the server, it is:
- Listen on address
- Get connection
- Create a new goroutine to handle the connection
Here is a simple example, client code:
package main
import (
"net"
)
func main() {
// Establish connection
conn, err := net.Dial("tcp", "0.0.0.0:1234")
if err != nil {
panic(err)
}
defer conn.Close()
// Send data
for i := range 10 {
_, err := conn.Write([]byte{'a' + byte(i)})
if err != nil {
panic(err)
}
}
}Server code:
package main
import (
"errors"
"fmt"
"io"
"net"
"sync"
)
func main() {
// Listen on address
listener, err := net.Listen("tcp", "0.0.0.0:1234")
if err != nil {
panic(err)
}
defer listener.Close()
var wg sync.WaitGroup
for {
// Block and wait for the next connection to be established
conn, err := listener.Accept()
if err != nil {
panic(err)
}
// Start a new goroutine to handle the connection asynchronously
wg.Add(1)
go func() {
defer wg.Done()
buf := make([]byte, 4096)
for {
// Read data from the connection
n, err := conn.Read(buf)
if errors.Is(err, io.EOF) {
break
} else if err != nil {
panic(err)
}
data := string(buf[:n])
fmt.Println(data)
}
}()
}
wg.Wait()
}The client sends data, and the server receives data. This example is very simple. When the server establishes a new connection, it only needs to start a new goroutine to handle it without blocking. UDP is generally written in a similar way.
Complete Example
The program below runs as-is: copy it into a file and execute go run main.go, or paste it into the Go Playground.
package main
import (
"fmt"
"net"
)
func main() {
ip := net.ParseIP("192.168.0.1")
fmt.Println(ip != nil, ip.IsPrivate())
host, port, err := net.SplitHostPort("example.com:8080")
fmt.Println(host, port, err == nil)
fmt.Println(net.JoinHostPort("127.0.0.1", "80"))
}Expected output:
true true
example.com 8080 true
127.0.0.1:80Common Pitfalls
net.ParseIPreturns nil on failure. Always check before calling methods such asIsPrivate.SplitHostPortneeds a port. Inputs without a colon return an error; handle IPv6 bracket syntax correctly.Dialhas no timeout by default. UseDialTimeoutor a context-aware dialer for production code.- Always close connections.
defer conn.Close()in both client and server paths. - UDP uses
PacketConn. The stream-styleconn.Write/Readsemantics differ; handle datagram boundaries yourself.
Change Log
- 2026-10-14: Added "Before You Start" notes, a complete runnable example with expected output, common pitfalls, and this change log.
