...

Master Group Text Chat In Go: A Simple and Practical Guide in Just 3 Steps

Rate this post

Introduction

Group text chat in Go involves creating a real-time communication system that enables multiple users to exchange messages within a shared environment. Leveraging Go’s robust concurrency and networking capabilities, developers can design and implement efficient group chat applications.

The process typically involves establishing communication channels between users, enabling message transmission and reception in a synchronized manner. Go’s concurrent nature allows simultaneous handling of multiple connections, ensuring seamless interactions among participants.

Developing a group text chat in Go requires considerations for user authentication, message routing, data synchronization, and scalability. Go’s strong support for concurrent operations and its standard library’s networking features streamline the creation of responsive, scalable, and reliable chat systems.

Utilizing Go’s features like goroutines, channels, and net package, developers can craft responsive and scalable group chat applications that facilitate real-time communication among multiple users, illustrating Go’s prowess in building robust and efficient networking solutions.

Key takeaways from this post

  • Learn about creating Group Text Chat In Go
  • Understand the use of WebSocket in real-world applications.
  • How to write lockless (free) programs.
  • How to optimize the code step by step.
  • How to start with basics and extend the solution to production.

What is a group text Chat application?

We all are familiar with whatsup groups in which one person sends some text that everyone receives. In this article, we will develop a similar application in Go (backend). Since we are developing a chat application that requires the server to send some data to the client so we will use websockets to achieve the same. You can learn websockets basics from here.

Entities of group text Chat system

  • Users
  • Groups
  • Text

Let us assume group creation, updation, and deletion are done through the rest APIs. Similarly, users joining a group or leaving a group are done through rest APIs. We will focus on the chat part only.

The Chat part of group text Chat application will consist of the following:

  • Joining the group
  • Sending texts to other group members
  • Receiving texts from other group members

Joining the group

First, a client will connect to the server over the websocket protocol. So we will store this connection in a map against the client’s remote address. Other members of the group will connect similarly and we will store their connection in the same fashion.

Note: We are adding the client’s websocket connection indirectly using the addConnection function which receives the connection’s map as a function parameter. This frees us from locking-unlocking maps manually. Instead of locking-unlocking manually, we are sending the addConnnection function to operationChan which ensures synchronous execution. This is an elegant example of lockless programming.

Note: we will delete the connection from the map whenever a client’s connection breaks or closes due to any reason because we can’t write data to a broken connection.

This also teaches us the famous Go proverb.
Don’t communicate by sharing memory, share memory by communicating.
Channels orchestrate; mutexes serialize.

Implementation of Group Text Chat in Go

Understand in detail in below code block.

Step 1 – Store clients in map

//// client connection over websocket

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

func main() {
	// start http server
	http.HandleFunc("/ws", wsHandler)

	go defaultGroupChat.executeOperations()

	log.Fatal(http.ListenAndServe(":8080", nil))
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		log.Errorln("Failed to upgrade connection:", err)
		return
	}

	// make sure to close the connection before exit
	defer conn.Close()


	defaultGroupChat.addConnection(conn)
	// remove from conns map
	defer defaultGroupChat.removeConnection(conn.RemoteAddr().String())
        

        // other code ...
}


type connsType map[string]*websocket.Conn

var (
	defaultGroupChat = &GroupChat{
		operationChan: make(chan func(connsMap connsType)),
	}
)

type GroupChat struct {
	operationChan chan func(connsMap connsType)
}

// keeps on receiving operations in the form of functions from channel
// and executes them
func (g *GroupChat) executeOperations() {
	m := make(connsType)
	for op := range g.operationChan {
		op(m)
	}
}

func (g *GroupChat) addConnection(conn *websocket.Conn) {
	g.operationChan <- func(connsMap connsType) {
		connsMap[conn.RemoteAddr().String()] = conn
	}
}

func (g *GroupChat) removeConnection(remoteAddr string) {
	g.operationChan <- func(connsMap connsType) {
		delete(connsMap, remoteAddr)
	}
}

Step 2 – Broadcasting text messages to the group

Since we have already stored the connection of the members of the group in a map, so we will iterate over the map and write the text data to other members of the group connection.

func wsHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		log.Errorln("Failed to upgrade connection:", err)
		return
	}

	// make sure to close the connection before exit
	defer conn.Close()

	defaultGroupChat.addConnection(conn)
	// remove from conns map
	defer defaultGroupChat.removeConnection(conn.RemoteAddr().String())

	for {
		messageType, messageBytes, err := conn.ReadMessage()
		if err != nil {
			log.Errorln("Failed to read message:", err)
			break
		}

		conn.SetReadDeadline(time.Now().Add(time.Second * 300))
		conn.SetWriteDeadline(time.Now().Add(time.Second * 300))

		message := string(messageBytes)
		log.Infof("Received message type %d with contents: %s\n", messageType, message)
		defaultGroupChat.broadcast(conn, message)
	}
}


