What is socketpair()?
Sometimes you want a single process to create two connected sockets and use them for communication between a parent and child process. Normally, this would require many steps: socket(), bind(), listen(), connect(), and accept().
socketpair() does all of this in one system call. It creates two pre-connected sockets and returns their file descriptors. Think of it as creating a private two-way pipe between two processes.
Important: socketpair() only works with AF_UNIX. It makes no sense for network sockets since both ends must be on the same machine.
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
/*
* Returns: 0 on success, -1 on error
*
* domain: Must be AF_UNIX (only UNIX domain supported)
* type: SOCK_STREAM or SOCK_DGRAM
* (can OR with SOCK_CLOEXEC or SOCK_NONBLOCK on Linux 2.6.27+)
* protocol: Must be 0
* sockfd: Output array - receives the two connected file descriptors
* sockfd[0] and sockfd[1] are connected to each other
*/
both directions
Write to sockfd[1] โ Read from sockfd[0]
- Bidirectional byte stream
- Like a pipe but in both directions
- Data flows as a continuous stream (no message boundaries)
- Called a stream pipe
- On BSD systems, regular
pipe()is actually implemented usingsocketpair()
- Preserves message boundaries
- Each write is a separate datagram
- Reliable (local machine, no packet loss)
- Better when you need to distinguish individual messages
The most common pattern: create a socket pair, then fork. Parent and child each close one end and use the other. The result is a private, bidirectional channel.
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
int sv[2]; /* sv[0] and sv[1] are the two connected sockets */
pid_t pid;
char buf[100];
ssize_t n;
/* Step 1: Create the connected socket pair */
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1) {
perror("socketpair");
exit(1);
}
/* Step 2: Fork */
pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* ---- CHILD ---- */
close(sv[0]); /* Child doesn't use sv[0] */
/* Read a message from the parent */
n = read(sv[1], buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Child received: %s\n", buf);
}
/* Send a reply */
write(sv[1], "Hello from child!", 18);
close(sv[1]);
exit(EXIT_SUCCESS);
} else {
/* ---- PARENT ---- */
close(sv[1]); /* Parent doesn't use sv[1] */
/* Send a message to the child */
write(sv[0], "Hello from parent!", 19);
/* Read the child's reply */
n = read(sv[0], buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("Parent received: %s\n", buf);
}
close(sv[0]);
wait(NULL); /* Wait for child to finish */
exit(EXIT_SUCCESS);
}
}
Child received: Hello from parent!
Parent received: Hello from child!
close(sv[1]);
uses sv[0]
close(sv[0]);
uses sv[1]
One of the biggest differences between socketpair() and manually creating two connected sockets: socketpair() does not bind to any file system path.
Creates a file at /tmp/mysock
Any process that can read the file system can attempt to connect
Must manually unlink the socket file when done
No file system entry created
Only the process and its children (via fork()) can use the fds
No cleanup needed โ closed when fds are closed
Since Linux kernel 2.6.27, you can OR extra flags into the type argument to avoid extra fcntl() calls:
/* Create a non-blocking socket pair that closes on exec */
int sv[2];
if (socketpair(AF_UNIX,
SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
0, sv) == -1) {
perror("socketpair");
exit(1);
}
/*
* SOCK_CLOEXEC:
* Sets FD_CLOEXEC on both sv[0] and sv[1].
* When the process calls exec(), both fds are automatically closed.
* Equivalent to: fcntl(sv[0], F_SETFD, FD_CLOEXEC);
* fcntl(sv[1], F_SETFD, FD_CLOEXEC);
*
* SOCK_NONBLOCK:
* Sets O_NONBLOCK on both sockets.
* read()/write() return immediately with EAGAIN if no data.
* Equivalent to: fcntl(sv[0], F_SETFL, O_NONBLOCK);
* fcntl(sv[1], F_SETFL, O_NONBLOCK);
*/
fcntl() calls because they avoid a race condition in multi-threaded programs between socketpair() and fcntl().| Feature | pipe() | socketpair(SOCK_STREAM) |
|---|---|---|
| Direction | One-way only | โ Both directions |
| File descriptors | 2 (one read, one write) | 2 (each can read & write) |
| Bidirectional IPC | Needs two pipe() calls | โ Single call |
| Message boundaries | No (byte stream) | SOCK_DGRAM: Yes SOCK_STREAM: No |
| Filesystem path | None | None |
| BSD implementation | Uses socketpair() internally | The primitive |
A: socketpair() creates two pre-connected sockets in a single call. You use it when a process wants to communicate with its child (after fork()) over a private, bidirectional channel. It is simpler and more secure than manually creating, binding, and connecting two sockets.
A: Because socketpair() creates both endpoints in the same kernel call on the same machine. Network sockets (AF_INET) require two different hosts or at least IP-level addressing, which does not make sense for a local pair created in a single system call.
A: A stream pipe is a bidirectional pipe created using socketpair(AF_UNIX, SOCK_STREAM, 0, sv). Unlike a traditional pipe() (which is one-directional), a stream pipe allows data to flow in both directions. On BSD-based systems, the regular pipe() system call is actually implemented internally using socketpair().
A: SOCK_CLOEXEC sets the close-on-exec flag (FD_CLOEXEC) on both socket file descriptors atomically at creation time. This means if the process calls exec(), the socket fds are automatically closed. Without this flag, you need a separate fcntl() call, which creates a race condition in multi-threaded programs (another thread might fork() between socketpair() and fcntl()).
A: The protocol argument selects the specific protocol within the socket family. For AF_UNIX, there is only one protocol, so this argument must always be 0.
A: After fork(), both parent and child hold copies of both file descriptors. For clean communication, each process should close the fd it does not use. If they don’t, the parent closing its end does not fully close the socket because the child still holds it open (and vice versa). This could cause read() to block indefinitely instead of returning EOF.
A: socketpair() creates sockets that are not bound to any file system path. No other process can discover or connect to them. Named sockets (created with bind()) have a path in the file system that any process with appropriate permissions can find and connect to, introducing security risks.
Next: Linux Abstract Socket Namespace โ sockets without filesystem paths
