Skip to content

Instantly share code, notes, and snippets.

@vndmtrx
Created August 17, 2012 18:01
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save vndmtrx/3381137 to your computer and use it in GitHub Desktop.
Save vndmtrx/3381137 to your computer and use it in GitHub Desktop.
Socket não bloqueante com notificação de eventos usando epoll()
/* 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