Lab 3

Interprocess Communication

CSCE 313 · Introduction to Computer Systems

Released
Tuesday, September 22, 2026, 9:00 AM CT
Due
Monday, October 5, 2026, 11:59 PM CT

1. Objectives

By the end of this lab you will be able to:

  1. Create unnamed pipes with pipe() and use them to connect a parent to its children.
  2. Redirect a child’s standard input and output onto pipe ends with dup2(), so the program it execs talks through the pipe without knowing it.
  3. Create and open named pipes with mkfifo() and open().
  4. Explain why every unused pipe end must be closed, and show what goes wrong when one is not.
  5. Send fixed-size messages with read() and write(), and detect when one fails.

In Lab 2 the RequestChannel was handed to you, its implementation hidden. In this lab you write it — twice.

2. Background

2.1 What changed since Lab 2

The banking system is the same: one client, three servers, the same menu and the same log. What changed is underneath it.

  Lab 2 Lab 3
The channel given, hidden in LLVM IR yours, in channel.cpp
How it is built one RequestChannel class an abstract RequestChannel and two concrete ones: PipeChannel, FIFOChannel
Choosing the kind the client asks first: 1 for pipes, 2 for FIFOs
Strings in messages std::string fixed char arrays, so a whole Request is one block of bytes
A QUIT request server exits without replying server replies, then exits
Log file flag ./logging -f <file> ./logging -n <file>-f now means use FIFOs
Process-information printing Task 2 gone — no print_process_info in this lab

You no longer need clang: there is no .ll file. Plain g++ builds everything.

2.2 Unnamed pipes

pipe(fds) gives you two file descriptors: fds[0] to read and fds[1] to write. Bytes written into one come out of the other, in order.

A pipe carries data one way only. For a request to go one way and a response to come back, each connection needs two pipes. Three servers means six pipes.

A pipe has no name. The only way for another process to use it is to inherit it — so the pipes must exist before the fork(). Both processes then hold all four ends, and each closes the two it does not use.

One client-server connection built from two unnamed pipes The client is on the left and a server on the right. Pipe 1, along the top, carries requests: the client writes into pipe1 index 1 and the server reads from pipe1 index 0, which it has made its standard input. Pipe 2, along the bottom, carries responses: the server writes into pipe2 index 1, its standard output, and the client reads from pipe2 index 0. The client closes pipe1 index 0 and pipe2 index 1. The server closes pipe1 index 1 and pipe2 index 0. Each process keeps exactly one end of each pipe. client creates both pipes, then forks closes pipe1[0], pipe2[1] server after exec, sees only stdin and stdout closes pipe1[1], pipe2[0] pipe 1 write(pipe1[1]) read(pipe1[0]) dup2 onto stdin (0) requests pipe 2 write(pipe2[1]) dup2 onto stdout (1) read(pipe2[0]) responses
Figure 1 — One connection is two pipes, one each way. Each process keeps exactly one end of each pipe and closes the other. The server never learns the descriptor numbers: it was handed the pipes as its standard input and output.

Reading a pipe blocks until there is data. It returns 0 — end of file — only when every write end of that pipe is closed, in every process that holds one.

2.3 Named pipes (FIFOs)

mkfifo(name, 0600) makes a pipe that has a name in the filesystem. Any process that knows the name can open() it, related or not, and it stays on disk until someone removes it.

Opening a FIFO blocks until the other end is opened too: an open for reading waits for a writer, and an open for writing waits for a reader. That rule is what makes FIFOs easy to deadlock — see section 4.3.

Each connection again needs two FIFOs, one each way. Their names are given to you in the FIFOChannel constructor: fifo_<name>1 and fifo_<name>2, created in the directory you run the client from.

2.4 Why closing unused ends is not optional

Every server in this lab stops when it reads end-of-file. It can only ever read end-of-file if no process anywhere still holds a write end of its input pipe.

Pipe descriptors are inherited by every child forked after they were created. So if the finance server’s child keeps the write end of the logging server’s pipe, and the client dies, the logging server waits forever for a message from a process that will never send one.

