← Назад к вопросам

В чём разница между 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. Таблица сравнения

ХарактеристикаPipeSocket
НаправленностьОднонаправленныйДвунаправленный
ЛокальностьТолько локальныйЛокальный и сетевой
Область примененияПростое 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 для сетевого взаимодействия!