-
-
Save silvioprog/c58853b91e32eddeee09 to your computer and use it in GitHub Desktop.
Socket não bloqueante com notificação de eventos usando epoll()
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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 <signal.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; | |
} | |
/* static 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. | |
*/ | |
static 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(). | |
*/ | |
static void ev_clienteconecta(int epoll, int sock); | |
static void ev_clientedadosdisp(int sock); | |
/* Variáveis globais. Declaradas assim para poderem ser liberadas caso | |
* uma interrupção termine a aplicação. | |
*/ | |
int servidor, i_epoll; | |
struct epoll_event *i_eventos; | |
/* void sigcallback(int sig) | |
* Função para capturar sinais de término da aplicação e | |
* efetuar a limpeza dos recursos alocados. | |
*/ | |
void sigcallback(int sig) { | |
close(servidor); | |
close(i_epoll); | |
free(i_eventos); | |
fprintf(stdout, "Server Terminated.\n"); | |
exit(sig); | |
} | |
/* 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 status; | |
// Seta manipuladores para os sinais de término da aplicação. | |
signal(SIGINT, sigcallback); // Ctrl+C | |
signal(SIGTERM, sigcallback); // Comando "kill" | |
// 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() | |
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(i_eventos); | |
close(servidor); | |
close(i_epoll); | |
return EXIT_SUCCESS; | |
} | |
/* static void ev_clienteconecta(int epoll, int sock) | |
* Função referenciada cada vez que novos clientes estão aguardando por conexão | |
* ao servidor. | |
*/ | |
static 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; | |
} | |
} | |
/* static 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. | |
*/ | |
static 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); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment