I think it is essential to begin where Go deals with communication over the Internet. I do not want to dive down into the deepest depths, all the way to the network primitives of Go. Still, I want to understand how remote computers can communicate with each other using Go's network-related libraries. I hope that starting here will help later with fine-tuning and debugging TCP issues.

Let's learn by creating a simple socket.

Sockets are a computer networking equivalent to getting a new telephone number and phone at your house. Your friend also happens to get a unique telephone number, so you both want to communicate with each other. Your friend (a client) calls you at your telephone number. You are a server in this scenario. You can accept your friend's call when your phone rings and start communicating if you listen for incoming calls.

Analogies are great, but we are not dealing with telephones and voice signals. Instead, we need to receive connections and data from a program running on a remote computer and probably send back a response.

Prerequisites

I don't know if you want to follow along, but in case you do, here are some prerequisites to help you get started.

  1. Install Go from https://golang.org/.
  2. Get some basic Hello, World stuff running at a minimum to confirm your install works.
  3. Play with Go modules in the official tutorial.
I used Go version go1.16.6 windows/amd64 at the time of this writing.

If you don't have Go installed, you should start with the GoLang Docs and complete some of the "Getting Started" content before you continue.

Let's get started.

Check our Setup Works


Here is a task list of stuff we need to get done.

  1. Create a folder to serve as your Go project
  2. Create a new sub-directory under your project folder called server
  3. Create a new file under /server named main.go
  4. Write the Go code for "Hello, World."
  5. In a terminal, change to the /server directory and enter go run main.go

With an empty main.go, let's start by getting Hello, World to run.

package main

import "fmt"

func main() {
	fmt.Println("Hello, World")
}
Example Hello, World in Go.

Run this program with go run main.go and it should print Hello, World to your console. If not, head back to the pre-requisites listed above and get your Go environment set up and working before moving on.

Setup a Simple Server

For this exercise, I am referring to the Go source documentation for the net package. I am still learning, but I will do my best to transform examples along the way to Go standards. I plan to leave myself in a good place at the end of this post.

Here is a task list of stuff we need to get done.

  1. Open main.go in the editor again and write the source code for a simple TCP/IP server.
  2. Import the "net" package.
  3. Listen for client connections.
  4. After we accept a client connection, close it.

Note: This starting example ignores errors to focus on the core mechanics, but we should NOT do this in practice. We will fix some of this as we move forward.

package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println("Starting TCP server...")
	server, _ := net.Listen("tcp", ":8080")
	for {
		client, _ := server.Accept()
		fmt.Println("New client connected!")
		client.Close()
	}
}
Example Production Ready TCP Server in Go. (Only kidding.)

These two lines of code do the work:

server, _ := net.Listen("tcp", ":8080")

This Go code calls Listen exported from the net package and sends the TCP/IP instructions down the stack to the operating system, ultimately controlling the network hardware. Once this happens, if there were no errors, Go receives an open TCP socket on port:8080, in listening mode. This socket is ready for connections to be accepted.

client, _ := server.Accept()

The call to server.Accept() blocks until new client connections are received. I'm not entirely sure yet, but perhaps this is an area where we will need to consider multi-threading in the future (using Goroutines, perhaps?)

Test The Server

Here is a task list of stuff we need to get done.

  1. In a terminal, change to the /server directory and enter go run main.go
  2. Open a client software of your choice to connect to localhost:8080 (telnet, curl, web browser, etc.)

This server doesn't do anything interesting yet. It prints some text to the server console when a new client connection is accepted. After this, the server closes the client connection and waits for the next client to connect.

You may get a firewall/security prompt from your operating system to allow this Go program to connect to the network.

>go run main.go
Starting TCP server...

Once you have your TCP server up and running, waiting for connections, you can try to connect to it. For example, you could use telnet localhost 8080 or open your web browser or curl and try to get "http://localhost:8080". You should see the server write one or more messages for each new client connection. Chrome web browser tries to connect to this server three times in a row before it stops.

>go run main.go
Starting TCP server...
New client connected!
New client connected!
New client connected!
...

Adding Some Error Handling

Here is a task list of stuff we need to get done.

  1. Open /server/main.go in the editor again and update the source code to check for errors
  2. Check for errors after net.Listen
  3. Check for errors after server.Accept
  4. Check for errors after client.Close
  5. Re-test the server to confirm it still works as it did previously.
package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println("Starting TCP server...")
	server, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println(err)
		// Do something smart about this error
	}
	for {
		client, err := server.Accept()
		if err != nil {
			fmt.Println(err)
			// No really, do something about these errors
		}
		fmt.Println("New client connected!")
		err = client.Close()
		if err != nil {
			fmt.Println("Error closing client socket.")
			// Consider yourself warned...
		}
	}
}
Example TCP Server in Go with Minimal Error Checking.

If all goes well, then you should see the same behavior as before.

>go run main.go
Starting TCP server...
New client connected!
New client connected!
New client connected!
...

Wrapping Up

Let's add one more thing to this server to try something for the client before closing the connection. Let's send a message across the socket back to the client. Our server will begin life here as a "Hello, World Server."

Here is a task list of stuff we need to get done.

  1. Open server/main.go in the editor again and update the source code.
  2. After a client connects, send "Hello, World" to the client (we will use fmt.Fprintf for this).
  3. Check for errors after sending the message.
  4. Re-test the server to confirm it still works.
package main

import (
	"fmt"
	"net"
)

func main() {
	fmt.Println("Starting TCP server...")
	server, err := net.Listen("tcp", ":8080")
	if err != nil {
		fmt.Println(err)
		// Do something smart about this error
	}
	for {
		client, err := server.Accept()
		if err != nil {
			fmt.Println(err)
			// No really, do something about these errors
		}
		fmt.Println("New client connected!")
		_, err = fmt.Fprintf(client, "Hello, World!\r\n")
		if err != nil {
			fmt.Println("Error sending our message to the client.")
		}
		err = client.Close()
		if err != nil {
			fmt.Println("Error closing client socket.")
			// Consider yourself warned...
		}
	}
}
Example of a Sweet "Hello, World Server" in Go.

The only new part of this program comes after a client has connected but before the server closes the connection. We use a reference to client and send a string.

...
		_, err = fmt.Fprintf(client, "Hello, World!\r\n")
		if err != nil {
			fmt.Println("Error sending our message to the client.")
		}
...

If all goes well, you should see the same behavior as before on the server console, but in the output of your telnet client or similar, you should now see "Hello, World!" returned from the server. We got a simple TCP server running, and this is where we will leave off this topic for now.

> telnet localhost 8080
Hello, World!

Connection to host lost.
>

I hope you found this interesting, and I look forward to learning and sharing more about using TCP and HTTP in Go.

Cheers!

Photo by Annie Spratt on Unsplash
Yellow telephone. Download this photo by Annie Spratt on Unsplash