In this tutorial, we'll see how to build a straightforward TCP server that takes incoming connections. The server will reply with a message with some information. Beginners who wish to learn about the Go net package and how to process requests should check out this tutorial.
Package net provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets.
Although the package provides access to low-level networking primitives, most clients will need only the basic interface provided by the Dial, Listen, and Accept functions and the associated Conn and Listener interfaces. The crypto/tls package uses the same interfaces and similar Dial and Listen functions.
Building a Simple Golang TCP Server
Set the variables and listen and close TCP connections
First of all, we need to define the variables for the TCP connection:
const (
HOST = "localhost"
PORT = "8080"
TYPE = "tcp"
)
The following step is to listen to new connections. The net package provides Listen()
function to do that. If the listener raises any errors, logs the error message to the console and exists with a status code of 1.
func Listen(network, address string) (Listener, error)
: Listen announces on the local network address. The network must be "tcp", "tcp4", "tcp6", "unix" or "unixpacket". For TCP networks, if the host in the address parameter is empty or a literal unspecified IP address, Listen listens on all available unicast and anycast IP addresses of the local system.
listen, err := net.Listen(TYPE, HOST+":"+PORT)
if err != nil {
log.Fatal(err)
os.Exit(1)
}
To close the listener after the server terminates, use the defer keyword and the Close()
method:
func (c *IPConn) Close() error
: Close closes the connection.
Continuously listen for connections and handle requests
To continuously listen for connections, use an infinity loop:
for {
conn, err := listen.Accept()
if err != nil {
log.Fatal(err)
os.Exit(1)
}
We can write a function to handle any incoming requests:
Our function will take the type net.Conn
struct as the parameter. We define a new buffer to store the incoming data of size 1024. Using the Write method to send data back to the client:
// incoming request
buffer := make([]byte, 1024)
_, err := conn.Read(buffer)
if err != nil {
log.Fatal(err)
}
// write data to response
time := time.Now().Format(time.ANSIC)
responseStr := fmt.Sprintf("Your message is: %v. Received time: %v", string(buffer[:]), time)
conn.Write([]byte(responseStr))
// close conn
conn.Close()
Full TCP Server Code
We will wrap the handle request function to a go routine:
package main
import (
"fmt"
"log"
"net"
"os"
"time"
)
const (
HOST = "localhost"
PORT = "8080"
TYPE = "tcp"
)
func main() {
listen, err := net.Listen(TYPE, HOST+":"+PORT)
if err != nil {
log.Fatal(err)
os.Exit(1)
}
// close listener
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
log.Fatal(err)
os.Exit(1)
}
go handleRequest(conn)
}
}
func handleRequest(conn net.Conn) {
// incoming request
buffer := make([]byte, 1024)
_, err := conn.Read(buffer)
if err != nil {
log.Fatal(err)
}
// write data to response
time := time.Now().Format(time.ANSIC)
responseStr := fmt.Sprintf("Your message is: %v. Received time: %v", string(buffer[:]), time)
conn.Write([]byte(responseStr))
// close conn
conn.Close()
}
Building a Simple Golang TCP Client
Set the variables and set up a connection to TCP Server
First of all, we need to define the variables for the TCP connection:
const (
HOST = "localhost"
PORT = "8080"
TYPE = "tcp"
)
Next, we have to resolve TCP server's address
tcpServer, err := net.ResolveTCPAddr(TYPE, HOST+":"+PORT)
To dial to TCP server, we will use DialTCP()
function:
func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
: DialTCP acts like Dial for TCP networks. The network must be a TCP network name; see func Dial for details. If laddr is nil, a local address is automatically chosen. If the IP field of raddr is nil or an unspecified IP address, the local system is assumed. Dial connects to the address on the named network.
conn, err := net.DialTCP(TYPE, nil, tcpServer)
if err != nil {
println("Dial failed:", err.Error())
os.Exit(1)
}
We need to close the connection after sending and receiving data:
conn.Close()
Send and receive data from the server
To send and receive data, we can use Write()
and Read()
functions as shown below:
_, err = conn.Write([]byte("This is a message"))
if err != nil {
println("Write data failed:", err.Error())
os.Exit(1)
}
// buffer to get data
received := make([]byte, 1024)
_, err = conn.Read(received)
if err != nil {
println("Read data failed:", err.Error())
os.Exit(1)
}
Full TCP client code
package main
import (
"net"
"os"
)
const (
HOST = "localhost"
PORT = "8080"
TYPE = "tcp"
)
func main() {
tcpServer, err := net.ResolveTCPAddr(TYPE, HOST+":"+PORT)
if err != nil {
println("ResolveTCPAddr failed:", err.Error())
os.Exit(1)
}
conn, err := net.DialTCP(TYPE, nil, tcpServer)
if err != nil {
println("Dial failed:", err.Error())
os.Exit(1)
}
_, err = conn.Write([]byte("This is a message"))
if err != nil {
println("Write data failed:", err.Error())
os.Exit(1)
}
// buffer to get data
received := make([]byte, 1024)
_, err = conn.Read(received)
if err != nil {
println("Read data failed:", err.Error())
os.Exit(1)
}
println("Received message:", string(received))
conn.Close()
}
Output:
Received message: Your message is: This is a message and received time: Wed Nov 2 14:36:37 2022
We can use the terminal to get the same result:
Summary
List the main points once more.
- With the help of the net package, a TCP server and client can be made with incredibly little code.
- Listen on all possible local unicast and anycast IP addresses if the host supplied in the address argument is null or a literal IP address that is not specified.
After reading this article, you should have a deeper comprehension of the principles involved in setting up a TCP server and client.
References
https://pkg.go.dev/net
https://en.wikipedia.org/wiki/Transmission_Control_Protocol