Fundamentos de TCP Sockets
No ambiente Linux, a comunicação em rede é abstraída através do conceito de Sockets. Um socket atua como um ponto final de comunicação entre dois processos. Como o Linux segue a filosofia de que "tudo é um arquivo", um socket é manipulado por meio de um descritor de arquivo, permitindo o uso de chamadas de sistema padronizadas para leitura e escrita de dados.
O TCP (Transmission Control Protocol) é o protocolo de transporte utilizado quando se exige confiabilidade, garantindo que os pacotes cheguem em ordem e sem erros através de uma conexão orientada a fluxo de bytes.
Fluxo de Comunicação TCP
Abaixo, detalhamos o ciclo de vida de uma conexão TCP entre um servidor e um cliente:
| Etapa | Servidor (Escuta) | Cliente (Ativo) |
|---|---|---|
| Inicialização | socket() |
socket() |
| Configuração | bind() |
— |
| Preparação | listen() |
— |
| Conexão | accept() |
connect() |
| Troca de Dados | recv() / send() |
send() / recv() |
| Encerramento | close() |
close() |
Implementação em Linguagem C
Exemplo de Servidor (listener.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#define PORT_NUM 8080
#define QUEUE_SIZE 10
int main() {
int srv_fd, new_conn;
struct sockaddr_in srv_addr, cli_addr;
socklen_t cli_len = sizeof(cli_addr);
char msg_buffer[2048];
// Criando o descritor do socket
srv_fd = socket(AF_INET, SOCK_STREAM, 0);
if (srv_fd < 0) {
perror("Falha ao criar socket");
exit(EXIT_FAILURE);
}
// Configurando endereço local
memset(&srv_addr, 0, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = INADDR_ANY;
srv_addr.sin_port = htons(PORT_NUM);
// Vinculando o socket à porta
if (bind(srv_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr)) < 0) {
perror("Erro no bind");
exit(EXIT_FAILURE);
}
// Escutando conexões
listen(srv_fd, QUEUE_SIZE);
printf("Aguardando conexões na porta %d...\n", PORT_NUM);
// Aceitando um novo cliente
new_conn = accept(srv_fd, (struct sockaddr*)&cli_addr, &cli_len);
if (new_conn > 0) {
printf("Conexão estabelecida com: %s\n", inet_ntoa(cli_addr.sin_addr));
ssize_t bytes_read = recv(new_conn, msg_buffer, sizeof(msg_buffer) - 1, 0);
if (bytes_read > 0) {
msg_buffer[bytes_read] = '\0';
printf("Mensagem recebida: %s\n", msg_buffer);
}
const char *reply = "Dados processados com sucesso.";
send(new_conn, reply, strlen(reply), 0);
}
close(new_conn);
close(srv_fd);
return 0;
}
Exemplo de Cliente (sender.c)
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int sock_fd;
struct sockaddr_in target_addr;
char response[1024];
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
target_addr.sin_family = AF_INET;
target_addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &target_addr.sin_addr);
if (connect(sock_fd, (struct sockaddr*)&target_addr, sizeof(target_addr)) == 0) {
const char *payload = "Olá do Cliente!";
send(sock_fd, payload, strlen(payload), 0);
recv(sock_fd, response, sizeof(response), 0);
printf("Resposta do Servidor: %s\n", response);
}
close(sock_fd);
return 0;
}
Funções Essenciais da API
- socket(): Aloca um recurso de rede no kernel.
AF_INETdefine IPv4 eSOCK_STREAMdefine TCP. - bind(): Associa o socket a um endereço IP e porta específicos no host local.
- listen(): Coloca o socket em modo passivo, pronto para enfileirar solicitações de conexão.
- accept(): Extrai a primeira solicitação da fila e cria um novo socket dedicado para essa conexão específica.
- connect(): Inicia o three-way handshake do TCP com o servidor remoto.
Configurações Avançadas com setsockopt
Para ajustar o comportamento do socket, utiliza-se a função setsockopt(). Uma configuração comum é a reutilização de endereços para evitar o erro "Address already in use" após reiniciar um servidor:
int habilitar = 1;
setsockopt(srv_fd, SOL_SOCKET, SO_REUSEADDR, &habilitar, sizeof(int));
Outras opções relevantes incluem:
TCP_NODELAY: Desativa o algoritmo de Nagle, enviando pacotes pequenos imediatamente (útil para baixa latência).SO_KEEPALIVE: Ativa o monitoramento da conexão para detectar se o parceiro ainda está ativo.SO_RCVBUF/SO_SNDBUF: Ajusta o tamanho dos buffers de recepção e envio do kernel.
Análise de Conexão e Diagnóstico
Para depurar aplicações de rede no Linux, as seguintes ferramentas são indispensáveis:
- ss -tlnp: Lista todos os sockets TCP em estado de escuta e os processos associados.
- netstat -at: Exibe o estado de todas as conexões TCP ativas.
- tcpdump -i any port 8080: Captura e analisa os pacotes trafegando em uma porta específica para validar o handshake.
- lsof -i :8080: Identifica qual processo está utilizando uma determinada porta.
Processo de Conexão (Handshake)
O TCP utiliza um processo de três etapas para garantir que ambos os lados estejam prontos:
- SYN: O cliente envia um pedido de sincronização.
- SYN-ACK: O servidor responde confirmando o recebimento e solicitando sincronização mútua.
- ACK: O cliente confirma o recebimento final e a conexão é estabelecida (ESTABLISHED).
O encerramento utiliza um processo similar de quatro etapas (FIN/ACK) para garantir que todos os dados pendenets sejam entregues antes do fechamento total do canal.