TCP and UDP What is the difference and how to use them directly in Go.

TCP vs. UDP

TCP and UDP are network protocols, used for sending data packages over a network. They are used on top of the IP Stack inside the transport layer.

Transmission Control Protocol (TCP) is a connection-orientated protocol. This means that it first establishes a connection before sending the actual data. TCP is also able to confirm if the data is received by the receiver.

User Datagram Protocol (UDP) however is a connectionless protocol, which means it does not establish a connection with the receiver and therefore won’t acknowledge in case of failed delivery.

TCP is used in cases where it is important that all the send data is received. UDP can be used when you don’t care that a few packages get lost in favor of more speed, like in media streaming.

Go net package

The Go net package exposes all kind of methods when you need to deal with TCP or UDP, either through there dedicated methods (like DialTCP and ListenTCP) or through the more abstracted methods (like Dial and Listen).

We will start out with TCP, and create a TCP server and client that can communicate with each other.

Go TCP

To run a TCP server in GO, use the code listed below. You can specify the host:port to listen to as CLI argument. This argument is then parsed as a TCPAddr and used to start the TCP server.

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
)

func main() {

	if len(os.Args) == 1 {
		fmt.Println("Please provide host:port")
		os.Exit(1)
	}

	// Resolve the string address to a TCP address
	tcpAddr, err := net.ResolveTCPAddr("tcp4", os.Args[1])

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Start listening for TCP connections on the given address
	listener, err := net.ListenTCP("tcp", tcpAddr)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	for {
		// Accept new connections
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println(err)
		}
		// Handle new connections in a Goroutine for concurrency
		go handleConnection(conn)
	}
}

func handleConnection(conn net.Conn) {
	defer conn.Close()

	for {
		// Read from the connection untill a new line is send
		data, err := bufio.NewReader(conn).ReadString('\n')
		if err != nil {
			fmt.Println(err)
			return
		}

		// Print the data read from the connection to the terminal
		fmt.Print("> ", string(data))

		// Write back the same message to the client.
		conn.Write([]byte(data))
	}
}

New connections produced by the listener.Accept() method will be handled in a goroutine. This is because else one connection would block the server.

With the above code, we can run the server, start it with:
> go run main.go
:1200
from your terminal.

Fire up another terminal and test your server with telnet:
> telnet localhost 1200
Write any message and after pressing enter it should be repeated!

Now that we know the server is working we can create the client in Go.

TCP Client

The client code uses DialTCP to connect to the tcp address specified as an argument when starting the program. After successful connecting, it sends one message to the server then it reads the server reply and exits.

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
)

func main() {

	if len(os.Args) == 1 {
		fmt.Println("Please provide host:port to connect to")
		os.Exit(1)
	}

	// Resolve the string address to a TCP address
	tcpAddr, err := net.ResolveTCPAddr("tcp4", os.Args[1])

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Connect to the address with tcp
	conn, err := net.DialTCP("tcp", nil, tcpAddr)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Send a message to the server
	_, err = conn.Write([]byte("Hello TCP Server\n"))
	fmt.Println("send...")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Read from the connection untill a new line is send
	data, err := bufio.NewReader(conn).ReadString('\n')
	if err != nil {
		fmt.Println(err)
		return
	}

	// Print the data read from the connection to the terminal
	fmt.Print("> ", string(data))
}

We will make one minor adjustment to the server code in order to send a different reply. Instead of echoing the original message, we will reply with “Hello TCP client”.

Change (59) conn.Write([]byte(data)) to:
conn.Write([]byte(“Hello TCP Client\n”))

Restart the server and also start the client in a new terminal.
> go run main.go
:1200
from your terminal.

The result should be as followed:

That’s it! You now have a working TCP Server/Client in Go. Next, we will create a similar application with the UDP protocol.

Go UDP

The UDP code looks a lot like the TCP code, Instead of the resolve and listen TCP methods it uses the UDP variants. The main difference however is that you don’t have a listener that accepts “connections”. This is because it UDP is “connectionless”.

package main

import (
	"fmt"
	"net"
	"os"
)

func main() {

	if len(os.Args) == 1 {
		fmt.Println("Please provide host:port")
		os.Exit(1)
	}

	// Resolve the string address to a UDP address
	udpAddr, err := net.ResolveUDPAddr("udp", os.Args[1])

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Start listening for UDP packages on the given address
	conn, err := net.ListenUDP("udp", udpAddr)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Read from UDP listener in endless loop
	for {
		var buf [512]byte
		_, addr, err := conn.ReadFromUDP(buf[0:])
		if err != nil {
			fmt.Println(err)
			return
		}

		fmt.Print("> ", string(buf[0:]))

		// Write back the message over UPD
		conn.WriteToUDP(buf[0:], addr)
	}
}

In the UDP server just listens for new packages in an endless loop, it prints the content to the console and writes the same message to the UDP address from the original sender. The response sent by the server is completely separated from the original message. Because there is no connection established between the server and potential clients, we don’t have to run this code in with goroutines.

To test this server we can use Netcat, first start the server:
> go run main.go :1200
Then open another terminal and run Netcat in UDP mode:
> nc -u localhost 1200

When we send messages from our client to the server, these messages should be logged in our server console and then repeated in our client console.

UDP Client

The Go UDP server looks almost exactly the same as the TCP client, the only difference is calling the ResolveUDPAddr and DialUDP methods instead of there TCP counterparts. Because Go returns a connection interface we can process it like a connection.

package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
)

func main() {

	if len(os.Args) == 1 {
		fmt.Println("Please provide host:port to connect to")
		os.Exit(1)
	}

	// Resolve the string address to a UDP address
	udpAddr, err := net.ResolveUDPAddr("udp", os.Args[1])

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Dial to the address with UDP
	conn, err := net.DialUDP("udp", nil, udpAddr)

	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Send a message to the server
	_, err = conn.Write([]byte("Hello UDP Server\n"))
	fmt.Println("send...")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Read from the connection untill a new line is send
	data, err := bufio.NewReader(conn).ReadString('\n')
	if err != nil {
		fmt.Println(err)
		return
	}

	// Print the data read from the connection to the terminal
	fmt.Print("> ", string(data))
}

As before we will make one minor adjustment to the server code.
Change (44) conn.WriteToUDP(buf[0:], addr) to:
conn.WriteToUDP([]byte(“Hello UDP Client\n”), addr)

Restart the (UDP) server and then start the client in a new terminal.
> go run main.go
:1200
from your terminal.

The result should be like the image below:

Higher level interfaces

It became clear in the client implementations that a lot of the code is the same for TCP & UDP connections. Go does a good job of creating a higher interface that works well with both. So instead of using DialTCP and DialUDP, you could use Dial.

Dial accepts a network (like “tcp”) and an address (as a string).
So if we pass both as an argument the first part of our client code would only be:

conn, err := net.Dial(os.Args[1], os.Args[2])

Conclusion

You should now have some basic understanding of the net package in go and how to leverage it to create TCP or UDP client/server connections!

The full source code of this post is available at:
https://github.com/jeroendk/go-tcp-udp

Leave a Reply

Your email address will not be published. Required fields are marked *