POSIX Message Queues Receiving Messages with mq_receive()

 

POSIX Message Queues
Part 2 – Receiving Messages with mq_receive()
Topic
mq_receive()
Level
Intermediate
Chapter
52 – TLPI

What is mq_receive()?

Once messages are placed on a POSIX message queue using mq_send(), a process on the other end needs to read them. That is exactly what mq_receive() does. It reads the highest-priority message that is currently sitting on the queue. If two messages share the same priority, the one that arrived first (FIFO order) is returned first.

Think of a POSIX message queue like a post office sorting room where letters are arranged by urgency (priority). The clerk (mq_receive()) always picks the most urgent letter from the top of the pile.

Function Signature
#include <mqueue.h>

ssize_t mq_receive(mqd_t mqdes,
                   char *msg_ptr,
                   size_t msg_len,
                   unsigned int *msg_prio);

/* Returns: number of bytes in received message on success, -1 on error */
/* Link with: -lrt  (e.g. gcc prog.c -lrt) */

Parameter Type Meaning
mqdes mqd_t Message queue descriptor returned by mq_open()
msg_ptr char * Pointer to buffer where the received message will be stored
msg_len size_t Size of the buffer — MUST be ≥ mq_msgsize attribute of the queue
msg_prio unsigned int * If not NULL, priority of received message is stored here

How Priority-Based Retrieval Works

Messages are always retrieved in descending priority order, not insertion order.

Queue after sending msg-a(p=5), msg-b(p=0), msg-c(p=10):
HEAD →
msg-c    priority=10
msg-a    priority=5
TAIL →
msg-b    priority=0
1st receive → msg-c (p=10)
2nd receive → msg-a (p=5)
3rd receive → msg-b (p=0)

⚠ Critical Rule: msg_len Must Be ≥ mq_msgsize

The buffer size you pass as msg_len must be at least as large as the queue’s mq_msgsize attribute. If it is smaller, mq_receive() fails with EMSGSIZE. You should always call mq_getattr() first to find the correct buffer size.

✘ Wrong

char buf[64];
/* mq_msgsize = 256 */
mq_receive(mqd, buf, 64, NULL);
/* FAILS: EMSGSIZE */
✔ Correct

struct mq_attr attr;
mq_getattr(mqd, &attr);
char *buf = malloc(attr.mq_msgsize);
mq_receive(mqd, buf, attr.mq_msgsize, NULL);
/* Works */

Blocking vs Non-Blocking Behavior

Call mq_receive()
Is queue empty?

NO → Read highest
priority message
Return byte count

YES
O_NONBLOCK OFF
→ Block until
message arrives
O_NONBLOCK ON
→ Return -1
errno=EAGAIN

Note: Unlike pipes, there is no EOF concept on an empty queue. If there are no writers, mq_receive() still blocks — it does not return 0 (end-of-file).

Example 1 – Basic mq_receive()

A simple receiver that reads one message and prints its content and priority.

/* pmsg_receive_basic.c
 * Compile: gcc pmsg_receive_basic.c -o pmsg_receive_basic -lrt
 * Run:     ./pmsg_receive_basic /myqueue
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s /queue-name\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Open the queue read-only */
    mqd_t mqd = mq_open(argv[1], O_RDONLY);
    if (mqd == (mqd_t)-1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    /* Get queue attributes so we know the max message size */
    struct mq_attr attr;
    if (mq_getattr(mqd, &attr) == -1) {
        perror("mq_getattr");
        exit(EXIT_FAILURE);
    }

    printf("Queue: max %ld messages, max msg size = %ld bytes\n",
           attr.mq_maxmsg, attr.mq_msgsize);

    /* Allocate buffer of exactly mq_msgsize bytes */
    char *buffer = malloc(attr.mq_msgsize);
    if (buffer == NULL) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    unsigned int priority;
    /* This call BLOCKS if queue is empty */
    ssize_t bytes_read = mq_receive(mqd, buffer, attr.mq_msgsize, &priority);
    if (bytes_read == -1) {
        perror("mq_receive");
        free(buffer);
        exit(EXIT_FAILURE);
    }

    printf("Received %zd bytes at priority %u\n", bytes_read, priority);
    printf("Message: %.*s\n", (int)bytes_read, buffer);

    free(buffer);
    mq_close(mqd);
    return 0;
}