We measured this on the previous reference solution, which forgot to close them: killing the client left all three servers running as orphans. Closing every unused end, all three had exited on their own two seconds later. The autograder runs exactly that experiment on your client — see section 5.4.

3. Environment setup

3.1 Accept the assignment

  1. Sign in to classroom50.org with your GitHub account.
  2. Accept Lab 3: https://classroom50.org/CSCE-313-FA26/csce-313-fa26/assignments/lab-3/accept. Your repository is created for you.
  3. Clone it:
git clone https://github.com/CSCE-313-FA26/<your-lab-3-repository>.git
cd <your-lab-3-repository>

3.2 Repository layout

Path What it is Yours to change?
channel.cpp RequestChannel, PipeChannel, FIFOChannel — all TODOs Yes
client.cpp Creates the pipes, starts the servers, opens the channels Yes
finance.cpp, logging.cpp, file.cpp The three servers — each needs its channel Yes, the channel only
channel.h The three classes’ declarations No
common.h Request, Response, RequestType, IPCType No
Makefile Builds all four programs No
tests/ The unit tests, and grade.py — the same checks the autograder runs No
storage/example_execution.txt A complete correct session
storage/example.log The log that session produced

3.3 Build and run

make
./client

The first thing the client asks is which kind of channel to use:

Choose IPC type, 1 for PIPE, 2 for FIFO:

After that it is the Lab 2 program: the maximum account number, the log file name, the allowed extensions, then the menu. storage/example_execution.txt is a whole session, recorded from the reference solution in pipe mode, and storage/example.log is the log it wrote. Your program should produce the same log in either mode.

3.4 Test your work

make test

That builds everything and runs tests/grade.py: every unit test in tests/, then complete sessions in both modes, scored exactly as the autograder scores them, out of 100. Run it often. It is much faster than pushing and waiting.

The one difference: run locally, every part uses your code, so a broken channel fails the client and server checks too. The autograder runs your client against reference servers, and your servers against a reference client, so each is graded on its own. Fix your channel first and the rest follows.

4. Walkthrough

4.1 What a message is

Request and Response now hold only numbers and fixed-size char arrays. That means a whole message is one solid block of bytes, and sending it is a single call:

cwrite((void*)&req, sizeof(Request));    // the entire Request, as raw bytes

and receiving one is the matching cread into a Request of your own. There is no parsing: the struct goes in one end and comes out the other.

A read or write that moves fewer bytes than the whole struct has failed, even if it did not return -1. A read that returns 0 means end-of-file — the other side is gone.

A Request is 1296 bytes, under the 4096 bytes Linux guarantees a pipe writes in one piece, so each message goes across whole.

4.2 Pipe mode

The client builds all six pipes before the first fork(), inside the if (ipc == PIPE) block the starter already has. In each child, before exec:

After the exec, the server knows nothing about pipes. It just reads requests from STDIN_FILENO and writes responses to STDOUT_FILENO:

channel = new PipeChannel(STDIN_FILENO, STDOUT_FILENO);

Back in the parent, once all three servers are started, close the child-side ends (each server’s request read end and response write end), then make one PipeChannel per server from the two ends the client keeps.

4.3 FIFO mode

The client does not create anything. Each side makes its own FIFOChannel with the same name, and the channel’s constructor creates and opens the two FIFOs:

  client side server side
finance FIFOChannel("finance", CLIENT_SIDE) FIFOChannel("finance", SERVER_SIDE)
logging FIFOChannel("logging", CLIENT_SIDE) FIFOChannel("logging", SERVER_SIDE)
file FIFOChannel("file", CLIENT_SIDE) FIFOChannel("file", SERVER_SIDE)

Use exactly these names. Your client is graded against reference servers that use them, and your servers against a reference client.

4.4 Limits worth knowing

5. To-do

The points match the autograder. Every task is graded by running your code.

5.1 Task 1 — RequestChannel (25 points)

In channel.cpp. Each method is one unit test in tests/, worth 5 points.

