Creating a Simple Chat Server in Go

Table of Contents

  1. Introduction
  2. Prerequisites
  3. Setting Up the Chat Server
  4. Creating the Server
  5. Handling Connections
  6. Broadcasting Messages
  7. Testing the Chat Server
  8. Conclusion

Introduction

In this tutorial, we will learn how to create a simple chat server using Go (or Golang). By the end of this tutorial, you will be able to build a basic chat server that allows multiple clients to connect and exchange messages in real-time.

Prerequisites

Before getting started, make sure you have the following prerequisites:

  • Basic understanding of Go programming language.
  • Go installed on your machine.
  • Familiarity with TCP/IP networking concepts.

Setting Up the Chat Server

To begin, let’s create a new directory for our chat server project. Open your terminal or command prompt and run the following command:

mkdir chat-server

Navigate to the newly created directory:

cd chat-server

Creating the Server

Now let’s create the main file for our chat server. In the chat-server directory, create a new file called main.go and open it in a text editor.

Add the following code to import the necessary packages and define the main function:

package main

import (
    "fmt"
    "log"
    "net"
)

func main() {
    // Server code goes here
}

Inside the main function, we will create a TCP server that listens for incoming client connections. Add the following code to create a server listening on a specific port (e.g., 9000):

func main() {
    // Create server
    ln, err := net.Listen("tcp", ":9000")
    if err != nil {
        log.Fatal(err)
    }
    defer ln.Close()

    fmt.Println("Chat server started on port 9000")
    
    // Server code continues here
}

The net.Listen function creates a new TCP listener on the specified port. If there’s an error, we log it and exit the program. The defer ln.Close() statement ensures that the listener is properly closed when the server exits.

Handling Connections

Next, we need to handle incoming client connections. Modify the main function as follows:

func main() {
    // Create server
    ln, err := net.Listen("tcp", ":9000")
    if err != nil {
        log.Fatal(err)
    }
    defer ln.Close()

    fmt.Println("Chat server started on port 9000")

    // Handle connections
    for {
        conn, err := ln.Accept()
        if err != nil {
            log.Println(err)
            continue
        }
        go handleConnection(conn)
    }
}

func handleConnection(conn net.Conn) {
    // Handle individual client connection
}

Within the for loop, we call ln.Accept() to accept incoming client connections. If there’s an error accepting a connection, we log it and continue to the next iteration of the loop.

For each successful connection, we spawn a new goroutine (using go) and pass the connection to the handleConnection function. Goroutines allow us to handle multiple client connections concurrently.

Broadcasting Messages

Now let’s implement the functionality to broadcast messages from one client to all connected clients. Update the handleConnection function as follows:

func handleConnection(conn net.Conn) {
    defer conn.Close()

    // Add client to connected clients list
    // ...

    // Read and broadcast messages
    for {
        // Read message from client
        // ...

        // Broadcast message to connected clients
        // ...
    }
}

In the handleConnection function, we defer closing the connection to ensure it is closed when the function returns.

Inside the loop, we will read messages from the client and broadcast them to all connected clients. Add the following code to read messages:

scanner := bufio.NewScanner(conn)
for scanner.Scan() {
    message := scanner.Text()
    if message == "/quit" {
        break
    }
    // Broadcast message to connected clients
    // ...
}

if err := scanner.Err(); err != nil {
    log.Println(err)
}

We use a bufio.Scanner to read messages line by line from the client. If the client sends “/quit”, we exit the loop and close the connection.

To broadcast messages, we need to maintain a list of all connected clients. Add the following code before the message reading loop:

clients := make(map[net.Conn]bool)

for client := range clients {
    _, err := client.Write([]byte("New client joined"))
    if err != nil {
        log.Println(err)
        client.Close()
        delete(clients, client)
    }
}

clients[conn] = true

The clients variable is a map with net.Conn as the key and a boolean value. It keeps track of whether a client is still connected or not.

In the loop, we send a message to all currently connected clients to notify them about the new client.

To broadcast messages, add the following code after the message reading loop:

for client := range clients {
    _, err = client.Write([]byte(message))
    if err != nil {
        log.Println(err)
        client.Close()
        delete(clients, client)
    }
}

This loop sends the received message to all connected clients, excluding the client who sent the message.

Testing the Chat Server

To test the chat server, let’s create a simple client program that connects to the server and allows us to send messages.

Create a new file called client.go in the chat-server directory and add the following code:

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "os"
)

func main() {
    conn, err := net.Dial("tcp", "localhost:9000")
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    scanner := bufio.NewScanner(os.Stdin)
    for scanner.Scan() {
        message := scanner.Text()
        _, err := fmt.Fprintf(conn, "%s\n", message)
        if err != nil {
            log.Println(err)
            break
        }
        if message == "/quit" {
            break
        }
    }

    if err := scanner.Err(); err != nil {
        log.Println(err)
    }
}

This client program connects to the chat server running on localhost:9000. It provides a console interface to send messages to the server. The loop reads input from the user, sends it to the server, and exits when “/quit” is entered.

Open two terminal windows and run the chat server in one window:

go run main.go

In the other window, run the client program:

go run client.go

Type messages in the client window and press Enter to send them to the server. The server should broadcast the messages to all connected clients, including the client that sent the message.

Conclusion

Congratulations! You have successfully created a simple chat server in Go. This server allows multiple clients to connect and exchange messages in real-time. You have learned how to handle connections, read and broadcast messages, and test the chat server using a simple client program.

Feel free to explore further and extend the functionality of the chat server. You can add username support, implement a chat room system, or even secure the communication using encryption.

Remember to always handle errors properly, add necessary error checking, and consider the performance and scalability aspects of your application.