In this project, we built a simplified version of the classic NetCat ("nc") tool — a TCP-based chat server that allows multiple clients to connect, send messages, and interact in real time.
The goal was not just to recreate a chat system, but to deeply understand:
TCP networking
Go concurrency (goroutines & channels)
State management in concurrent systems
Client-server architecture
At its core, the system needed to:
Accept multiple client connections
Allow clients to send messages
Broadcast messages to other clients
Track when users join or leave
Handle unexpected disconnects (like Ctrl+C)
This introduces a key challenge:
«Multiple clients interacting with shared state at the same time.»
TCP Server Basics
The server listens for incoming connections using:
listener, _ := net.Listen("tcp", ":8989")
Then continuously accepts clients:
for
conn, _ := listener.Accept()
go handleConnection(conn)
Important concept:
"Accept()" blocks until a client connects
Each client is handled in a separate goroutine
This allows multiple users to connect simultaneously.
Goroutines and Concurrency
Each client runs in its own goroutine:
go handleConnection(conn)
This means:
One slow client does not block others
Each connection is handled independently
However, this introduces a problem:
«Multiple goroutines modifying shared data can cause race conditions.»
The Shared State Problem
We needed to track all connected clients:
var connections map[net.Conn]string
But multiple goroutines might:
Add clients
Remove clients
Broadcast messages
At the same time.
This can cause:
Data corruption
Crashes ("fatal error: concurrent map writes")
Solution 1: Mutex
One approach is using a mutex:
mu.Lock()
connections[conn] = name
mu.Unlock()
But this introduces:
Complexity
Potential deadlocks
Performance bottlenecks
Solution 2: Channels (The Go Way)
Instead of sharing memory, we used channels to communicate changes.
This follows Go’s philosophy:
«“Do not communicate by sharing memory; share memory by communicating.”»
ChatRoom Architecture
We designed a "ChatRoom" struct:
type ChatRoom struct
chatters map[*Client]struct
history []string
Register chan *Client
Unregister chan *Client
Broadcast chan Message
Only one goroutine manages "chatters" and "history"
Other goroutines send events via channels
The Event Loop
The core of the system is the "Run()" method:
for
select
case client := <-Register:
case client := <-Unregister:
case msg := <-Broadcast:
This acts like a central controller.
Handling Events
1. Client Join
Ask for name
Add to chatters map
Send chat history
Broadcast join message
cr.chatters[client] = struct
2. Message Broadcast
Append to history
Send to all clients except sender
for c := range cr.chatters
if c != sender
c.receive <- message
3. Client Leave
Remove from map
Close channel
Broadcast leave message
Handling Ctrl+C (Unexpected Disconnects)
When a client presses Ctrl+C:
TCP connection closes
"ReadString()" returns "io.EOF"
We detect this:
if err != nil
// client disconnected
And broadcast:
"%s has left the chat"
Message Flow
Here’s how a message travels:
Client sends message
Goroutine reads it
Sends it to "Broadcast" channel
"Run()" receives it
Loops through clients
Sends message to each client’s "receive" channel
Client writer goroutine prints it
Main Concepts were:
1. TCP is Just a Stream
Everything is bytes
Messages are manually structured ("\n")
_
Blocking is Normal_
"Accept()" blocks waiting for connections
"ReadString()" blocks waiting for input
But only within their goroutine.
3. Goroutines Enable Concurrency
Lightweight threads managed by Go
Thousands can run efficiently
4. Channels Simplify Concurrency
Avoid shared memory issues
Centralize state management
Create predictable flow
Challenges Faced
Handling empty messages
Debugging raw string issues
Understanding blocking behavior
Managing client disconnects
Avoiding race conditions
This project goes beyond just building a chat app.
It revealed:
How real-time systems work
How servers handle multiple users
How to design safe concurrent programs
In many ways, this is a mini version of real-world systems like chat apps, multiplayer servers, and messaging platforms.
Building this TCP chat server helped me understand how powerful Go is for concurrent systems.
By combining:
TCP networking
Goroutines
Channels
we can build scalable, real-time applications with relatively simple code.
Tags:
#0
Want to run a more efficient business?
Mewayz gives you CRM, HR, Accounting, Projects & eCommerce — all in one workspace. 14-day free trial, no credit card needed.
Try Mewayz Free →