In this post we’ll enhance the telnet_portal plugin to better handle connections.

The Connection Struct

To prepare it to interact with the game world, we need to make our server handle connections in a little cleaner way. We’ll start by creating a new type called Connection that will represent a single connection to the server. To do this we create a file in the internal/server directory called connection.go, we’ll start it out like this:

package server

type connection struct {
    uuid        string
    conn        net.Conn
    stop        chan bool
}

We have three fields here, the unique ID of the connection, the connection itself, and a channel that will be used to signal the connection loop to stop.

A channel is a type that facilitates communication between goroutines.

Golang doesn’t have a built-in way to generate uuids, so we’ll use the Google uuid package by calling go get github.com/google/uuid in the telnet_portal plugin directory.

Now let’s create a function that our server will call to create a new connection:

func newConnection(conn net.Conn) *connection {
	uid := uuid.New().String()

	return &connection{
		uuid: uid,
		conn: conn,
		stop: make(chan bool),
	}
}

Now for a connection loop. Let’s create a Start() and we’ll move our connection loop from the server into the connection:

// Start starts the connection
func (c *connection) Start() {
	go func() {
		c.logger.Trace().Msg("starting connection loop")
		for {
			buf := make([]byte, 1024)

			_, err := c.conn.Read(buf)

			if err != nil {
				c.logger.Error().Err(err).Msg("error reading from connection")
				c.Stop()

				break
			}
			// Send a response back to person contacting us.
			_, err = c.conn.Write([]byte(fmt.Sprintf("Received %s", string(buf))))

			if err != nil {
				fmt.Println("Error writing:", err.Error())
				c.Stop()

				break
			}
		}
	}()

	<-c.stop

	fmt.Println("Connection closed")
}

You’ll notice that we added a <-c.stop after the for loop. This is a blocking read on the stop channel. This is a way to keep the connection loop running until we signal it to stop.

Finally we add the Stop() function to stop the connection loop:

// Stop stops the connection
func (c *connection) Stop() {
	c.logger.Debug().Msg("stopping connection")
	_ = c.conn.Close()

	c.stop <- true
}

By passing c.stop <- true, it causes the c.stop channel to stop blocking, allowing the Start() function to complete.

Tracking Connections

We need a way to track all the connections that are currently active within the server. We’ll update the server to have a new map that tracks all the connections by their unique ID so we’ll add the following to our server struct:

type server struct {
	// .. server ..
	connections     map[string]*connection
}

And make sure the new map is created when we call New():

func New() *server {
	return &server{
		connections: make(map[string]*connection),
	}
}

In our handleConnection function form which we removed the connection loop, we’ll instead add the code to create a instance of the connection struct and add it to the server’s connections map, making it look something like this:

func (s *server) handleConnection(conn net.Conn) {
	connection := newConnection(conn)
	s.connections[connection.uuid] = connection

	go connection.Start()
}

With this all in place we’ve made a big step forward to being able to start building on the world side of our engine. All we have left before we start that step is setting up the message bus which we will cover in the next post.

Previous Post In This Series <> Next Post In This Series