@@ -0,0 +1,262 @@
/* Copyright 2012 Eduardo Rolim
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <errno.h>
#define TAMBUFFER 512
#define PORTA 9899
#define QTDEVENTOS 64
#define CHUCKNORRIS (x ) perror(x); exit(EXIT_FAILURE);
#define DEBUG (x ) fprintf(stdout, "%d: %s", __LINE__, x)
/* static int cria_servidor(int porta)
* Função para criação simples de socket servidor para IPv4.
* Para criação de um socket para funcionar tanto em IPv4 quanto em IPv6
* Consulte a seguinte página: http://linux.die.net/man/3/getaddrinfo
*/
static int cria_servidor (int porta ) {
struct sockaddr_in endereco ;
int sock , status ;
// Criando o socket servidor
sock = socket (PF_INET , SOCK_STREAM , 0 );
if (sock < 0 ) {
CHUCKNORRIS ("Erro de criação do socket." );
}
memset (& endereco , 0 , sizeof (endereco ));
endereco .sin_family = AF_INET ;
endereco .sin_port = htons (porta );
endereco .sin_addr .s_addr = htonl (INADDR_ANY );
// Linkando o socket a um endereço (como é um servidor, nenhum em especial)
status = bind (sock , (struct sockaddr * ) & endereco , sizeof (endereco ));
if (status != 0 ) {
CHUCKNORRIS ("Erro de ligação do socket." );
}
return sock ;
}
/* static int nonblock_socket(int sock)
* Função que converte o socket para o modo não bloqueante.
*/
static int nonblock_socket (int sock ) {
int flags , status ;
flags = fcntl (sock , F_GETFL , 0 );
if (flags == -1 ) {
DEBUG ("fcntl get" );
return -1 ;
}
flags |= O_NONBLOCK ;
status = fcntl (sock , F_SETFL , flags );
if (status == -1 ) {
DEBUG ("fcntl set" );
return -1 ;
}
return 0 ;
}
/* int cria_evento(int epoll, int socket)
* Função refatorada de main() para adicionar novos sockets ao epoll() usando o
* método Edge-triggered através da flag EPOLLET.
*/
int cria_evento (int epoll , int sock ) {
int status ;
struct epoll_event i_evento ;
// Criando um evento em "epoll" para o descritor "sock"
i_evento .data .fd = sock ;
i_evento .events = EPOLLIN | EPOLLET ;
status = epoll_ctl (epoll , EPOLL_CTL_ADD , sock , & i_evento );
if (status == -1 ) {
CHUCKNORRIS ("cria epoll_ctl" );
}
return status ;
}
/* Cabeçalhos para funções implementadas depois de main().
*/
void ev_clienteconecta (int epoll , int sock );
void ev_clientedadosdisp (int sock );
/* int main(int argc, char *argv[])
* Função principal da aplicação, onde o looping de eventos das notificações
* gerenciadas pelo epoll() está inserido.
*/
int main (int argc , char * argv []) {
int servidor , status , i_epoll ;
struct epoll_event * eventos ;
// Criando o servidor na porta designada
servidor = cria_servidor (PORTA );
// Mudando o servidor para não bloqueante
status = nonblock_socket (servidor );
if (status == -1 ) {
exit (EXIT_FAILURE );
}
// Iniciando a escura por novas conexões
status = listen (servidor , SOMAXCONN );
if (status != 0 ) {
CHUCKNORRIS ("Erro ao setar socket no modo escuta." );
}
// Criando uma instância de epoll()
i_epoll = epoll_create1 (0 );
if (i_epoll == -1 ) {
CHUCKNORRIS ("epoll_create1" );
}
// Criando um evento para monitorar a chegada de novas conexões
cria_evento (i_epoll , servidor );
// Iniciando o looping de eventos para epoll()
struct epoll_event * i_eventos ;
i_eventos = calloc (QTDEVENTOS , sizeof (struct epoll_event ));
while (1 ) {
int n , i ;
n = epoll_wait (i_epoll , i_eventos , QTDEVENTOS , -1 );
for (i = 0 ; i < n ; i ++ ) {
if ((i_eventos [i ].events & EPOLLERR ) ||
(i_eventos [i ].events & EPOLLHUP ) ||
(!(i_eventos [i ].events & EPOLLIN ))) {
/* Algum erro ocorreu com o socket ou ele não está pronto.
* Segundo o man de epoll_ctl, epoll() sempre notificará para os eventos
* EPOLLERR e EPOLLHUP, independente de setados ou não.
*/
fprintf (stderr , "erro no epoll" );
close (i_eventos [i ].data .fd );
continue ;
} else if (servidor == i_eventos [i ].data .fd ) {
/* Recebemos uma notificação no socket servidor, o que significa que há
* conexões a serem tratadas.
*/
ev_clienteconecta (i_epoll , i_eventos [i ].data .fd );
continue ;
} else {
/* Recebemos uma notificação de que há dados para serem lidos em um
* socket cliente. Precisamos ler todos os dados disponíveis, já que
* estamos rodando no modo edge-triggered e não receberemos novas
* notificações para dados ainda não lidos.
*/
ev_clientedadosdisp (i_eventos [i ].data .fd );
continue ;
}
}
}
free (eventos );
close (servidor );
return EXIT_SUCCESS ;
}
/* void ev_clienteconecta(int epoll, int sock)
* Função referenciada cada vez que novos clientes estão aguardando por conexão
* ao servidor.
*/
void ev_clienteconecta (int epoll , int sock ) {
struct sockaddr in_addr ;
socklen_t in_len ;
int status , cliente ;
char host [NI_MAXHOST ], porta [NI_MAXSERV ];
while (1 ) {
in_len = sizeof (in_addr );
cliente = accept (sock , & in_addr , & in_len );
if (cliente == -1 ) {
if ((errno == EAGAIN ) ||
(errno == EWOULDBLOCK )) {
//Todas as novas conexões processadas.
break ;
} else {
perror ("accept" );
break ;
}
}
status = getnameinfo (& in_addr , in_len ,
host , sizeof (host ),
porta , sizeof (porta ),
NI_NUMERICHOST | NI_NUMERICSERV );
if (status == 0 ) {
printf ("Conexao cliente aceita no descritor %d "
"(host='%s', porta='%s').\n" , cliente , host , porta );
}
// Tornando a conexão cliente não bloqueante
status = nonblock_socket (cliente );
if (status == -1 ) {
exit (EXIT_FAILURE );
}
//Adicionando a conexão ao epoll
cria_evento (epoll , cliente );
continue ;
}
}
/* void ev_clientedadosdisp(int sock)
* Função referenciada cada vez que há dados em um cliente aguardando para ser
* lido com read().
* Aviso: operação unsafe em write() precisa ser corrigida para evitar que buffers
* maiores que 512 bytes disparem duas chamadas seguidas, o que gerará o aviso
* EWOULDBLOCK na mesma.
*/
void ev_clientedadosdisp (int sock ) {
int concluido = 0 ;
ssize_t qtd ;
char buf [TAMBUFFER ];
while (1 ) {
qtd = read (sock , buf , sizeof (buf ));
if (qtd == -1 ) {
// Se o erro for EAGAIN significa que todos os dados foram lidos.
if (errno != EAGAIN ) {
perror ("read" );
concluido = 1 ;
}
break ;
} else if (qtd == 0 ) {
// Fim do arquivo. Socket foi fechado pelo cliente remoto.
concluido = 1 ;
break ;
}
// Enviando de volta para o cliente remoto (unsafe).
qtd = write (sock , buf , qtd );
if (qtd == -1 ) {
perror ("write" );
}
}
if (concluido ) {
printf ("Conexao cliente terminada no descritor %d.\n" , sock );
close (sock );
}
}