No desenvolvimento de servidores que lidam com múltiplos clientes, a implementação de um modelo de pool de threads pode envolver bloquear threads na operação de accept ou na leitura de dados. Bloquear na accept geralmente resulta em uma abordagem mais simples.
Abordagem 1: Bloqueando na except com sincronização via join de threads
Nesta técnica, múltiplas threads são designadas para executar a accept em um socket de escuta. Cada thread bloqueia até receber uma nova conexão, após o que lida com a comunicação do cliente.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <errno.h>
void * gerenciar_conexao(void * arg) {
int socket_escuta = *((int *)arg);
struct sockaddr_in endereco_cliente;
socklen_t tamanho_cliente = sizeof(endereco_cliente);
printf("Socket de escuta associado: %d\n", socket_escuta);
while (1) {
printf("Aguardando conexão de cliente...\n");
int socket_cliente = accept(socket_escuta, (struct sockaddr *)&endereco_cliente, &tamanho_cliente);
if (socket_cliente == -1) {
perror("Falha ao aceitar conexão");
continue;
}
char buffer_dados[1024];
memset(buffer_dados, 0, sizeof(buffer_dados));
while (1) {
printf("Thread ID %ld: Conexão estabelecida, iniciando leitura de dados...\n", pthread_self());
ssize_t tamanho_lido = read(socket_cliente, buffer_dados, sizeof(buffer_dados));
if (tamanho_lido > 0) {
printf("Recebidos %zd bytes de dados\n", tamanho_lido);
} else if (tamanho_lido == 0) {
printf("Cliente desconectou normalmente\n");
break;
} else {
perror("Erro durante a leitura");
break;
}
}
close(socket_cliente);
}
return NULL;
}
int main() {
struct sockaddr_in servidor;
memset(&servidor, 0, sizeof(servidor));
servidor.sin_family = AF_INET;
servidor.sin_addr.s_addr = htonl(INADDR_ANY);
servidor.sin_port = htons(8888);
int socket_escuta = socket(AF_INET, SOCK_STREAM, 0);
if (bind(socket_escuta, (struct sockaddr *)&servidor, sizeof(servidor)) == -1) {
perror("Erro ao vincular socket");
return 1;
}
listen(socket_escuta, 128);
pthread_t pool_threads[2];
for (int i = 0; i < 2; i++) {
if (pthread_create(&pool_threads[i], NULL, gerenciar_conexao, (void *)&socket_escuta) != 0) {
perror("Criação de thread falhou");
}
}
for (int i = 0; i < 2; i++) {
pthread_join(pool_threads[i], NULL);
}
close(socket_escuta);
return 0;
}
Abordagem 2: Bloqueando threads com semáforo e aceitação no thread principal
Esta abordagem utiliza um semáforo para coordenar a execução. Threads secundárias bloqueiam aguardando o sinal do semáforo, enquanto o thread principal realiza a accept. Cada nova conexão acionada libera uma thread para processamento.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <semaphore.h>
sem_t semaforo_conexoes;
void * processar_dados_cliente(void * arg) {
pthread_detach(pthread_self());
sem_wait(&semaforo_conexoes);
int socket_cliente = *((int *)arg);
char buffer_recebimento[1024];
memset(buffer_recebimento, 0, sizeof(buffer_recebimento));
while (1) {
printf("Thread %ld: Iniciando processamento para o socket %d...\n", pthread_self(), socket_cliente);
ssize_t bytes_recebidos = read(socket_cliente, buffer_recebimento, sizeof(buffer_recebimento));
if (bytes_recebidos > 0) {
printf("Dados lidos: %zd bytes\n", bytes_recebidos);
} else if (bytes_recebidos == 0) {
printf("Fim da transmissão de dados do cliente\n");
break;
} else {
perror("Erro na operação de leitura");
break;
}
}
close(socket_cliente);
return NULL;
}
int main() {
struct sockaddr_in servidor;
memset(&servidor, 0, sizeof(servidor));
servidor.sin_family = AF_INET;
servidor.sin_addr.s_addr = htonl(INADDR_ANY);
servidor.sin_port = htons(8888);
int socket_escuta = socket(AF_INET, SOCK_STREAM, 0);
if (bind(socket_escuta, (struct sockaddr *)&servidor, sizeof(servidor)) == -1) {
perror("Erro no bind do socket");
return 1;
}
listen(socket_escuta, 128);
sem_init(&semaforo_conexoes, 0, 0);
pthread_t threads_trabalhadoras[2];
int descritor_cliente = 0;
for (int i = 0; i < 2; i++) {
if (pthread_create(&threads_trabalhadoras[i], NULL, processar_dados_cliente, (void *)&descritor_cliente) != 0) {
perror("Falha ao criar thread");
}
}
struct sockaddr_in cliente_remoto;
socklen_t tamanho_endereco;
while (1) {
descritor_cliente = accept(socket_escuta, (struct sockaddr *)&cliente_remoto, &tamanho_endereco);
if (descritor_cliente == -1) {
perror("Erro na aceitação de conexão");
continue;
}
printf("Nova conexão aceita com descritor: %d\n", descritor_cliente);
sem_post(&semaforo_conexoes);
}
close(socket_escuta);
sem_destroy(&semaforo_conexoes);
return 0;
}