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

Что нужно для работы с дескриптором в неблокируемом режиме?

2.0 Middle🔥 281 комментариев
#Язык C++#Linux и операционные системы

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что нужно для работы с дескриптором в неблокируемом режиме?

Для работы с файловым дескриптором (file descriptor) в неблокируемом (non-blocking) режиме необходимо выполнить несколько шагов и обработать особенности такой работы.

1. Установка флага O_NONBLOCK

Способ 1: При открытии файла/сокета

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

// Открыть файл в неблокируемом режиме
int fd = open("file.txt", O_RDONLY | O_NONBLOCK);
if (fd == -1) {
    perror("open");
    return 1;
}

// Для сокетов
#include <sys/socket.h>
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
    perror("socket");
    return 1;
}

Способ 2: Установка флага на уже открытый дескриптор

#include <fcntl.h>
#include <unistd.h>

int fd = open("file.txt", O_RDONLY);

// Получить текущие флаги
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {
    perror("fcntl F_GETFL");
    return 1;
}

// Добавить флаг O_NONBLOCK
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) == -1) {
    perror("fcntl F_SETFL");
    return 1;
}

Способ 3: Для сокетов альтернативный способ (ioctl)

#include <sys/ioctl.h>
#include <sys/socket.h>

int sock = socket(AF_INET, SOCK_STREAM, 0);

// Способ 1: через fcntl (рекомендуется)
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);

// Способ 2: через ioctl (также работает)
int nonblock = 1;
if (ioctl(sock, FIONBIO, &nonblock) == -1) {
    perror("ioctl");
    return 1;
}

2. Обработка ошибок EAGAIN и EWOULDBLOCK

При работе в неблокируемом режиме операции read/write могут вернуть ошибку, если данные не готовы. Это не является ошибкой программы, а нормальным поведением:

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <cstring>
#include <iostream>

int main() {
    int fd = open("data.bin", O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    char buffer[1024];
    
    // Попытка чтения в неблокируемом режиме
    ssize_t n = read(fd, buffer, sizeof(buffer));
    
    if (n > 0) {
        // Успешно прочитано n байт
        std::cout << "Read " << n << " bytes\n";
    }
    else if (n == 0) {
        // End of file
        std::cout << "EOF reached\n";
    }
    else {
        // n == -1, check errno
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            // Данные не готовы — это нормально в non-blocking режиме!
            std::cout << "No data available, try again later\n";
        }
        else if (errno == EINTR) {
            // Прервано сигналом
            std::cout << "Interrupted by signal\n";
        }
        else {
            // Реальная ошибка
            perror("read");
        }
    }
    
    close(fd);
    return 0;
}

Важно: EAGAIN и EWOULDBLOCK имеют одинаковое значение на большинстве систем, но лучше проверять оба:

// Корректная проверка
if (errno == EAGAIN || errno == EWOULDBLOCK) {
    // Данные не готовы
}

3. Использование с I/O multiplexing (select/poll/epoll)

Рекомендуемый способ работы — комбинировать неблокируемые операции с механизмами ожидания:

Пример с select (кроссплатформенный):

#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <cstring>
#include <iostream>

int main() {
    int fd = open("data.bin", O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    while (true) {
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);
        
        struct timeval tv;
        tv.tv_sec = 5;   // 5 секунд timeout
        tv.tv_usec = 0;
        
        int ret = select(fd + 1, &readfds, nullptr, nullptr, &tv);
        
        if (ret == -1) {
            perror("select");
            break;
        }
        else if (ret == 0) {
            std::cout << "Timeout — no data available\n";
            continue;
        }
        
        // Данные готовы к чтению
        if (FD_ISSET(fd, &readfds)) {
            char buffer[1024];
            ssize_t n = read(fd, buffer, sizeof(buffer));
            if (n > 0) {
                std::cout << "Read " << n << " bytes\n";
            }
            else if (n == 0) {
                std::cout << "EOF\n";
                break;
            }
        }
    }
    
    close(fd);
    return 0;
}

