A shared-memory chat program for Linux users on the same machine. No server, no network, no dependencies beyond libc.
chat is a single C file with no external dependencies. Compile with GCC:
$ gcc -Wall -o chat chat.c
This produces a chat binary in the current directory.
Place it anywhere on your PATH if you like.
Open a terminal and run chat. The first instance creates the
shared memory segment. Subsequent users on the same machine simply run
chat in their own terminals and connect automatically.
$ ./chat
chat: joined as "alice" (slot 0). Ctrl-C or Ctrl-D to quit.
Your Linux username becomes your chat ID. If you open a second session while the first is still running, the duplicate gets your username suffixed with its PID so identities stay unique:
chat: joined as "alice_28491" (slot 1). Ctrl-C or Ctrl-D to quit.
Just type. Every keystroke is immediately pushed into the shared queue and echoed back through it, so all participants see characters appear in real time. When the sender changes, a header line is printed so you know who is typing:
alice:
hey bob, are you around?
bob:
yeah! one sec
The sender name only appears when the active writer switches, so consecutive messages from the same person are not cluttered with repeated headers.
chat monitors the user-slot table each iteration and detects when participants join or leave. Changes are announced inline:
*** bob has joined the chat ***
bob:
hey everyone
*** bob has left the chat ***
This works regardless of which version of the chat program each participant is running, as long as they share the same shared memory layout.
You can paste multi-line content (scripts, logs, etc.) directly into the terminal. chat reads up to 65,535 bytes at a time, so even large pastes go through cleanly. The putq history mechanism means other users' earlier messages stay recoverable from older queue snapshots as long as the paste fits within the 256 KB circular buffer.
When a new user connects, chat scans the four putq index slots for the oldest valid one and starts reading from there. This means a late joiner sees some of the recent conversation history rather than an empty screen.
Press Ctrl-C or Ctrl-D to leave. Your terminal is restored to its normal mode and your user slot is freed. When the last user exits, the shared memory segment is automatically removed.
/tmp/.chat11.lock (created automatically, mode 0660)fcntl file locking, and POSIX terminal
control (termios), so all participants must be on the same
Linux machine. It does not work across a network or on other operating systems.
The first chat process creates a SysV shared memory segment
(key 0x43483131) containing a fixed-size header followed by
the circular queue:
All indices into the queue are masked to 18 bits (0 – 262,143).
A separate putq_valid byte array tracks which putq entries
hold genuine positions rather than uninitialised zeros.
When a user types, the input handler first shifts the putq history
— putq[0] ← putq[1] ← putq[2] ← putq[3] —
along with their corresponding valid flags, preserving three prior write
positions. It then writes two NUL-terminated strings into the queue via
putQ: the sender's chat ID, followed by the message text.
For each byte written, putQ checks whether advancing the
write pointer would collide with any active reader's getq index.
If a reader is about to be overrun, its getq is nudged forward by one
position so it never points at stale, about-to-be-overwritten data.
After writing, putq_valid[3] is set to mark the new
write position as genuine.
Each iteration, the process checks whether the queue has data for its
slot. If so, stdout is added to the select(2) write set;
output only occurs when select confirms stdout is writable.
When no data is pending, stdout is excluded from the write set so
select truly sleeps on the 50 ms timeout rather than
spinning.
getQ pulls one byte at a time from the queue. A three-state
parser (STATE_IDLE → STATE_ID →
STATE_TEXT) separates the sender ID from the message body.
The sender header is only printed when the active writer changes.
chat places the terminal in raw mode with echo disabled
(ICANON, ECHO, and ISIG all off).
This means keystrokes are delivered immediately rather than being
line-buffered, and the program handles display directly. A few
translations are applied on output to compensate:
Enter arrives as \r (since ICRNL
is disabled) and is printed as \r\n.
Backspace arrives as 0x08 (BS) or 0x7F (DEL) and is
rendered as the three-byte sequence \b \b to visually
erase the previous character. Ctrl-C and
Ctrl-D are detected as literal bytes
(0x03, 0x04) and trigger a clean exit.
Each chat process runs a single-threaded select(2) loop.
Reading is lock-free : each user has their own getq pointer that only
they advance. Writing, however, requires mutual exclusion because
multiple processes may try to update putq[3] at the same time.
Before writing, a process acquires an exclusive fcntl(F_SETLKW)
lock on a shared lock file (/tmp/.chat11.lock). This serialises
the putq-history shift, the putQ writes, and the valid-flag
update into a single atomic section. The lock is released immediately
after. The lock file is created with mode 0660 to match
the shared memory permissions.
Each iteration, after select(2) returns, the process takes a
snapshot of the user-slot table and compares it against the previous
snapshot. New chat IDs trigger a "has joined the chat" message;
disappeared IDs trigger "has left the chat." The process never
announces itself.
On exit, a process clears its user slot and checks whether any slots
remain occupied. If all are empty, it calls shmctl(IPC_RMID)
to remove the shared memory segment. The lock file descriptor is closed.
This means no manual cleanup with ipcrm is needed under
normal operation.