Server-Side Request Handling File Open, Data Chunks, Error & End Messages

 

Server-Side Request Handling
serveRequest(): File Open, Data Chunks, Error & End Messages
Chapter 46 · TLPI · EmbeddedPathashala
Part
3 of 4
Topic
Server Logic
Key Calls
msgsnd, read, open
Msg Types
FAILURE/DATA/END

What This Part Covers

Once the server forks a child process, the child calls serveRequest(). This function is the heart of the file-serving logic. It opens the requested file, sends its contents in chunks as RESP_MT_DATA messages, and signals the end of transfer with a RESP_MT_END message. If the file cannot be opened, it sends a RESP_MT_FAILURE message.

This part walks through every step of that logic, explains the design choices, and shows what the client-side sees in response.

Key Terms

serveRequest() open() O_RDONLY read() loop msgsnd() RESP_MT_FAILURE RESP_MT_DATA RESP_MT_END zero-length mtext req->clientId snprintf() exit(EXIT_FAILURE)

What serveRequest() Does — Overview

serveRequest() receives a pointer to the requestMsg struct that the parent read from the server queue. From this, it gets two things:

  • req->pathname — the file to open and serve
  • req->clientId — the message queue ID to send responses to

The function follows this decision tree:

open(req->pathname, O_RDONLY)

open() == -1 (FAIL)
Send RESP_MT_FAILURE
mtype = RESP_MT_FAILURE
data = “Couldn’t open”
exit(EXIT_FAILURE)
open() OK
Loop: read() chunks
Send each as RESP_MT_DATA
Send RESP_MT_END
zero-length mtext

Error Path — RESP_MT_FAILURE

If open() fails (file not found, permission denied, etc.), the server child sends a single RESP_MT_FAILURE message with a short error description in the data field, then calls exit(EXIT_FAILURE).

fd = open(req->pathname, O_RDONLY);
if (fd == -1) {
    resp.mtype = RESP_MT_FAILURE;
    snprintf(resp.data, sizeof(resp.data),
             "%s", "Couldn't open");

    /* Send error text to client.
       Size = strlen(data) + 1 to include the null terminator. */
    msgsnd(req->clientId, &resp,
           strlen(resp.data) + 1, 0);

    exit(EXIT_FAILURE); /* child exits */
}
Field Value Why
resp.mtype RESP_MT_FAILURE Client checks this type first to detect errors
resp.data “Couldn’t open” Human-readable error text the client can print
size arg to msgsnd strlen(data) + 1 Include null terminator so client can print as C string
Why use exit() here instead of _exit()? The server child is not expected to do any more work after this failure. TLPI uses exit(EXIT_FAILURE) here. The key insight from the book is that in a real robust server you might want stdio flushing for error logging — the child’s exit path is different from the normal “work done” path.

Success Path — Sending File Data as RESP_MT_DATA

If the file opens successfully, the server reads it in chunks of RESP_MSG_SIZE bytes. Each chunk is sent as a separate message with mtype = RESP_MT_DATA.

resp.mtype = RESP_MT_DATA;

while ((numRead = read(fd, resp.data, RESP_MSG_SIZE)) > 0)
    if (msgsnd(req->clientId, &resp, numRead, 0) == -1)
        break; /* can't notify client — just stop */

/* Signal end of file with a zero-length mtext message */
resp.mtype = RESP_MT_END;
msgsnd(req->clientId, &resp, 0, 0);

Key points in this loop:

Detail Explanation
read(fd, resp.data, RESP_MSG_SIZE) Reads up to RESP_MSG_SIZE bytes into the message data buffer. May return less than RESP_MSG_SIZE at end of file or for special files.
msgsnd(..., numRead, 0) The size is the actual number of bytes read — not RESP_MSG_SIZE. This avoids sending garbage padding bytes.
No error handling on failure If msgsnd() fails mid-transfer, there is no way to notify the client (no reverse channel at this point). The server just stops. The client will eventually time out or get stuck — a known limitation discussed in Exercise 46-5.

The End-of-File Marker — RESP_MT_END

After all data messages are sent, the server sends one final message with mtype = RESP_MT_END and a zero-length data field. This is the signal to the client that the entire file has been transferred.

resp.mtype = RESP_MT_END;
msgsnd(req->clientId, &resp, 0, 0);
/*                            ^
                              zero-length mtext */

Sequence of messages the client receives for a 20KB file (RESP_MSG_SIZE = 8192):

