Sending & Receiving Messages – free linux system programming course

 

Chapter 46 · File 3 of 6

Sending & Receiving Messages

msgsnd() · msgrcv() · Message Structure · Flags · Blocking

Once you have a queue ID, you use msgsnd() to put messages in and msgrcv() to take them out. These two calls form the core of all message queue I/O. This file explains both in detail with examples.

The Message Structure

Before calling msgsnd or msgrcv, you must define your message structure. The rule is simple: the first field must be a long called mtype. Everything after that is your payload — you design it however you want.

/* The general message template */
struct mymsg {
    long  mtype;    /* REQUIRED: message type. Must be > 0 */
    char  mtext[];  /* Payload: can be any data, any size */
};

/* Real examples — you design the payload */

/* Example 1: Simple text message */
struct TextMsg {
    long mtype;
    char text[256];
};

/* Example 2: Numeric command message */
struct CmdMsg {
    long mtype;
    int  command;
    int  value;
};

/* Example 3: Sensor data */
struct SensorMsg {
    long  mtype;       /* 1=temperature, 2=pressure, 3=humidity */
    int   sensor_id;
    float reading;
    long  timestamp;
};

/* Example 4: Zero-length payload — the type IS the message */
struct SignalMsg {
    long mtype;        /* e.g. 1=READY, 2=DONE, 3=ERROR */
    /* no payload needed — the type encodes the event */
};
Why must mtype be positive? The kernel uses mtype=0 and negative values internally for msgrcv’s type-selection logic. Your sent messages must have mtype > 0. If you pass mtype ≤ 0 to msgsnd, you get EINVAL.

Section 46.2.1 — msgsnd(): Sending Messages

#include <sys/types.h>
#include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

/* msqid — queue identifier from msgget() msgp — pointer to your message struct msgsz — size of the PAYLOAD only (NOT the full struct) = sizeof(struct YourMsg) – sizeof(long) msgflg — 0 for blocking, IPC_NOWAIT for non-blocking Returns 0 on success, -1 on error */

The msgsz Argument — Common Mistake!

The size argument is the size of the payload only — not the whole struct. You must subtract the size of mtype (which is a long = 8 bytes on 64-bit, 4 bytes on 32-bit).

struct MyMsg {
    long mtype;     /* 8 bytes on 64-bit */
    char text[100]; /* 100 bytes */
};
struct MyMsg m;

/* WRONG — includes mtype in size */
msgsnd(qid, &m, sizeof(struct MyMsg), 0);   /* sends 108 bytes — wrong! */

/* CORRECT — payload size only */
msgsnd(qid, &m, sizeof(m.text), 0);         /* sends 100 bytes — correct */

/* Also correct using offsetof trick */
msgsnd(qid, &m, sizeof(struct MyMsg) - sizeof(long), 0);

Blocking Behaviour of msgsnd()

msgsnd() blocks (waits) if the queue is full. The queue has a maximum byte limit (default 16384 bytes on most systems). If adding the message would exceed this, msgsnd blocks until space becomes available.

msgsnd() Blocking Behaviour
Situation Behaviour with msgflg=0 Behaviour with IPC_NOWAIT
Queue has space Returns 0 immediately ✓ Returns 0 immediately ✓
Queue is full Blocks until space available Returns -1, errno = EAGAIN
Queue is deleted while blocked Returns -1, errno = EIDRM N/A (not blocking)
Signal caught while blocked Returns -1, errno = EINTR N/A (not blocking)

Section 46.2.2 — msgrcv(): Receiving Messages

#include <sys/types.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t maxmsgsz, long msgtyp, int msgflg);

/* msqid — queue identifier msgp — buffer to receive the message into maxmsgsz — max payload bytes your buffer can hold msgtyp — which message type to receive (see table below) msgflg — IPC_NOWAIT, MSG_NOERROR, MSG_EXCEPT Returns: number of bytes in payload on success, -1 on error */

The msgtyp Argument — Type Selection Rules

msgtyp value Which message is returned
0 First message in the queue (FIFO, any type)
> 0 (e.g. 3) First message with mtype == 3
< 0 (e.g. -5) First message with mtype ≤ abs(-5) = 5 — lowest type first
Negative msgtyp trick: If you pass msgtyp = -N, msgrcv returns the message with the lowest type number that is ≤ N. This lets you implement simple priority: type 1 = highest priority, type 5 = lowest. Using msgtyp = -5 returns the highest-priority waiting message.

