FreeBSD sendfile(2): Moving File Data to Sockets Efficiently
Use FreeBSD sendfile(2) to stream files from a connected socket while understanding offsets, partial progress, headers, and failure handling.
FreeBSD’s sendfile(2) transfers file data to a connected socket through a kernel interface. It can avoid the application copying every file block through a userspace buffer, which is useful for static-file servers and other file-to-network paths. It is an optimization opportunity, not a promise that every byte travels directly from disk to the NIC without copies: filesystem, cache, protocol, TLS, and hardware paths still affect the actual work.
The interface is also FreeBSD-specific. Do not copy its argument order or header/trailer structure into code targeting Linux, where sendfile(2) has a different API.
Model the call as progress, not an all-or-nothing write
The call names a regular-file descriptor, a connected socket, a file offset, and a byte count. An optional sf_hdtr can describe header and trailer data. The output count reports bytes sent. On a non-blocking socket, a call can make partial progress and return EAGAIN; the caller must preserve the unsent range and resume when the socket is writable.
off_t offset = 0;
off_t sent = 0;
int error = sendfile(file_fd, client_fd, offset, remaining,
NULL, &sent, 0);
if (sent > 0) {
offset += sent;
remaining -= (size_t)sent;
}
if (error == -1 && errno != EAGAIN && errno != EINTR) {
/* Record the failure and close or recover the connection. */
}
This is a control-flow sketch, not a complete event loop. Production code must define behavior for zero progress, interrupted calls, peer disconnects, and the exact platform release it supports. Do not advance an offset by the requested length; advance it only by the reported count.
Keep HTTP framing and file identity separate
When sending a response, generate the status line and headers using the protocol layer, then send the exact representation length expected by the client. If the file changes while the response is being produced, a previously calculated content length can disagree with the bytes later read. A robust server uses a stable file identity or otherwise detects/rejects that race.
TLS is another boundary. A plain kernel file-to-socket path does not automatically encrypt HTTP content. For TLS, use a supported TLS library integration or a carefully measured alternative; never send cleartext file bytes to a TLS connection and assume the socket will wrap them.
Use sendfile after profiling shows the copy path matters. Compare throughput, CPU use, tail latency, and backpressure behavior under realistic clients, and retain a buffered fallback where the platform or transport cannot use the optimized path.
Related:
- FreeBSD netmap: Memory-Mapped Packet I/O Without a Socket per Packet
- kqueue and kevent: FreeBSD’s Native Scalable Event Notification Interface
Sources: