Building A TCP Bridger Server in Go - Part 1
Tags: go, tcp, network, io
Published: 2021-12-05 09:28:03

### What is going on?!
I was experimenting on how to transfer data between two clients using a tcp connection. 
I began by building a server capable of connecting two clients in order to be able to send textual messages back and forth over a TCP connection.
Then tried to transfer voice packets using that connection and realized I can build different clients that can utilize the power of the same server to transfer, well anything!

In this 3 or 4 part series, I'll show you how to build such a server with two clients.

### What is TCP?
According to [OSI Model](https://en.wikipedia.org/wiki/OSI_model), TCP is defined inside Transport Layer which is Layer 4. That's where the notion of ports like 80, 22, etc. is added to the previous layer, Network.
Protocols like HTTP are build on top of TCP. They are basically a set of standards that define how messages should be transferred and interpreted.

### What are the Requirements?
* Intermediate understanding of Go language
* Some knowledge about networks

### What...?!
Let's start by building a tcp server that listens on a specific port, like 4004.

```go
// Start func main()
func main() {
	var port string
	flag.StringVar(&port, "p", "4004", "port number")
	flag.Parse()

	addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%s", port))
	if err != nil {
		log.Fatalln(err)
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		log.Fatalln(err)
	}
	defer l.Close()
// ...
```
`ResolveTCPAddr` returns an address of TCP end point. Then we provide it to `ListenTCP` which in turn, gives us a pointer to `net.TCPListener`. 

`TCPListener` internally consists of a network file descriptor which is basically a [file descriptor](https://en.wikipedia.org/wiki/File_descriptor) to a socket bound to that `4004` port and a `ListenConfig` struct that contains options for listening to an address.

We also `defer` the `Close()` function on our newly created listener to make sure it gets closed when the outer function returns.

*I'm not gonna discuss how the internals really work for now, but let me know if you're interested in a deep dive.*

Accepting Connections and spawning a new go routine for each one:
```go
// ...

fmt.Printf("waiting for clients on port %s...\n", port)
for {
	c, err := l.AcceptTCP()
	if err != nil {
		fmt.Println(err)
		return
	}
	defer c.Close()
	fmt.Printf("client connected: %s\n", c.RemoteAddr().String())
	
	_, err = c.Write([]byte("Hello, Client :)\nYou deserve a new Go routine!\n"))
	if err != nil {
		fmt.Println("err greeting user: ", err)
		return
	}
	go cmdHandler(c)

}
// End func main()
```
Inside a for loop, we will wait for clients to reach out. `AcceptTCP` blocks until a new client is connected. Then it returns a pointer to `net.TCPConn`.
`net.TCPConn` contains another network file descriptor that again points to a socket.
> It is really important to understand that each `net.TCPConn` returned from `Accept()` is unique in the sense that it's source IP and port are different from others.
*We will come back to this topic later when we start building a client for our server.*

Again, `defer` the `Close()` function since we are dealing with a file descriptor like `net.TCPListener`
>Each time you open a file or in general, anything that gives you a file descriptor, you have to close it when you no longer need it.

`net.TCPConn` has some methods on it. `Read()` and `Write()` are two of them. In fact, it implements both `io.Reader` and `io.Writer` interfaces.
That's what we expect from a connection, right?
We want to be able to both read from and write to it.

`c.RemoteAddr().String()` returns a string representation of newly connected user in the form of `ip:port`. That's the port I was talking about which is unique for each `net.TCPConn`.

>You can navigate to [official documentations](https://pkg.go.dev/net#TCPConn) to see all available methods on `net.TCPConn`.

So, after a successful connection, we send a message back to user using `Write()` method which accepts a byte slice and returns number of bytes written and an error.

> There are also other ways to send data to clients. For example `fmt.Fprint(w io.Writer, a ...interface{})`.
It takes an `io.Writer` (which we now know that `net.TCPConn` is one such thing) and writes the provided data to it.
```go
_, err = fmt.Fprint(c, "Hello, Client :)\nYou deserve a new Go routine!\n")
if err != nil {
	fmt.Println("err greeting user: ", err)
	return
}
```

If everything goes as planned, we reach to the point that we spawn a go routine for each client with it's associated `net.TCPConn`. As you probably know, this is accomplished by calling `go cmdHandler(c)`.

Now let's see how `cmdHandler()` function looks like.
Although we have bigger plans as the subject suggests, for now we just read the textual data sent by each client and print it to standard output of the server machine.
```go
func cmdHandler(c *net.TCPConn) {
	defer c.Close()
	for {
		msg, err := bufio.NewReader(c).ReadString('\n')
		if err != nil {
			if err == io.EOF {
				fmt.Printf("client '%s' left the session.\n", c.RemoteAddr().String())
				return
			}
			fmt.Println(err)
			return
		}
		fmt.Printf("%s -> %s", c.RemoteAddr().String(), msg)
	}
}
```
 Well, we start by entering a loop.
`bufio.NewReader(c).ReadString('\n')` blocks the loop until is reads the provided element from the Reader. 
In our case, Reader is a `*net.TCPConn` and the element we are waiting for to occur is '\n' (LF).

>Let's talk a little bit about buffered I/O.
If you ever try to run this program and connect to it using a tool like telnet, you will see when you write something on the client side, as soon as you press enter, the data gets printed on the server side.
You might think the moment you press enter, the data gets sent but that's not always the case.
Data is constantly coming from a client, we want to interpret it as we wish. so we use something called a buffer to store it only print it when it's somehow complete. in our case, when the data contains a line feed ('\n').
*But be aware that tools like telnet may themselves have an internal buffer, so they won't send data to the server until you press enter.*

So `cmdHandler` prints the data sent by client to server's standard output.
 
When the connection is closed manually or by the client, the last message is the EOF which indicates the End-Of-File.

And finally `c.RemoteAddr().String()` returns the string representation of the remote address.

I think here is a good place to end the part 1.
Let me know your ideas in the comments section and wait for part 2.

**Here is the complete code for ctrl+c / ctrl+v lovers like me:**
```go
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
)

func main() {
	var port string
	flag.StringVar(&port, "p", "4004", "port number")
	flag.Parse()

	addr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf(":%s", port))
	if err != nil {
		log.Fatalln(err)
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		log.Fatalln(err)
	}
	defer l.Close()

	fmt.Printf("waiting for clients on port %s...\n", port)
	for {
		c, err := l.AcceptTCP()
		if err != nil {
			fmt.Println(err)
			return
		}
		defer c.Close()
		fmt.Printf("client connected: %s\n", c.RemoteAddr().String())

		_, err = c.Write([]byte("Hello, Client!\nYou deserve a new Go routine...\n"))
		if err != nil {
			fmt.Println("err greeting user: ", err)
			return
		}
		go cmdHandler(c)
	}
}

func cmdHandler(c *net.TCPConn) {
	defer c.Close()
	for {
		msg, err := bufio.NewReader(c).ReadString('\n')
		if err != nil {
			if err == io.EOF {
				fmt.Printf("client '%s' left the session.\n", c.RemoteAddr().String())
				return
			}
			fmt.Println(err)
			return
		}
		fmt.Printf("%s -> %s", c.RemoteAddr().String(), msg)
	}
}
```