MSG_MORE – Per-Call Output Control

 

MSG_MORE – Per-Call Output Control
Chapter 61 · Part 3 of 4 · MSG_MORE vs TCP_CORK
send()
System Call
Per-Call
Granularity
Linux
Specific Flag

What is MSG_MORE?

MSG_MORE is a flag you pass to the send() system call. It tells the kernel: “I have more data coming after this call, so please do not transmit a segment yet – wait for the next send() call.”

It achieves the same goal as TCP_CORK – batching multiple writes into fewer TCP segments – but at the level of individual system calls rather than at the socket level.

#include <sys/socket.h>

/*
 * send() with MSG_MORE:
 * flags = MSG_MORE means "do not send yet, more data is coming"
 * flags = 0        means "this is the last piece, go ahead and send"
 */
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

Simple Example

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>

/*
 * Send HTTP headers and body using MSG_MORE.
 * The kernel batches them together into one TCP segment.
 */
void send_response_msg_more(int sockfd,
                            const char *headers, size_t hlen,
                            const char *body,    size_t blen)
{
    /* Tell the kernel: more data is coming after these headers */
    ssize_t n = send(sockfd, headers, hlen, MSG_MORE);
    if (n == -1) {
        perror("send headers MSG_MORE");
        return;
    }

    /* Last piece – no MSG_MORE flag, kernel flushes now */
    n = send(sockfd, body, blen, 0);
    if (n == -1) {
        perror("send body");
        return;
    }
}
Key point: MSG_MORE is a hint per send() call. When you omit MSG_MORE (or pass flags=0), the kernel treats the call as the final piece and transmits whatever has been buffered.

MSG_MORE vs TCP_CORK – Detailed Comparison

Aspect MSG_MORE TCP_CORK
Granularity Per send() call – set on each call individually Socket-level – set once, affects all writes until disabled
API used send() flags argument setsockopt() with IPPROTO_TCP and TCP_CORK
Works with write() No – only with send(), sendto(), sendmsg() Yes – affects all output including write() and sendfile()
Works with sendfile() No – sendfile() does not accept flags Yes – sendfile() respects TCP_CORK on the socket
exec() inheritance Does not persist across exec() – only applies per call Persists as a socket option across exec()
Source code changes Requires explicit MSG_MORE in every relevant send() call Can be set programmatically before exec() without modifying the child
Safety timeout No timeout – flush happens on the send() call without MSG_MORE 200ms auto-flush timeout as a safeguard
OS availability Linux only Linux only (FreeBSD uses TCP_NOPUSH)

The exec() Inheritance Advantage of TCP_CORK

This is a subtle but important difference. Consider the following server architecture:

/* Parent server – sets up the socket, then exec()s a child handler */
int optval = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, &optval, sizeof(optval));

/*
 * The child program inherits sockfd AND the TCP_CORK option.
 * The child does not know about TCP_CORK but benefits from it.
 * All its write() calls are buffered until the parent uncorks,
 * or until the child's socket is closed.
 */
execl("/usr/lib/cgi-bin/handler", "handler", NULL);

With TCP_CORK: The parent sets the socket option. The child exec’d program inherits the socket file descriptor and the socket option is still active. The child’s output is buffered without any changes to its source code.

With MSG_MORE: The child would need to be modified to use send(..., MSG_MORE) instead of write() on every call where batching is desired. You cannot inject MSG_MORE into an existing program without changing its code.

When to prefer TCP_CORK: When you need to enable batching for a program you do not control, or when you want a single toggle to affect all output functions (write, sendfile, send) uniformly.
When to prefer MSG_MORE: When you want fine-grained, call-by-call control within a single program and do not use sendfile(). MSG_MORE makes the batching intent visible at the call site, which can improve code readability.

Alternative: writev() – Gather Write

Before MSG_MORE and TCP_CORK existed, a common technique was to assemble all data into a buffer and call write() once, or to use writev() which takes a vector of buffers and writes them all in one system call.

#include <sys/uio.h>

/*
 * writev() – scatter/gather write: combine multiple buffers in one syscall.
 * This avoids separate TCP segments without needing TCP_CORK or MSG_MORE.
 * Limitation: does not give zero-copy for file data like sendfile() does.
 */
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