msgrcv() Flags

Flag Effect
IPC_NOWAIT Don’t block if no matching message — return -1 with errno=ENOMSG
MSG_NOERROR If message payload > maxmsgsz, silently truncate instead of failing
MSG_EXCEPT Receive first message whose type is NOT equal to msgtyp (Linux-specific)

Code Example 1 — Basic Send and Receive

/* basic_send_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

#define MAX_TEXT 128

struct Message {
    long mtype;
    char mtext[MAX_TEXT];
};

int main(void)
{
    int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);
    if (qid == -1) { perror("msgget"); return 1; }

    /* --- SENDING --- */
    struct Message out;
    out.mtype = 42;  /* type = 42 */
    strncpy(out.mtext, "System V rocks!", MAX_TEXT - 1);

    /* msgsz = payload size only = sizeof(mtext) */
    if (msgsnd(qid, &out, sizeof(out.mtext), 0) == -1) {
        perror("msgsnd");
        return 1;
    }
    printf("Sent: type=%ld, text='%s'\n", out.mtype, out.mtext);

    /* --- RECEIVING --- */
    struct Message in;
    /* msgrcv returns number of bytes copied into mtext */
    ssize_t n = msgrcv(qid, &in, sizeof(in.mtext),
                       42,   /* receive only type=42 */
                       0);   /* blocking */
    if (n == -1) {
        perror("msgrcv");
        return 1;
    }
    printf("Received: type=%ld, text='%s', bytes=%zd\n",
           in.mtype, in.mtext, n);

    msgctl(qid, IPC_RMID, NULL);
    return 0;
}

Code Example 2 — Non-Blocking Receive with EAGAIN Handling

/* nonblock_recv.c — Poll queue without blocking */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct Msg { long mtype; char text[64]; };

int main(void)
{
    int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);

    /* Send one message */
    struct Msg out = { .mtype = 1 };
    strncpy(out.text, "Non-blocking demo", sizeof(out.text)-1);
    msgsnd(qid, &out, sizeof(out.text), 0);

    struct Msg in;
    int count = 0;

    while (1) {
        /* IPC_NOWAIT: don't block — return immediately if empty */
        ssize_t n = msgrcv(qid, &in, sizeof(in.text), 0, IPC_NOWAIT);

        if (n == -1) {
            if (errno == ENOMSG) {
                /* Queue is empty — no message of requested type */
                printf("Queue empty, poll %d. Sleeping 1 second...\n", ++count);
                if (count >= 3) break;  /* stop after 3 polls */
                sleep(1);
                continue;
            }
            perror("msgrcv");
            break;
        }
        printf("Got message: type=%ld, text='%s'\n", in.mtype, in.text);
    }

    msgctl(qid, IPC_RMID, NULL);
    return 0;
}

Code Example 3 — Priority Messaging with Negative msgtyp

/* priority_msgs.c — Using negative msgtyp for priority */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

struct Msg { long mtype; char text[64]; };

int main(void)
{
    int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);

    /* Send messages with priorities: lower number = higher priority */
    struct Msg m;

    m.mtype = 3; strcpy(m.text, "Priority 3 (LOW)");
    msgsnd(qid, &m, sizeof(m.text), 0);

    m.mtype = 1; strcpy(m.text, "Priority 1 (CRITICAL)");
    msgsnd(qid, &m, sizeof(m.text), 0);

    m.mtype = 2; strcpy(m.text, "Priority 2 (HIGH)");
    msgsnd(qid, &m, sizeof(m.text), 0);

    printf("Sent: P3, P1, P2 in that order\n");
    printf("Reading with msgtyp = -10 (lowest mtype first):\n\n");

    /* Receive 3 messages — each time get the one with lowest mtype */
    for (int i = 0; i < 3; i++) {
        struct Msg rcv;
        /* msgtyp = -10: get first message where mtype <= 10, lowest first */
        msgrcv(qid, &rcv, sizeof(rcv.text), -10, 0);
        printf("  Got type=%ld: '%s'\n", rcv.mtype, rcv.text);
    }

    msgctl(qid, IPC_RMID, NULL);
    return 0;
}
/* Output:
   Got type=1: 'Priority 1 (CRITICAL)'
   Got type=2: 'Priority 2 (HIGH)'
   Got type=3: 'Priority 3 (LOW)'
*/