// in groupchat.go
func (g *GroupChat) broadcast(senderConn *websocket.Conn, message string) {
	g.operationChan <- func(connsMap connsType) {
		for remoteAddr, conn := range connsMap {
                           // do not send to the self
			if remoteAddr == senderConn.RemoteAddr().String() {
				continue
			}
			err := conn.WriteMessage(websocket.TextMessage, []byte(message))
			if err != nil {
				log.Errorf("broadcast: send error: sender: %v, recipient: %v", senderConn.RemoteAddr().String(), remoteAddr)
				continue
			}
			log.Infof("broadcast: send success: sender: %v, recipient: %v, message: %v", senderConn.RemoteAddr().String(), remoteAddr, message)
		}
	}
}

The complete code is as below:

file -> go.mod
module grouptextchat

go 1.20

require (
	github.com/gorilla/websocket v1.5.0
	github.com/sirupsen/logrus v1.9.0
)

require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect


file -> main.go
package main

import (
	"net/http"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/gorilla/websocket"
)

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

func main() {
	// start http server
	http.HandleFunc("/ws", wsHandler)

	go defaultGroupChat.executeOperations()

	log.Fatal(http.ListenAndServe(":8080", nil))
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)

	if err != nil {
		log.Errorln("Failed to upgrade connection:", err)
		return
	}

	// make sure to close the connection before exit
	defer conn.Close()

	defaultGroupChat.addConnection(conn)
	// remove from conns map
	defer defaultGroupChat.removeConnection(conn.RemoteAddr().String())

	for {
		messageType, messageBytes, err := conn.ReadMessage()
		if err != nil {
			log.Errorln("Failed to read message:", err)
			break
		}

		conn.SetReadDeadline(time.Now().Add(time.Second * 300))
		conn.SetWriteDeadline(time.Now().Add(time.Second * 300))

		message := string(messageBytes)
		log.Infof("Received message type %d with contents: %s\n", messageType, message)
		defaultGroupChat.broadcast(conn, message)
	}
}


file -> groupchat.go
package main

import (
	"github.com/gorilla/websocket"
	log "github.com/sirupsen/logrus"
)

type connsType map[string]*websocket.Conn

var (
	defaultGroupChat = &GroupChat{
		operationChan: make(chan func(connsMap connsType)),
	}
)

type GroupChat struct {
	operationChan chan func(connsMap connsType)
}

// keeps on receiving operations in the form of functions from channel
// and executes them
func (g *GroupChat) executeOperations() {
	m := make(connsType)
	for op := range g.operationChan {
		op(m)
	}
}

func (g *GroupChat) addConnection(conn *websocket.Conn) {
	g.operationChan <- func(connsMap connsType) {
		connsMap[conn.RemoteAddr().String()] = conn
	}
}

func (g *GroupChat) removeConnection(remoteAddr string) {
	g.operationChan <- func(connsMap connsType) {
		delete(connsMap, remoteAddr)
	}
}

func (g *GroupChat) broadcast(senderConn *websocket.Conn, message string) {
	g.operationChan <- func(connsMap connsType) {
		for remoteAddr, conn := range connsMap {
			// do not send to the self
			if remoteAddr == senderConn.RemoteAddr().String() {
				continue
			}
			err := conn.WriteMessage(websocket.TextMessage, []byte(message))
			if err != nil {
				log.Errorf("broadcast: send error: sender: %v, recipient: %v", senderConn.RemoteAddr().String(), remoteAddr)
				continue
			}
			log.Infof("broadcast: send success: sender: %v, recipient: %v, message: %v", senderConn.RemoteAddr().String(), remoteAddr, message)
		}
	}
}


file -> ReadMe.md
install go
install npm
install wscat using npm -> npm install -g wscat
build -> go build
run -> ./grouptextchat

open 3 othe terminals and run wscat -c ws://localhost:8080/ws
send some message from each client and check if received or not by other clients

Github link

Step 3 – Execution

install go

install npm

install wscat using npm -> npm install -g wscat

build -> go build

run -> ./grouptextchat

open 3 other terminals and run wscat -c ws://localhost:8080/ws

send some messages from each client and check if received or not by other clients

Screenshot of a sample run

Conclusion

Group text chat in Go epitomizes the language’s ability to construct efficient, scalable, and responsive real-time communication systems. Leveraging Go’s concurrent nature, developers can create robust group chat applications that facilitate seamless interactions among multiple users.

Throughout the development process, considerations for data synchronization, user authentication, and message routing play pivotal roles in ensuring a smooth and reliable chat experience. Go’s concurrency primitives like goroutines and channels, coupled with its powerful networking capabilities, enable the creation of responsive and scalable chat solutions.

Furthermore, Go’s standard library offers comprehensive tools and functionalities that streamline the implementation of networking protocols, enhancing the development of group text chat applications. The language’s efficiency in handling concurrent tasks and managing network connections underscores its suitability for crafting high-performance, real-time communication systems, establishing Go as a proficient choice for building group text chat applications with reliability and scalability in mind.

Spread the love

Leave a Comment

Seraphinite AcceleratorOptimized by Seraphinite Accelerator
Turns on site high speed to be attractive for people and search engines.