Пример с epoll (Linux, самый эффективный):

#include <fcntl.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <errno.h>
#include <cstring>
#include <iostream>

int main() {
    int fd = open("data.bin", O_RDONLY | O_NONBLOCK);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    // Создать epoll instance
    int epfd = epoll_create1(0);
    if (epfd == -1) {
        perror("epoll_create1");
        return 1;
    }
    
    // Добавить дескриптор в epoll
    epoll_event event;
    event.events = EPOLLIN;  // Интересует читаемость
    event.data.fd = fd;
    
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event) == -1) {
        perror("epoll_ctl");
        return 1;
    }
    
    epoll_event events[10];
    
    while (true) {
        int n = epoll_wait(epfd, events, 10, 5000);  // 5 сек timeout
        
        if (n == -1) {
            perror("epoll_wait");
            break;
        }
        else if (n == 0) {
            std::cout << "Timeout\n";
            continue;
        }
        
        for (int i = 0; i < n; ++i) {
            if (events[i].data.fd == fd && events[i].events & EPOLLIN) {
                char buffer[1024];
                ssize_t bytes = read(fd, buffer, sizeof(buffer));
                if (bytes > 0) {
                    std::cout << "Read " << bytes << " bytes\n";
                }
                else if (bytes == 0) {
                    std::cout << "EOF\n";
                    goto exit_loop;
                }
            }
        }
    }
    
exit_loop:
    close(epfd);
    close(fd);
    return 0;
}

4. Практический пример: неблокируемый TCP сокет

#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <cstring>
#include <iostream>

class NonBlockingSocket {
    int sock;
    
public:
    NonBlockingSocket() : sock(-1) {}
    
    bool create() {
        sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock == -1) {
            perror("socket");
            return false;
        }
        
        // Установить неблокируемый режим
        int flags = fcntl(sock, F_GETFL, 0);
        if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
            perror("fcntl");
            return false;
        }
        
        return true;
    }
    
    bool connect(const char* host, int port) {
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        inet_pton(AF_INET, host, &addr.sin_addr);
        
        int ret = ::connect(sock, (struct sockaddr*)&addr, sizeof(addr));
        
        if (ret == 0) {
            // Подключение успешно (редко в non-blocking)
            return true;
        }
        
        if (errno == EINPROGRESS) {
            // Подключение в процессе — это нормально
            std::cout << "Connection in progress...\n";
            return true;  // Проверить позже через select/epoll
        }
        
        perror("connect");
        return false;
    }
    
    ssize_t send_data(const char* data, size_t len) {
        ssize_t n = send(sock, data, len, MSG_NOSIGNAL);
        
        if (n > 0) {
            return n;  // Успешно отправлено
        }
        
        if (errno == EAGAIN || errno == EWOULDBLOCK) {
            // Буфер заполнен, попробуйте позже
            return 0;
        }
        
        if (errno == EPIPE) {
            // Сокет закрыт
            std::cerr << "Socket closed by peer\n";
            return -1;
        }
        
        perror("send");
        return -1;
    }
    
    void close_socket() {
        if (sock != -1) {
            close(sock);
            sock = -1;
        }
    }
    
    ~NonBlockingSocket() {
        close_socket();
    }
};

Ключевые моменты

АспектЧто делать
Установка флагаfcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK)
Ошибки EAGAINЭто НЕ ошибка, данные не готовы
Умное ожиданиеИспользовать select/epoll вместо busy-waiting
Обработка сигналовEINTR требует повтора операции
БуферыМогут быть полными при write — проверять EAGAIN
TCP connectВернёт EINPROGRESS, проверить статус позже

Резюме: Для неблокируемого режима нужно: 1) Установить флаг O_NONBLOCK; 2) Обрабатывать EAGAIN/EWOULDBLOCK; 3) Использовать multiplexing (select/epoll); 4) Правильно обрабатывать сигналы и ошибки.

Что нужно для работы с дескриптором в неблокируемом режиме? | PrepBro