Method Must do On failure
cread(buf, n) read() up to n bytes from rfd return what read() returned: 0 at end-of-file, -1 on error
cwrite(buf, n) write() n bytes to wfd return what write() returned
send_request(req) cwrite the whole Request, then cread a whole Response return a Response whose success is false
receive_request() cread a whole Request return Request(FAILURE) — for an error and for end-of-file
send_response(resp) cwrite the whole Response return false

Use cread and cwrite inside the other three — never read or write directly.

5.2 Task 2 — PipeChannel (25 points)

Eight checks in tests/test_pipechannel.cpp, scored separately. The check that a bad descriptor makes send_request fail only earns its points once send_request also works on a good one.

5.3 Task 3 — FIFOChannel (25 points)

Eight checks in tests/test_fifochannel.cpp, scored separately.

5.4 Task 4 — The client (15 points)

In client.cpp: create the six pipes, wire each child and close its unused ends, close the parent’s, and create the channels for whichever mode was chosen. At the end, delete all three channels.

Check Points
A complete pipe-mode session produces the right output and log 3
…and afterwards every server has exited and nothing is left behind 2
A complete FIFO-mode session produces the right output and log 3
…and afterwards no server and no fifo_* file is left behind 2
Unused pipe ends are closed: kill the client mid-session, and all three servers must exit on their own within 4 seconds 5

The last check is the experiment from section 2.4. It first confirms three servers are running and a user is logged in, so a client that never starts its servers cannot pass it by default.

5.5 Task 5 — The servers (10 points)

In each of finance.cpp, logging.cpp and file.cpp, create the channel for the mode the server was started with:

Check Points
All three servers answer correctly in a complete pipe-mode session 5
All three servers answer correctly in a complete FIFO-mode session 5

6. Deliverables

Commit and push your work:

git add channel.cpp client.cpp finance.cpp logging.cpp file.cpp
git commit -m "lab 3"
git push

Only those five files are graded. channel.h, common.h, the Makefile and tests/ are replaced with the reference copies before grading, so changing them has no effect on your score. Do not commit the built programs, log files, or fifo_* files — .gitignore already excludes them.

The autograder runs on every push. Push as often as you like and read the result.

6.1 Mistakes that cost points

Symptom Cause
Every FIFO session hangs, printing nothing The two sides open the FIFOs in different orders (section 4.3)
Servers keep running after the client is killed A pipe end was left open — often another server’s, inherited by a later child
Response fields hold garbage in pipe mode A server printed to standard output, which is the response pipe
fifo_* files left behind after a clean exit The destructor does not remove the FIFOs, or the client never deletes its channels
In pipe mode, the client quits with no message the first time it uses one of the servers — at the first deposit, say That server’s standard input was not redirected. It reads the keyboard instead of its request pipe, so nothing reads that pipe, and the client’s write to it kills the client (SIGPIPE)
receive_request test fails on end-of-file 0 from cread must become FAILURE, not a default Request
Works locally, fails a FIFO check in the grader Your channel names differ from finance, logging, file

7. Getting help

Ask on Discord. Bring the exact command you ran and the exact output, not a description of it. The Canvas inbox is not monitored.

Office hours and TA contact details are in the syllabus.

Revision history

Date Change
2026-09-19 Migrated from the Google Doc to this page. Due date corrected from June 28 (a leftover from a summer offering) to Monday, October 5. GitHub Classroom replaced by classroom50. The unit tests, previously hidden, now ship in tests/ and run with make test. Removed claims that the client prints process information and that the tests must stay secret. Corrected the FIFOChannel task, which described a pipe constructor taking descriptors. Added the IPC-choice prompt, the QUIT reply, the -n log-file flag, the FIFO names and open order, the standard-output warning, the upload limit, and the orphaned-server experiment. The missing storage/example_execution.txt and storage/example.log were recorded from a real run and added to the starter. The figure was redrawn with the client and server named and each process’s closed ends shown.
2026-09-21 Section 6.1: corrected the symptom of a server whose standard input was not redirected. It does not exit at start-up; the client dies silently at its first request to that server.