Создаю неблокирующий сокет, выполняю connect. С помощью select проверяю что можно отправлять данные в сокет. При этом с другой стороной соединения нет.
Пытаюсь отправить данные в сокет и ловлю ошибку SIGPIPE. В связи с этим есть пара вопросов:
Как мог select вернуть 1 при проверке на запись если обратной стороны нет?
Если это нормальное поведение, то как узнать что подключение произведено?
Если не было произведено подключение, то как могла возникнуть ошибка SIGPIPE, означающая, если я не ошибаюсь, обрыв соединения?
UPD
Вот минимальный пример
int main()
{
int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if ( sockfd == -1 )
printf("Error
");
//Говорим что сокет неблокирующий
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
//Здесь SIGPIPE
char buffer[1];
buffer[0] = '0';
rc = ::send(sockfd, reinterpret_cast
return 0;
}
Ответ
Проверять через 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)) { ... }
Комментариев нет:
Отправить комментарий