This repository contains the source code for a chat application built in C, using a hybrid client-server and peer-to-peer (P2P) model. It uses libuv for asynchronous networking and nanopb for data serialization via Protocol Buffers.
The system consists of two main components:
-
Discovery Server: A central server that manages a list of connected clients and introduces them to each other.
-
Client: A command-line application that connects to the discovery server, requests to "greet" other clients, and then establishes direct UDP-based P2P communication with them.
-
The
serverapplication is launched and listens for TCP connections on a specified port (default24744, defined inserver/src/settings.h). -
A
clientapplication starts. The user types/connectto establish a TCP connection with the discovery server (client/src/command_handlers.c). -
Upon connection, the server sends the client a "Message of the Day" (MOTD) (
server/src/network.c) and the client requests a list of other connected users (client/src/network.c). -
To initiate a chat, both clients must mutually "greet" each other.
-
Client 1 (e.g., with ID 1) types
/greet 2to show interest in Client 2 (client/src/command_handlers.c). -
Client 2 (with ID 2) must also type
/greet 1.
-
-
When the server receives this mutual greet request (
server/src/network_handlers.c), it acts as an introducer. It sends agreet_establishedmessage to both Client 1 and Client 2. This message contains the IP address and the dedicated UDP "beacon port" (default24745) of the other party (server/src/network.c,client/src/settings.h). -
Once this introduction is complete, both clients add each other to their internal
peer_list(client/src/network_handlers.c). -
From this point, any message typed into the client (without a
/prefix) is broadcast directly to all peers in itspeer_listvia UDP (C2C), bypassing the server entirely (client/src/command_handlers.c,client/src/network.c). -
A keepalive mechanism (
client/src/keepalive.c) periodically sends UDP packets to peers to maintain the connection.
-
Language: C
-
Core Library:
libuv(for asynchronous I/O, event loop, TCP, UDP, TTY, and timers). -
Serialization: Google Protocol Buffers, using the
nanopbC implementation. -
Protocols:
-
C2D (Client-to-Discovery): TCP-based. Used for connecting, sending greet requests, and asking for the client list.
-
D2C (Discovery-to-Client): TCP-based. Used for sending MOTD, client lists, and connection introductions.
-
C2C (Client-to-Client): UDP-based. Used for direct peer-to-peer chat messages and keepalives.
-
Protocol Buffers are used to define the message structures. The definitions are located in the proto/ directory:
-
common.proto: Defines thecommon.clientmessage. -
c2d_packets.proto: (Client -> Server) Definesgreet_packetandclient_list_request_packet. -
d2c_packets.proto: (Server -> Client) Definesmotd_packet,client_list_packet, andgreet_established_packet. -
c2c_packets.proto: (Client <-> Client) Definesmessage_packetandkeepalive_packet.
The repository uses a Makefile for building.
Dependencies:
-
gcc(or compatible C compiler) -
make -
protoc(Protocol Buffer compiler) -
nanopb(protoc plugin and C library) -
libuv(development library, e.g.,libuv1-devon Debian/Ubuntu)
Steps:
-
Generate Protocol Files:
make proto
This will use
protocand thenanopbplugin to convert the.protofiles into C source (.pb.c) and header (.pb.h) files in thecommon/src/generated/directory. -
Build Server and Client:
make all
This will compile and link all source files, creating two executables in the
build/directory:-
build/server -
build/client
-
-
Clean Build Artifacts:
make clean
-
Start the Server: Open a terminal and run the server executable.
./build/server
You should see a log message:
Listening on 0.0.0.0:24744 -
Start Client 1: Open a new terminal.
./build/client
-
Start Client 2: Open a third terminal.
./build/client
-
Connect Clients:
-
In Client 1's terminal, type:
/connectYou will see the server's MOTD and a list of connected clients (which will be empty or just you). The server will log your connection and assign you ID 1.
-
In Client 2's terminal, type:
/connectYou will see the server's MOTD. The server will assign you ID 2. Both clients will now see an updated client list showing IDs 1 and 2.
-
-
Establish P2P Chat:
-
In Client 1's terminal, greet Client 2:
/greet 2 -
In Client 2's terminal, greet Client 1:
/greet 1 -
Once both greets are registered, the server will send the P2P connection details to both clients. You will see a log message in each client:
Client at [IP]:[Port] has accepted our greeting.
-
-
Chat: Now, any text you type in either client terminal (without the
/prefix) and press Enter will be sent directly to the other client via UDP.