How to Use

Building

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.

Starting a session

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.

Chatting

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:

Terminal — what you see
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.

Join & leave notifications

chat monitors the user-slot table each iteration and detects when participants join or leave. Changes are announced inline:

Terminal — presence notifications
*** 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.

Pasting text

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.

Late joiners

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.

Exiting

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.

Quick reference

Max users
8 simultaneous sessions
Buffer size
262,144 bytes (256 KB circular queue)
Poll interval
50 ms (select timeout)
Exit keys
Ctrl-C or Ctrl-D
Requirements
Linux (SysV shared memory, fcntl locking, raw termios)
Lock file
/tmp/.chat11.lock (created automatically, mode 0660)
Note: chat requires Linux. It relies on SysV shared memory, 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.

Download

chat.c Single file, ~618 lines, no dependencies
◆ ◆ ◆

How It Works

Shared memory layout

The first chat process creates a SysV shared memory segment (key 0x43483131) containing a fixed-size header followed by the circular queue:

Offset Size Contents ────── ───────── ────────────────────────────────── 0x0000 16 bytes putq[0..3] — four uint32_t write-position snapshots 0x0010 4 bytes putq_valid[0..3] — four uint8_t valid flags (one per putq) 0x0014 544 bytes users[0..7] — eight 68-byte slots (64-char id + uint32_t getq) 0x0234 256 KB queue[] — circular character buffer (2^18 bytes)

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.

Writing: the putQ routine

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.

Reading: the getQ routine and display parser

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_IDLESTATE_IDSTATE_TEXT) separates the sender ID from the message body. The sender header is only printed when the active writer changes.

Raw terminal mode

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.

Concurrency: file locking

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.

Join & leave detection

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.

Cleanup

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.