Example 2 – Non-Blocking Receive (O_NONBLOCK)

Use O_NONBLOCK when you do not want to block waiting for a message.

/* pmsg_receive_nonblock.c
 * Compile: gcc pmsg_receive_nonblock.c -o pmsg_receive_nonblock -lrt
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <mqueue.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s /queue-name\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    /* Open with O_NONBLOCK: calls return immediately if queue empty */
    mqd_t mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
    if (mqd == (mqd_t)-1) {
        perror("mq_open");
        exit(EXIT_FAILURE);
    }

    struct mq_attr attr;
    mq_getattr(mqd, &attr);

    char *buffer = malloc(attr.mq_msgsize);
    if (!buffer) { perror("malloc"); exit(EXIT_FAILURE); }

    unsigned int priority;
    ssize_t n = mq_receive(mqd, buffer, attr.mq_msgsize, &priority);

    if (n == -1) {
        if (errno == EAGAIN) {
            /* Queue was empty — not an error, just no data available */
            printf("Queue is empty (EAGAIN). No message received.\n");
        } else {
            perror("mq_receive");
        }
    } else {
        printf("Received %zd bytes, priority=%u: %.*s\n",
               n, priority, (int)n, buffer);
    }

    free(buffer);
    mq_close(mqd);
    return 0;
}

Example 3 – Drain All Messages From Queue

Loop with O_NONBLOCK to read every message currently in the queue.

/* drain_queue.c
 * Reads all available messages until EAGAIN, then exits.
 * Compile: gcc drain_queue.c -o drain_queue -lrt
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <mqueue.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s /queue-name\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    mqd_t mqd = mq_open(argv[1], O_RDONLY | O_NONBLOCK);
    if (mqd == (mqd_t)-1) { perror("mq_open"); exit(EXIT_FAILURE); }

    struct mq_attr attr;
    mq_getattr(mqd, &attr);

    char *buf = malloc(attr.mq_msgsize);
    if (!buf) { perror("malloc"); exit(EXIT_FAILURE); }

    int count = 0;
    unsigned int prio;

    while (1) {
        ssize_t n = mq_receive(mqd, buf, attr.mq_msgsize, &prio);
        if (n == -1) {
            if (errno == EAGAIN) {
                /* No more messages */
                break;
            }
            perror("mq_receive");
            break;
        }
        count++;
        printf("[%d] priority=%-3u  bytes=%-4zd  msg=%.*s\n",
               count, prio, n, (int)n, buf);
    }

    printf("Total messages drained: %d\n", count);
    free(buf);
    mq_close(mqd);
    return 0;
}

Example 4 – Full Sender + Receiver Pair

A complete working example: the sender creates the queue and sends 3 messages with different priorities. The receiver reads them in priority order.

/* sender.c — Creates queue, sends 3 messages with different priorities.
 * Compile: gcc sender.c -o sender -lrt
 * Run first: ./sender /testqueue
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(void)
{
    const char *qname = "/testqueue";

    /* Create queue: max 10 messages, each up to 256 bytes */
    struct mq_attr attr = { .mq_flags = 0,
                            .mq_maxmsg = 10,
                            .mq_msgsize = 256,
                            .mq_curmsgs = 0 };

    mqd_t mqd = mq_open(qname, O_CREAT | O_WRONLY, 0644, &attr);
    if (mqd == (mqd_t)-1) { perror("mq_open"); exit(EXIT_FAILURE); }

    /* Send messages: (content, priority) */
    struct { const char *msg; unsigned int prio; } msgs[] = {
        { "msg-a (prio 5)",  5  },
        { "msg-b (prio 0)",  0  },
        { "msg-c (prio 10)", 10 },
    };

    for (int i = 0; i < 3; i++) {
        if (mq_send(mqd, msgs[i].msg, strlen(msgs[i].msg) + 1,
                    msgs[i].prio) == -1) {
            perror("mq_send");
        } else {
            printf("Sent: \"%s\" at priority %u\n",
                   msgs[i].msg, msgs[i].prio);
        }
    }

    mq_close(mqd);
    printf("All messages sent.\n");
    return 0;
}
/* receiver.c — Reads all messages (comes out in priority order).
 * Compile: gcc receiver.c -o receiver -lrt
 * Run after sender: ./receiver /testqueue
 */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <mqueue.h>