# mtype data size Meaning
1 RESP_MT_DATA (2) 8192 bytes First 8KB of file
2 RESP_MT_DATA (2) 8192 bytes Next 8KB of file
3 RESP_MT_DATA (2) 3616 bytes Remaining bytes
4 RESP_MT_END (3) 0 bytes Transfer complete
Design insight: Using a sentinel message type (RESP_MT_END) instead of a length-prefix or special data value is clean and simple. The client does not need to know the file size in advance — it just keeps receiving until it sees RESP_MT_END.

Code Example — Complete serveRequest() Function
#include <fcntl.h>
#include <unistd.h>
#include <sys/msg.h>
#include <string.h>
#include "svmsg_file.h"

static void
serveRequest(const struct requestMsg *req)
{
    int fd;
    ssize_t numRead;
    struct responseMsg resp;

    /* Try to open the file the client requested */
    fd = open(req->pathname, O_RDONLY);
    if (fd == -1) {

        /* Tell client we couldn't open it */
        resp.mtype = RESP_MT_FAILURE;
        snprintf(resp.data, sizeof(resp.data),
                 "%s", "Couldn't open");
        msgsnd(req->clientId, &resp,
               strlen(resp.data) + 1, 0);
        exit(EXIT_FAILURE);
    }

    /* File opened OK — stream contents to client */
    resp.mtype = RESP_MT_DATA;
    while ((numRead = read(fd, resp.data, RESP_MSG_SIZE)) > 0)
        if (msgsnd(req->clientId, &resp, numRead, 0) == -1)
            break;

    /* Send end-of-file marker */
    resp.mtype = RESP_MT_END;
    msgsnd(req->clientId, &resp, 0, 0);
}

Why Doesn’t the Server Handle read() or msgsnd() Errors During Transfer?

This is a subtle design decision. Once the server has successfully opened the file and started sending data, if read() or msgsnd() fails:

  • The server has no second channel to notify the client that an error occurred mid-transfer.
  • The only communication channel is the client’s message queue — and if msgsnd() is failing, that channel itself is broken.
  • Adding complex error recovery would require a separate signaling mechanism.

The book acknowledges this limitation and suggests improvements in Exercise 46-4 (server improvements) and Exercise 46-5 (client improvements).

Real-world lesson: In production code you would add timeout-based detection on the client side (if RESP_MT_END does not arrive within N seconds, assume failure), or use a separate error notification mechanism.

Message Size Argument to msgsnd() and msgrcv()

The msgsnd() and msgrcv() size arguments refer only to the data part of the message — the mtype field is never counted:

struct responseMsg {
    long mtype;          /* NOT counted in size argument */
    char data[8192];     /* THIS is what the size refers to */
};

/* Sending actual read bytes (not the full buffer): */
msgsnd(clientId, &resp, numRead, 0);

/* Sending zero-length end marker: */
msgsnd(clientId, &resp, 0, 0);

/* Receiving — use max possible size: */
ssize_t n = msgrcv(clientId, &resp,
                    sizeof(resp.data), 0, 0);
/* n == 0 means RESP_MT_END was received (zero data) */
When msgrcv() returns 0 (zero bytes of data), it means a zero-length message was received. In this application that is the RESP_MT_END signal. However, the client actually checks mtype to distinguish DATA from END rather than relying on size alone.

Summary of This Part
serveRequest() function flow RESP_MT_FAILURE path RESP_MT_DATA read/send loop RESP_MT_END zero-length marker msgsnd size excludes mtype Why errors during transfer are not handled

Interview Questions — Server Request Handling
  1. What are the three response message types the server sends and in what situations?
  2. Why does msgsnd() use numRead as the size argument instead of RESP_MSG_SIZE?
  3. What does a zero-length msgsnd() call signify in this application?
  4. The server does not handle read() errors during file transfer. Why? What are the consequences?
  5. How does the client know when the file transfer is complete?
  6. The mtype field is never included in the size argument to msgsnd(). Why?
  7. Why does the server child call exit(EXIT_FAILURE) after sending the failure message?
  8. What would happen if the server sent all data in one huge message instead of multiple chunks?
  9. In serveRequest(), what determines how many messages are sent for a given file?
  10. If the client’s queue is full when the server tries to send a data message, what happens by default?

Continue Learning

Next: Client-Side Design — IPC_PRIVATE, atexit(), receiving responses

Next Part → ← Previous Part

Leave a Reply

Your email address will not be published. Required fields are marked *