Code Example 4 — MSG_NOERROR for Oversized Messages

/* msg_noerror.c — Truncation with MSG_NOERROR */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

/* Sender uses a large buffer */
struct BigMsg  { long mtype; char text[128]; };
/* Receiver has a small buffer */
struct SmallBuf { long mtype; char text[32]; };

int main(void)
{
    int qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666);

    /* Send 128 bytes of payload */
    struct BigMsg big;
    big.mtype = 1;
    memset(big.text, 'X', sizeof(big.text) - 1);
    big.text[127] = '\0';
    msgsnd(qid, &big, sizeof(big.text), 0);
    printf("Sent %zu bytes\n", sizeof(big.text));

    /* Try to receive into small buffer WITHOUT MSG_NOERROR */
    struct SmallBuf small;
    ssize_t n = msgrcv(qid, &small, sizeof(small.text), 1, 0);
    if (n == -1)
        printf("Without MSG_NOERROR: failed (E2BIG) — message too large\n");

    /* Send again since the message was not consumed on error */
    msgsnd(qid, &big, sizeof(big.text), 0);

    /* Receive WITH MSG_NOERROR — truncates silently */
    n = msgrcv(qid, &small, sizeof(small.text), 1, MSG_NOERROR);
    if (n != -1)
        printf("With MSG_NOERROR: received %zd bytes (truncated)\n", n);

    msgctl(qid, IPC_RMID, NULL);
    return 0;
}

Handling Interrupted Calls (EINTR)

If a signal arrives while msgsnd/msgrcv is blocking, the call returns -1 with errno=EINTR. Always check for this and retry:

/* Helper: msgsnd that auto-retries on EINTR */
int msgsnd_safe(int qid, void *msgp, size_t sz, int flags)
{
    int ret;
    do {
        ret = msgsnd(qid, msgp, sz, flags);
    } while (ret == -1 && errno == EINTR);
    return ret;
}

/* Helper: msgrcv that auto-retries on EINTR */
ssize_t msgrcv_safe(int qid, void *msgp, size_t sz, long type, int flags)
{
    ssize_t ret;
    do {
        ret = msgrcv(qid, msgp, sz, type, flags);
    } while (ret == -1 && errno == EINTR);
    return ret;
}

Interview Questions — msgsnd() & msgrcv()

Q1. What is the msgsz argument in msgsnd()? What common mistake do beginners make? It is the size of the payload (mtext) only — NOT the full struct size. The common mistake is passing sizeof(whole_struct), which includes sizeof(long) for mtype and confuses the kernel about the payload size.
Q2. What does a msgrcv() with msgtyp=0 do? Returns the first message in the queue regardless of type — pure FIFO order.
Q3. What does a negative msgtyp do in msgrcv()? Returns the first message whose type is ≤ abs(msgtyp), choosing the lowest-numbered type first. This implements a simple priority mechanism.
Q4. What happens if IPC_NOWAIT is passed to msgsnd() and the queue is full? msgsnd() returns immediately with -1 and errno = EAGAIN instead of blocking.
Q5. What is MSG_NOERROR in msgrcv()? When is it useful? When the incoming message’s payload is larger than the receiver’s buffer (maxmsgsz), instead of failing with E2BIG, MSG_NOERROR silently truncates the message to fit. Useful when you know the relevant data is in the first N bytes.
Q6. What errno is returned by msgrcv() when no matching message exists and IPC_NOWAIT is set? ENOMSG.
Q7. What happens to a blocking msgsnd() or msgrcv() if a signal is received? The call is interrupted and returns -1 with errno = EINTR. The application must check for this and retry the call.
Q8. What is the constraint on the mtype field of a sent message? mtype must be a positive integer (> 0). Passing 0 or a negative value to msgsnd() returns EINVAL.
Q9. What does MSG_EXCEPT do? When msgtyp > 0, MSG_EXCEPT receives the first message whose type is NOT equal to msgtyp — the opposite of normal behaviour. This is Linux-specific (not in POSIX).

Leave a Reply

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