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
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.