DEV Community

Cover image for How To Write Your First Golang WebSocket Client
Shridhar G Vatharkar
Shridhar G Vatharkar

Posted on • Updated on

How To Write Your First Golang WebSocket Client

I decided to explore Go (Golang) after listening to discussions about it. I chose to pull live forex data using Go because dealing with live FX data is my area of expertise.
Here is what I discovered.

  • The setup is perhaps the easiest
  • Functions with little to no problems.

Many Go and other language specialists will disagree with my second statement. Even though Python is weak in the two areas mentioned, I like it (I may have done it again).

You will need prior programming experience, an internet connection, and an email address, to work along this tutorial.

Let us start

Obtain WebSocket API Key

Let us receive our FX WebSocket API key from our login website before configuring our Go environment. It is unpaid. Create your API key after logging into your account and reserve it.

Image description

Environment Set up

Download and set up Go first from https://golang.org/doc/install.

Image description

We need to take the following three easy steps:

  1. As you install Golang. Open the command prompt and type in the following:
go version
Enter fullscreen mode Exit fullscreen mode

Image description

  1. Secondly, you should install the dependencies.
go get github.com/gorilla/websocket
Enter fullscreen mode Exit fullscreen mode
  1. It is time for you to Go run!

Let's start coding! We'll begin by developing the fundamental framework. When the program starts, the function main() will be invoked, and at the same time, we'll import the necessary libraries. We will employ the following in this illustration:

// +build ignore

package main

import (
  "log"
  "net/url"
  "os"
  "os/signal"
  "time"
  "github.com/gorilla/websocket"
)
func main() {
//Add progrma content here 
}

Enter fullscreen mode Exit fullscreen mode

A couple of variables will be added to this, one to store the incoming messages and the other to deal with interrupt events when the program ends. Similar to a buffer in a few other languages, these variables serve as the message's conduit.

Channels in Go can be either one-way or bidirectional. To tidy up nicely once the program ends, we will add a signal notifier to our interrupt variable.

messageOut := make(chan string)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
Enter fullscreen mode Exit fullscreen mode

We will now generate our URL for the WebSocket server connection and output it so we can verify the URL.

u := url.URL{Scheme: "wss", Host: "marketdata.tradermade.com", Path: "/feedadv",}
log.Printf("connecting to %s", u.String())
Enter fullscreen mode Exit fullscreen mode

The next step is to develop the code to connect to the server and handle various connection-related results. Upon creation, we scan for errors and notify the user if we find any.

The final line of this section adds "defer c.Close()," telling the program to call Close() on this connection when this method is finished. Consider the initial deferred execution as the final operation on the stack.

c, resp, err := websocket.DefaultDialer.Dial(u.String(), nil);

  if err != nil {
    log.Printf("handshake failed with status %d", resp.StatusCode)
    log.Fatal("dial:", err)
  }
  //When the program closes close the connection
  defer c.Close()
Enter fullscreen mode Exit fullscreen mode

Now that the method to manage the connection has been written, it will read the process from the feed asynchronously because it is a goroutine. It uses C to read the message as it loops through. With ReadMessage(), either a notification or an error is returned.

When a message is received, it is first tested to determine whether it is a connection message; if it is, a string containing the user key and the necessary symbols is returned. The code outputs an error message and leaves when an error is received.

Once this has been sent, the client will reply with a pricing message. In this illustration, we print the message to the screen, but you would analyze the message data here as necessary.

done := make(chan struct{})
  go func() {
    defer close(done)
    for {
      _, message, err := c.ReadMessage()
      if err != nil {
        log.Println("read:", err)
        return
      }
      log.Printf("recv: %s", message)
      if string(message) == "Connected"{
        log.Printf("Send Sub Details: %s", message)
        messageOut <- "{"userKey":"YOUR_API_KEY", "symbol":"EURUSD"}"
      }
    }
  }()

Enter fullscreen mode Exit fullscreen mode

This program's final component appears complex but is actually straightforward. As the goroutine is async, it does not lock the main thread. Thus without this section, the program would return, and your feed handler would stop. This continuous loop is used to keep the program continuing.

ticker := time.NewTicker(time.Second)
  defer ticker.Stop()
  for {
    select {
    case <-done:
      return
    case m := <-messageOut:
      log.Printf("Send Message %s", m)
      err := c.WriteMessage(websocket.TextMessage, []byte(m))
      if err != nil {
        log.Println("write:", err)
        return
      }

    case t := <-ticker.C:
      err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
      if err != nil {
        log.Println("write:", err)
        return
      }

    case <-interrupt:
      log.Println("interrupt")
      // Cleanly close the connection by sending a close message and then
      // waiting (with timeout) for the server to close the connection.
      err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
      if err != nil {
        log.Println("write close:", err)
        return
      }

      select {
      case <-done:
      case <-time.After(time.Second):
      }

      return
    }
  }
Enter fullscreen mode Exit fullscreen mode

We can combine all the code, specify your API _KEY, and save the file as webSocketClient.go.
Then launch your code using the following command from the terminal:

go run websocketClient.go
Enter fullscreen mode Exit fullscreen mode

Image description

You may see real-time pricing updates. The first Websocket client you ran to pull FX data has been successfully completed.
Here is the complete application code:

 // +build ignore
package main
import (
  "log"
  "net/url"
  "os"
  "os/signal"
  "time"
  "github.com/gorilla/websocket"
)

func main() {
//Create Message Out   
  messageOut := make(chan string)
  interrupt := make(chan os.Signal, 1)
  signal.Notify(interrupt, os.Interrupt)
  u := url.URL{Scheme: "wss", Host: "marketdata.tradermade.com", Path: "/feedadv",}
  log.Printf("connecting to %s", u.String())
  c, resp, err := websocket.DefaultDialer.Dial(u.String(), nil);
  if err != nil {
    log.Printf("handshake failed with status %d", resp.StatusCode)
    log.Fatal("dial:", err)
  }

  //When the program closes close the connection
  defer c.Close()
  done := make(chan struct{})
  go func() {
    defer close(done)
    for {
      _, message, err := c.ReadMessage()
      if err != nil {
        log.Println("read:", err)
        return
      }
      log.Printf("recv: %s", message)
      if string(message) == "Connected"{
        log.Printf("Send Sub Details: %s", message)
        messageOut <- "{"userKey":"YOUR_API_KEY", "symbol":"EURUSD"}"
      }
    }

  }()

  ticker := time.NewTicker(time.Second)
  defer ticker.Stop()
  for {
    select {
    case <-done:
      return
    case m := <-messageOut:
      log.Printf("Send Message %s", m)
      err := c.WriteMessage(websocket.TextMessage, []byte(m))
      if err != nil {
        log.Println("write:", err)
        return
      }
    case t := <-ticker.C:
      err := c.WriteMessage(websocket.TextMessage, []byte(t.String()))
      if err != nil {
        log.Println("write:", err)
        return
      }
    case <-interrupt:
      log.Println("interrupt")
      // Cleanly close the connection by sending a close message and then
      // waiting (with timeout) for the server to close the connection.
      err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
      if err != nil {
        log.Println("write close:", err)
        return
      }
      select {
      case <-done:
      case <-time.After(time.Second):
      }
      return
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

TraderMade provides reliable and accurate Forex data via Forex API. You can sign up for a free API key and start exploring real-time and historical data at your fingertips.

Also, go through our other tutorials:
How To Write Your First Golang WebSocket Client

Your First Golang REST API Client

Your First PHP WebSocket Client

Python Development Kit for Forex and CFDs

Top comments (0)