Luigi
Published on

Socket programming (using Go)

What is a socket?

A socket is that thing with 3 holes on your wall. :)

On a serious note, a socket is a standard way to perform communication through a computer operating system. Sockets are normally used to receive or send data over a specified network. The data is normally transmitted at the session layer of the OSI model.

There are two common types of sockets:

  • Stream sockets
  • Datagram sockets

Stream sockets

Stream packets provide a two way communication similar to when you are talking to someone on phone i.e, it is a full-duplex communication. Data flows to and fro between the (atleast) two parties. Stream sockets use the Transmission Control Protocol (TCP) to relay data in chunks called packets in sequence and without errors. Computer servers and their clients use this kind of sockets to communicate.

Datagram sockets

Unlike in stream sockets, communication in datagram sockets is one way. For example in walkie-talkies. Only one party can use the communication channel to relay whatever message they have. Therefore, the communication is termed as half-duplex Datagram sockets use the User Datagram Protocol (UDP) which is lightweight amd highly customizable.

In this article, we will focus on a subset of stream sockets called web sockets.

Working with web sockets in Go

While dealing with sockets, there are two main operations that you will perform, i.e reading from a source (e.g a mail server) and writing to a destination (e.g a client).

Go has a sub-directory package called golang.org/x/net/websocket. It's documentation states:

This package currently lacks some features found in an alternative and actively maintained Websockets package: https://pkg.go.dev/github.com/gorilla/websocket.

For the above matter, we shall use the suggested package.

Writing a web socket server and client

  1. Create a directory for your code, initialize a Go project, create files and initialize VCS using Git
$ cd ~ && mkdir websocket\
  && cd websocket\
  && go mod init github.com/your-name/websocket\
  && touch server.go client.go Makefile\
  && git init
  1. In your server.go file
package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
}

// HTTP handler
func Handler(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Handling /")

    conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		fmt.Println(err)
		return
	}

    for n := 0; n < 10; n++ {
		msg := "Hello" + string(n + 48)
		fmt.Println("Sending to client: " + msg)
		err = conn.WriteMessage(websocket.TextMessage,
			[]byte(msg))
		_, reply, err := conn.ReadMessage()
		if err != nil {
			fmt.Println("Can't receive")
			break
		}
		fmt.Println("Received back from client: " +
			string(reply[:]))
	}

	conn.Close()
}

func main() {
	http.HandleFunc("/", Handler)
	err := http.ListenAndServe("localhost:5000", nil)
	checkError(err)
}

func checkError(err error) {
	if err != nil {
		log.Fatalln("Fatal error ", err.Error())
	}
}

  1. Create a client in your client.go file
package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"

	"github.com/gorilla/websocket"
)

func main() {
	if len(os.Args) != 2 {
		log.Fatalln("Usage: ", os.Args[0], "ws://host:port")
	}

	service := os.Args[1]
	header := make(http.Header)
	header.Add("Origin", "http://localhost:5000")
	conn, _, err := websocket.DefaultDialer.Dial(service, header)
	checkError(err)

	for {
		_, reply, err := conn.ReadMessage()
		if err != nil {
			if err == io.EOF {
				// graceful shutdown by server
				fmt.Println(`EOF from server`)
				break
			}
			if websocket.IsCloseError(err,
				websocket.CloseAbnormalClosure) {
				fmt.Println(`Close from server`)
				break
			}
			fmt.Println("Couldn't receive msg " +
				err.Error())
			break
		}
		fmt.Println("Received from server: " +
			string(reply[:]))
		// return the msg
		err = conn.WriteMessage(websocket.TextMessage, reply)
		if err != nil {
			fmt.Println("Couldn't return msg")
			break
		}
	}
}

func checkError(err error) {
	if err != nil {
		log.Fatalln("Fatal error ", err.Error())
	}
}

  1. For easier running of shell commands, create a Makefile
$ vi Makefile

And then, copy the following to the Makefile

server:
    go run server.go

client:
    go run client.go
  1. Testing

In your terminal, make client ws://localhost:5000

Received from server: Hello  0

Received from server: Hello  9

Close from server

Back on the server, we see

Handling /

Sending to client: Hello  0

Received back from client: Hello  0

Sending to client: Hello  1

Sending to client: Hello  9

Received back from client: Hello  9

Resources

  • Network Programming with Go Language - Jan Newmarch Ronald Petty
  • Network Automation with Go - Nicolas Leiva, Michael Kashin

Let me know what your thoughts are about this article hi[at]luigimorel.com. Cheers!

© 2022 - 2024 Luigi Morel. All rights reserved.