← Назад к вопросам
В чём разница между Berkeley socket и POSIX pipe?
2.0 Middle🔥 121 комментариев
#Сети и протоколы#Linux и операционные системы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между Berkeley socket и POSIX pipe
Оба механизма для межпроцессного взаимодействия (IPC), но используются в совершенно разных целях. Давайте разберемся детально.
Основное различие
POSIX Pipe:
- Локальное взаимодействие между процессами на одной машине
- Однонаправленный канал (one-way)
- Простой, synchronous механизм
- Использует файловую систему
Berkeley Socket:
- Может быть локальным (Unix domain) или сетевым
- Двунаправленный (two-way)
- Асинхронный, event-driven
- Стандарт для сетевого взаимодействия
1. POSIX Pipe - простое взаимодействие
Основной пример:
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main() {
int pipefd[2]; // pipefd[0] = read, pipefd[1] = write
// Создаем pipe
if (pipe(pipefd) == -1) {
perror("pipe");
return 1;
}
pid_t pid = fork();
if (pid == 0) { // Дочерний процесс
close(pipefd[1]); // Закрываем write конец
char buffer[100];
read(pipefd[0], buffer, sizeof(buffer));
printf("Child received: %s\n", buffer);
close(pipefd[0]);
} else { // Родительский процесс
close(pipefd[0]); // Закрываем read конец
const char* msg = "Hello from parent!";
write(pipefd[1], msg, strlen(msg) + 1);
close(pipefd[1]);
wait(NULL);
}
return 0;
}
Характеристики pipe:
// Создание
int pipefd[2];
pipe(pipefd); // pipefd[0] = read, pipefd[1] = write
// Однонаправленный!
write(pipefd[1], "data", 4); // Только писать в [1]
read(pipefd[0], buf, 4); // Только читать из [0]
// Блокирующие операции (по умолчанию)
read(); // Блокирует, пока нет данных
write(); // Блокирует, пока буфер полон
// Буфер (обычно 4KB-64KB)
// Если буфер заполнен - write() блокируется
// Если буфер пуст - read() блокируется
2. Berkeley Socket - универсальное решение
Пример TCP socket (сетевой):
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
// Сервер
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
bind(server_socket, (struct sockaddr*)&addr, sizeof(addr));
listen(server_socket, 5);
int client_socket = accept(server_socket, NULL, NULL);
// Двунаправленный!
send(client_socket, "Hello", 5, 0); // Отправить
char buf[100];
recv(client_socket, buf, 100, 0); // Получить
close(client_socket);
close(server_socket);
Пример Unix domain socket (локальный):
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
// Создание Unix socket
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/socket");
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
int conn = accept(sockfd, NULL, NULL);
// Двунаправленный
send(conn, "data", 4, 0);
recv(conn, buf, 100, 0);
close(conn);
close(sockfd);
3. Таблица сравнения
| Характеристика | Pipe | Socket |
|---|---|---|
| Направленность | Однонаправленный | Двунаправленный |
| Локальность | Только локальный | Локальный и сетевой |
| Область применения | Простое IPC | Универсальное решение |
| Модель | Блокирующая (sync) | Асинхронная (async) |
| Буфер | ОС управляет (4KB-64KB) | Можно контролировать |
| Типы сокетов | - | SOCK_STREAM (TCP), SOCK_DGRAM (UDP) |
| Port/Address binding | Нет | Да |
| Масштабируемость | Маленькие объемы | Большие объемы |
| Производительность | Быстрый (локальный) | Зависит от сети |
| Сложность | Простой | Сложнее |
4. Блокирующ vs неблокирующий режим
Pipe (блокирующий по умолчанию):
int pipefd[2];
pipe(pipefd);
// Блокирующий режим (по умолчанию)
read(pipefd[0], buf, 100); // Ждет, пока данные появятся
write(pipefd[1], "data", 4); // Ждет, пока место в буфере
// Неблокирующий режим (с флагом O_NONBLOCK)
fcntl(pipefd[0], F_SETFL, O_NONBLOCK);
read(pipefd[0], buf, 100); // Вернет -1 с EAGAIN если нет данных
Socket (может быть асинхронным):
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
// Можно использовать select() для множественных сокетов
fd_set readfds;
FD_SET(sockfd, &readfds);
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
int activity = select(sockfd + 1, &readfds, NULL, NULL, &tv);
// Ждет 1 секунду, пока не будут данные
5. Практические примеры использования
Когда использовать Pipe:
// 1. Shell pipeline (ls | grep | sort)
// 2. Простая коммуникация между двумя процессами
// 3. Передача данных между fork()-ными процессами
// 4. Внутренняя логика процесса
// Пример: передача результата из child в parent
int pipefd[2];
pipe(pipefd);
if (fork() == 0) {
close(pipefd[0]); // Child не читает
// Вычисляем результат
int result = doSomeComputation();
write(pipefd[1], &result, sizeof(result));
close(pipefd[1]);
exit(0);
} else {
close(pipefd[1]); // Parent не пишет
int result;
read(pipefd[0], &result, sizeof(result));
close(pipefd[0]);
printf("Result: %d\n", result);
}
Когда использовать Socket:
// 1. Сетевое взаимодействие (HTTP, FTP, гейм-сервер)
// 2. Коммуникация между удаленными машинами
// 3. Микросервисная архитектура
// 4. Асинхронное взаимодействие (event-driven)
// 5. Высокопроизводительные приложения
// Пример: простой HTTP сервер
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
int reuse = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, SOMAXCONN);
while (true) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_sock = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
// Обработка клиента
char buffer[1024];
ssize_t n = recv(client_sock, buffer, sizeof(buffer), 0);
// Отправка ответа
send(client_sock, "HTTP/1.1 200 OK\r\n", 17, 0);
close(client_sock);
}
6. Пропускная способность и производительность
Pipe:
// Локальный канал, очень быстрый
// Пример: 1GB/sec на современных CPU
// Ограничения: только на одной машине
Socket:
// TCP через сеть: зависит от сети (100Mbps-10Gbps)
// Unix domain socket: близко к pipe (1GB/sec)
// UDP: может быть быстрее TCP но менее надежен
7. Лучшие практики
✓ Используй Pipe если:
- Нужна простая синхронная коммуникация между процессами
- Данные передаются в одном направлении
- Процессы работают на одной машине
- Простота важнее функциональности
✓ Используй Socket если:
- Нужна двусторонняя коммуникация
- Возможна сетевая коммуникация
- Нужна асинхронность (select, epoll, async)
- Требуется масштабируемость
- Нужны разные протоколы (TCP, UDP)
Best practices для Socket:
// 1. Используй SO_REUSEADDR для переиспользования портов
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
// 2. Используй epoll/kqueue для обработки множества соединений
int epfd = epoll_create1(0);
// 3. Устанавливай таймауты
struct timeval tv;
tv.tv_sec = 5; // 5 секунд
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// 4. Обработка сигналов (SIGPIPE при разрыве соединения)
signal(SIGPIPE, SIG_IGN);
// 5. Правильная обработка ошибок (EWOULDBLOCK, EAGAIN)
Заключение
Pipe:
- Простой, быстрый, локальный механизм
- Однонаправленный
- Хорош для shell, простых fork() сценариев
- Выступает в роли инструмента синхронизации
Socket:
- Универсальный, мощный, может быть сетевым
- Двунаправленный
- Хорош для микросервисов, асинхронных приложений
- Выступает в роли полнофункционального решения
В современных системах:
- Pipe для простоты и локальных задач
- Socket для масштабируемости и сетевого взаимодействия
Для высоконагруженных приложений часто используют оба: pipe для внутреннего взаимодействия процессов, socket для сетевого взаимодействия!