Страницы

Поиск по вопросам

суббота, 14 декабря 2019 г.

Что вызывает ошибку SIGPIPE?

#linux #c #сокет


Создаю неблокирующий сокет, выполняю connect. С помощью select проверяю что можно
отправлять данные в сокет. При этом с другой стороной соединения нет. 

Пытаюсь отправить данные в сокет и ловлю ошибку SIGPIPE. В связи с этим есть пара
вопросов:


Как мог select вернуть 1 при проверке на запись если обратной стороны нет?
Если это нормальное поведение, то как узнать что подключение произведено?
Если не было произведено подключение, то как могла возникнуть ошибка SIGPIPE,  означающая,
если я не ошибаюсь, обрыв соединения?


UPD:

Вот минимальный пример 

int main()
{
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);

    if ( sockfd == -1 )
        printf("Error\n");

    //Говорим что сокет неблокирующий
    int arg = ::fcntl(sockfd, F_GETFL);
    long flags = arg & ~O_NONBLOCK;
    flags |= O_NONBLOCK;
    ::fcntl(sockfd, F_SETFL, flags);

    //Создаем запрос на подключение
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(10502);
    addr.sin_addr.s_addr = inet_addr("10.14.0.121");
    int rc = ::connect(sockfd, (sockaddr*)&addr, sizeof(addr));
    int err = errno;//errno == EINPROGRESS 

    fd_set fdRead;
    fd_set fdWrite;
    fd_set fdExcept;
    FD_ZERO(&fdRead);
    FD_ZERO(&fdWrite);
    FD_ZERO(&fdExcept);

    FD_SET(sockfd, &fdWrite);

    struct timeval tv;
    tv.tv_sec  = 0;
    tv.tv_usec = 500;

    // здесь select возвращает 1
    rc = ::select(int(sockfd) + 1, &fdRead, &fdWrite, &fdExcept, &tv);

    //здесь пытаюсь проверить сокет на отсутствие ошибок, но  rc == -1.
    // errno == EFAULT 
    int value;
    int length = sizeof(value);
    rc = ::getsockopt(sockfd, SOL_SOCKET, SO_ERROR, reinterpret_cast(value),
(socklen_t*)&length);
    err = errno;


    //Здесь SIGPIPE
    char buffer[1];
    buffer[0] = '0';
    rc = ::send(sockfd, reinterpret_cast(&buffer), sizeof(buffer), 0);

    return 0;
}

    


Ответы

Ответ 1



Проверять через select на запись лишено смысла, сокет всегда готов для записи за исключением закрытого сокета или состояния после команды shutdown с параметрами SHUT_WR или SHUT_RDWR. С не блокируемыми сокетами еще веселее. Разумно проверять состояние сокета через getsockopt(.. SO_ERROR ..), пример такой функции: int socket_iserror(int sock) { int se = 0; socklen_t sl = sizeof(int); errno = 0; #if defined(SO_ERROR) if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &se, &sl) < 0) { switch(errno) { case EINTR: #if defined(EAGAIN) case EAGAIN: #elif defined(EWOULDBLOCK) case EWOULDBLOCK: #endif { return 0; } default: { return -1; } } } switch(se) { case 0: case EINTR: { return 0; } case ETIMEDOUT: { errno = ETIMEDOUT; return -1; } case ECONNRESET: { errno = ECONNRESET; return -1; } default: { errno = se; return se; } } #else return 0; #endif } В дополнении к этому, чтобы предотвращать фатальное действие сигнала SIGPIPE при приеме/передаче, необходимо добавить в начало каждого треда, если апликация много поточная, или в функцию main() следующее: sigaction(SIGPIPE, &(struct sigaction){SIG_IGN}, NULL); при этом, в случаях send()/write() будет возвращаться ошибка -1 и errno устанавливаться в EPIPE. Пример как с этим работать: int ret; if ((ret = socket_iserror(sockfd)) != 0) { //return, break если цикл (с закрытием сокета) { if ((ret = send(sockfd, ..., ..., 0)) < 0) { #if defined(EAGAIN) if ((errno == EAGAIN) || (errno == EINTR)) #elif defined(EWOULDBLOCK) if ((errno == EWOULDBLOCK) || (errno == EINTR)) #endif { // повторить код: continue, если это тело в цикле; // это как раз не блокируемый сокет } if (errno == EPIPE) { // закрыть сокет и более не обращаться к нему; } // return, break если цикл (с закрытием сокета) } В двух словах вроде все, для полноты можно добавить: инициализация не блокируемого сокета: #if (defined(LINUX_VERSION_CODE) && \ (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)) && \ defined(SOCK_NONBLOCK)) if ((socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0)) < 0) { ... } #else // используйте fcntl #endif обработка ошибок метода connect на не блокируемом сокете: if ((connect(sockfd, (sockaddr*)&addr, sizeof(addr)) < 0) && (errno != EINPROGRESS)) { ... }

Ответ 2



Как мог select вернуть 1 при проверке Потому, что в вызове select() "проверяется доступность операции в/в без блокировки". А если Ваш сокет НЕблокирующий, то естественно, что select() слетает с ожидания сразу же. Вы чего хотите? БЛОКИРОВАТЬ операцию в/в на ожидании готовности НЕ блокировать и самому разбираться с готовностью ?

Комментариев нет:

Отправить комментарий