/* Example: send headers + body together using writev() */
void send_with_writev(int sockfd,
                      const char *headers, size_t hlen,
                      const char *body,    size_t blen)
{
    struct iovec iov[2];

    iov[0].iov_base = (void *)headers;
    iov[0].iov_len  = hlen;

    iov[1].iov_base = (void *)body;
    iov[1].iov_len  = blen;

    ssize_t n = writev(sockfd, iov, 2);
    if (n == -1)
        perror("writev");
    else
        printf("Sent %zd bytes in one writev() call\n", n);
}
Technique Zero-Copy for File? Batches Segments? Requires Code Change?
writev() No Yes Yes – must restructure write calls
MSG_MORE No (no sendfile support) Yes Yes – must use send() not write()
TCP_CORK + sendfile() Yes Yes Minimal – just add setsockopt calls

The combination of TCP_CORK + sendfile() is the most powerful approach: you get both zero-copy file transfer and TCP segment batching. Use writev() when you have all data in memory and do not need sendfile().

MSG_MORE – Multi-Part Send Example

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>

/*
 * Demonstrates MSG_MORE for a multi-part message.
 * Parts 1..N-1 use MSG_MORE, last part uses flags=0.
 *
 * Think of it like building a train:
 *   MSG_MORE = "attach another car, train is not leaving yet"
 *   flags=0  = "train departs now"
 */
int send_multipart(int sockfd, const char **parts, size_t *lens, int count)
{
    int i;
    ssize_t sent;

    for (i = 0; i < count; i++) {
        int flags = (i < count - 1) ? MSG_MORE : 0;

        sent = send(sockfd, parts[i], lens[i], flags);
        if (sent == -1) {
            perror("send multipart");
            return -1;
        }
        if ((size_t)sent < lens[i]) {
            /* Partial send – need to loop (simplified here) */
            fprintf(stderr, "Partial send on part %d\n", i);
            return -1;
        }
    }
    return 0;
}

int main(void)
{
    /* Usage example (assumes sockfd is a connected TCP socket) */
    int sockfd = /* ... connect to server ... */ 0;

    const char *parts[] = {
        "GET / HTTP/1.1\r\n",
        "Host: example.com\r\n",
        "Connection: close\r\n",
        "\r\n"                   /* blank line ends HTTP request */
    };
    size_t lens[] = {
        strlen(parts[0]),
        strlen(parts[1]),
        strlen(parts[2]),
        strlen(parts[3])
    };

    /*
     * First 3 parts sent with MSG_MORE (not transmitted yet).
     * 4th part sent with flags=0 (flush – all 4 parts go in one segment).
     */
    send_multipart(sockfd, parts, lens, 4);
    return 0;
}

Interview Questions & Answers

Q1. What does the MSG_MORE flag do in a send() call?

MSG_MORE tells the kernel that more data will follow in subsequent send() calls. The kernel buffers the current data and does not transmit a TCP segment yet. When send() is called without MSG_MORE (flags=0), the kernel treats it as the final piece and transmits all buffered data.

Q2. Can you use MSG_MORE with sendfile()?

No. sendfile() does not take a flags argument, so you cannot pass MSG_MORE to it. If you need to combine sendfile() with deferred transmission, you must use TCP_CORK instead of MSG_MORE.

Q3. Why might TCP_CORK be preferable to MSG_MORE in a server that exec()s child handlers?

When a server uses exec() to launch a child handler program, socket options (including TCP_CORK) are inherited by the child through the file descriptor. MSG_MORE, on the other hand, is specified per send() call and requires explicit changes to the child program’s source code. With TCP_CORK, the parent can enable output buffering before exec() and the child benefits from it without any modifications.

Q4. How does writev() differ from MSG_MORE for batching output?

writev() takes a vector of memory buffers and writes them all in a single system call, effectively sending them as one unit. It does not interact with TCP segment control. MSG_MORE signals the kernel that more data will arrive in future send() calls. writev() works only with in-memory data, while MSG_MORE works across multiple send() calls. Neither writev() nor MSG_MORE supports zero-copy file transfer, so for file serving, TCP_CORK plus sendfile() is preferred.

Q5. What happens if you always use MSG_MORE and never call send() without it?

Unlike TCP_CORK which has a 200ms auto-flush, MSG_MORE has no timeout. If you never call send() without MSG_MORE, the buffered data will be stuck and never transmitted until the socket is closed. The close() operation flushes any pending buffered data, but this means the peer receives nothing until the connection is torn down. Always ensure the final piece of data is sent without MSG_MORE.

Next: getsockname() & getpeername()

Learn how to retrieve local and peer socket addresses programmatically.

Continue to Part 4 → Back to Index

Leave a Reply

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