Implementação do Apache para network I/O – sockets

Falei sobre a função poll() e eventos do kernel. Eis uma versão similar da implementação das funções recv()send()connect() do Apache (APR). Graças ao tratamento de erro, via errno, dá para entender que a APR lida com NON BLOCKED sockets. Daí, logo após criar o seu socket, use fcntl para setar o flag do descritor para O_NONBLOCK… Eis os códigos (modificados por mim):

static int wait_for_io(int socketfd);

int socket_send(int socketfd, void *buffer, size_t size)
{
  int rv;

  // Tenta enviar o conteúdo do buffer enquanto houver
  // interrupções.
  do {
    rv = send(socketfd, buffer, size);
  } while (rv == -1 && errno == EINTR);

  // Quando funções que lidam com sockets retornam
  // errno com EAGAIN ou EWOULDBLOCK, isso significa
  // que os dados NÂO FORAM ENVIADOS e o kernel está
  // te falando que a operação anterior poderia
  // bloquear! Enquanto o kernel informar esse erro,
  // o loop abaixo garante que continuaremos tentando
  // enviar o conteúdo do buffer.
  //
  // OBS: EAGAIN e EWOULDBLOCK são a mesma coisa,
  //      esse OR está ai para sistemas onde eles
  //      sejam diferentes!
  while (rv == -1
         && (errno == EAGAIN || errno == EWOULDBLOCK))
  {
    if (wait_for_io(socketfd, 0) <= 0)
      return -1; // timeout ou erro!
    else
      do {
        rv = send(socketfd, buffer, size);
      } while (rv == -1 && errno == EINTR);
  }

  return rv;
}

int socket_recv(int socketfd, void *buffer, size_t size)
{
  int rv;

  do {
    rv = recv(socketfd, buffer, size);
  } while (rv == -1 && errno == EINTR);

  while (rv == -1
         && (errno == EAGAIN || errno == EWOULDBLOCK))
  {
    if (wait_for_io(socketfd, 1) <= 0)
      return -1; // timeout ou erro!
    else
      do {
        rv = recv(socketfd, buffer, size);
      } while (rv == -1 && errno == EINTR);
  }

  return rv;
}

// Devolve 0 se tudo ok ou código de erro
// (-1 = erro "genérico").
int socket_connect(int socketfd, struct sockaddr *saddr,
                   size_t size)
{
  int rv;

  do {
    rv = connect(socketfd, saddr, size);
  } while (rv == -1 && errno == EINTR);

  // Já mandamos conectar... basta esperar pelo timeout...
  if (rv == -1
      && (errno == EINPROGRESS || errno == EALREADY))
  {
    // APR checa pela possibilidade de escrita
    // no descritor.
    if ((rv = wait_for_io(socketfd, 0)) > 0)
      return 0; // returna OK!
  }

// Se suportado, pega o código de erro do socket!
#ifdef SO_ERROR
  {
    int error;
    int len = sizeof(error);

    if ((rv = getsockopt(socketfd, SOL_SOCKET,
                SO_ERROR, &error, &len)) == -1)
      return errno;
    else
      return error;
  }
#endif

  return -1;
}

// Retorna 0 se timeout, -1 se erro ou
// > 0 se o evento ocorreu.
static int wait_for_io(int socketfd, int for_input)
{
  // Defini aqui o timeout como 1 segundo, pode
  // ser maior!
  #define TIMEOUT 1000

  struct pollfd pfd = {
    .fd = socketfd,
    .events = for_input ? POLLIN : POLLOUT
  };

  int rv;

  do {
    rv = poll(&pfd, 1, TIMEOUT);
  } while (rv == -1 && errno == EINTR);

  return rv;
}
Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s