#include <fcntl.h>

int main(void)
{
    const char *qname = "/testqueue";

    mqd_t mqd = mq_open(qname, O_RDONLY | O_NONBLOCK);
    if (mqd == (mqd_t)-1) { perror("mq_open"); exit(EXIT_FAILURE); }

    struct mq_attr attr;
    mq_getattr(mqd, &attr);
    char *buf = malloc(attr.mq_msgsize);

    unsigned int prio;
    int n;

    while (1) {
        n = mq_receive(mqd, buf, attr.mq_msgsize, &prio);
        if (n == -1) {
            if (errno == EAGAIN) break;
            perror("mq_receive"); break;
        }
        printf("Received [priority=%u]: %s\n", prio, buf);
    }

    /* Expected output (priority order, not send order):
     *   Received [priority=10]: msg-c (prio 10)
     *   Received [priority=5]:  msg-a (prio 5)
     *   Received [priority=0]:  msg-b (prio 0)
     */

    free(buf);
    mq_close(mqd);
    mq_unlink(qname);   /* Remove queue from filesystem */
    return 0;
}

Key Terms

mq_receive() mq_getattr() mq_msgsize O_NONBLOCK EAGAIN EMSGSIZE Priority-based retrieval Blocking call mqd_t mq_attr

Interview Questions & Answers
Q1. What happens if the buffer passed to mq_receive() is smaller than mq_msgsize?

The call fails immediately with errno set to EMSGSIZE. The buffer size (msg_len) must be greater than or equal to the queue’s mq_msgsize attribute. Always call mq_getattr() first and allocate a buffer of exactly attr.mq_msgsize bytes.

Q2. In what order does mq_receive() return messages?

Always in descending priority order. The message with the highest numeric priority is returned first. If two messages share the same priority, they are returned in FIFO (first-in, first-out) order.

Q3. What does mq_receive() do when the queue is empty and O_NONBLOCK is NOT set?

It blocks indefinitely until a message becomes available. Unlike reading from a pipe, it does not return 0 (EOF) just because there are no writers attached.

Q4. What error does mq_receive() return when the queue is empty and O_NONBLOCK IS set?

It returns -1 and sets errno to EAGAIN (also reported as EWOULDBLOCK on some systems).

Q5. How do you find out the maximum message size of a queue before calling mq_receive()?

Call mq_getattr(mqd, &attr) and read attr.mq_msgsize. Then allocate a buffer of that size. In cooperating-process applications where the message size is agreed upon in advance, mq_getattr() can be skipped.

Q6. How do you retrieve the priority of the received message?

Pass a pointer to an unsigned int as the fourth argument (msg_prio). After a successful call, that variable holds the priority of the message that was received. Pass NULL if you do not need the priority.

Q7. What is the difference between POSIX MQ and pipe regarding EOF?

A pipe reader sees end-of-file (read returns 0) when there are no more writers. A POSIX message queue has no such EOF concept. If the queue is empty and no writer is currently sending, mq_receive() simply blocks — it does not signal end-of-file.

Q8. Is the link flag -lrt always required for POSIX MQ on Linux?

On older Linux systems (glibc < 2.17), yes — you must link with -lrt to get the POSIX real-time library. On modern systems (glibc ≥ 2.17), the MQ functions are included in glibc directly, so -lrt is technically optional but harmless to include.

Leave a Reply

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