DEV Community

Alice Williams
Alice Williams

Posted on

Getting Started with Sockets Concurrently in GoLang

Getting Started with Sockets Concurrently in GoLang

What are sockets?

Sockets, or 'Web Sockets', are a TCP-based internet protocol for real-time and persistent connections between a server and one or more client(s). This protocol is faster than standard HTTP connections, allows for a two-way back-and-forth transfer of data between server and clients. Initialized with a HTTP upgrade request as a handshake operation, after which all parties, server and clients, may send data at any time, from either direction, at any time. Generally used for applications such as social feeds, chat clients, multiplayer games, multimedia streaming, and collaborative development sessions.

Why GoLang?

Concurrency, the biggest advantage to our use-case, socket programming, given it's asynchronous design. Along with the fact that GoLang is an extremely modern high-level language making development of established applications quick and efficient. Plus I just love the simplistic elegance of the language! :) Don't know GoLang? Well, this is a great low-level project to see some basic concepts at play, especially goroutines, GoLang's concurrency feature.

Project Start

This tutorial is based off of my open-source repository.

Create a new project folder and initialize a new git repository to track changes, even if only stored locally by opening a shell or command-line window within your new project folder and running, git init. Make two sub-folders in your project folder, for the server and client. Initialize a GoLang module in each folder by running, go mod init, within each folder, you may need to supply the path if you're outside of your GOPATH. Create two new files, each called, main.go, as the application entry-points, inside of each sub-folder.

Socket Server

The 'Hello World' step:

package main

import (
    "fmt"
)

const (
    connHost = "localhost"
    connPort = "8080"
    connType = "tcp"
)

func main() {
    fmt.Println("Starting " + connType + " server on " + connHost + ":" + connPort)

}

Listening and the main loop:

package main

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

const (
    connHost = "localhost"
    connPort = "8080"
    connType = "tcp"
)

func main() {
    fmt.Println("Starting " + connType + " server on " + connHost + ":" + connPort)
    l, err := net.Listen(connType, connHost+":"+connPort)
    if err != nil {
        fmt.Println("Error listening:", err.Error())
        os.Exit(1)
    }
    defer l.Close()

    for {

    }
}

Accept client connections:

package main

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

const (
    connHost = "localhost"
    connPort = "8080"
    connType = "tcp"
)

func main() {
    fmt.Println("Starting " + connType + " server on " + connHost + ":" + connPort)
    l, err := net.Listen(connType, connHost+":"+connPort)
    if err != nil {
        fmt.Println("Error listening:", err.Error())
        os.Exit(1)
    }
    defer l.Close()

    for {
        c, err := l.Accept()
        if err != nil {
            fmt.Println("Error connecting:", err.Error())
            return
        }
        fmt.Println("Client connected.")

        fmt.Println("Client " + c.RemoteAddr().String() + " connected.")
    }
}

Handling clients concurrently:

package main

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

const (
    connHost = "localhost"
    connPort = "8080"
    connType = "tcp"
)

func main() {
    fmt.Println("Starting " + connType + " server on " + connHost + ":" + connPort)
    l, err := net.Listen(connType, connHost+":"+connPort)
    if err != nil {
        fmt.Println("Error listening:", err.Error())
        os.Exit(1)
    }
    defer l.Close()

    for {
        c, err := l.Accept()
        if err != nil {
            fmt.Println("Error connecting:", err.Error())
            return
        }
        fmt.Println("Client connected.")

        fmt.Println("Client " + c.RemoteAddr().String() + " connected.")

        go handleConnection(c)
    }
}

func handleConnection(conn net.Conn) {
    buffer, err := bufio.NewReader(conn).ReadBytes('\n')

    if err != nil {
        fmt.Println("Client left.")
        conn.Close()
        return
    }

    log.Println("Client message:", string(buffer[:len(buffer)-1]))

    conn.Write(buffer)

    handleConnection(conn)
}

Testing without a client:

If you're developing on Linux you can test your new socket server application without writing the client application first. Start your socket server with, go run main.go, from the server sub-folder within your shell. Now run, nc localhost 8080, replacing the host and port number if needed. There are applications to run the nc (netcat) command on Windows, however, I don't know the best of options here.

Socket Client

The 'Hello World' step:

package main

import (
    "fmt"
)

const (
    connHost = "localhost"
    connPort = "8080"
    connType = "tcp"
)

func main() {
    fmt.Println("Connecting to " + connType + " server " + connHost + ":" + connPort)
}

Connecting to the server:

package main

import (
    "fmt"
)

const (
    connHost = "localhost"
    connPort = "8080"
    connType = "tcp"
)

func main() {
    fmt.Println("Connecting to " + connType + " server " + connHost + ":" + connPort)
}
    conn, err := net.Dial(connType, connHost+":"+connPort)
    if err != nil {
        fmt.Println("Error connecting:", err.Error())
        os.Exit(1)
    }

Handling input and relaying response:

package main

import (
    "fmt"
)

const (
    connHost = "localhost"
    connPort = "8080"
    connType = "tcp"
)

func main() {
    fmt.Println("Connecting to " + connType + " server " + connHost + ":" + connPort)Getting Started with Sockets in GoLang
}
    conn, err := net.Dial(connType, connHost+":"+connPort)
    if err != nil {
        fmt.Println("Error connecting:", err.Error())
        os.Exit(1)
    }
    reader := bufio.NewReader(os.Stdin)

    for {
        fmt.Print("Text to send: ")

        input, _ := reader.ReadString('\n')

        message, _  := bufio.NewReader(conn).ReadString('\n')

        log.Print("Server relay:", message)
    }

Testing the client:

Run your socket server with, go run main.go, from the server project sub-folder on your shell or command-line. Now open another shell or command-line session within the client folder and run, go run main.go here as well to start the client application. Any text entered into the client will be sent to the server, displayed with a timestamp, and relayed back, being displayed again.

This tests the basic functionality of the socket server-client interface and can be extended upon by expanding the server.go handleConnection function. Each client is handled concurrently by the server and as many clients may connect as your system can handle running, you may also connect through other local systems or over the internet if you expose the port to traffic.

Wrapping up

Well now you have stand-alone socket server and client interfaces, in pure standard-library GoLang! So what's next you ask? The sky's the limit, write an IRC-style chat client or a streaming application, just take it one step at a time, and that's one reason we started with a new git repository to manage all those changes you'll start adding.

My open-source Github repository the code for this tutorial is stored in has further line-by-line documentation.


All The Best and Happy Coding!
By, Alice Williams

Top comments (3)

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
cyberience profile image
Cyberience

Missing last Curly Brackets, I bet that got lost in Copy and paste. but everyone checks out the Git Repo, its actually better and not missing the Bracket.

Collapse
 
yeboahnanaosei profile image
Nana Yeboah

This is just unfortunate. Why don't you just point out what the issue is to the benefit of everybody who comes across this post.