Fundamentos do Protocolo TCP
O Transmission Control Protocol (TCP) atua como a espinha dorsal da camada de transporte em redes de computadores, garantindo a entrega confiável de fluxos de bytes entre processos em hosts distintos. Diferente de protocolos orientados a datagramas, o TCP estabelece um canal de comunicação dedicado antes de qualquer troca de dados.
Características Arquiteturais
- Orientado a Conexão: Exige um handshake inicial para sincronização de estados e um processo formal de encerramento.
- Confiabilidade Extrema: Implementa mecanismos para mitigar perdas, duplicações e reordenações de pacotes na rede subjacente.
- Comunicação Ponto a Ponto: Opera estritamente entre dois endpoints, não suportando nativmaente multicast ou broadcast.
- Fluxo de Bytes Contínuo: Não preserva as limitações de mensagens da camada de aplicação; os dados são tratados como um rio contínuo de octetos.
- Operação Full-Duplex: Permite que ambas as extremidades transmitam e recebam dados simultaneamente através do mesmo canal lógico.
Mecanismos de Garantia de Confiabilidade
Como o protocolo IP subjacente é inerentemente não confiável (best-effort), o TCP introduz uma série de algoritmos para assegurar a integridade e a ordem dos dados:
- Números de Sequência: Cada byte transmitido recebe um identificador de 32 bits. Isso permite que o receptor remonte o fluxo na ordem correta e descarte duplicatas. O Número de Sequência Inicial (ISN) é gerado aleatoriamente durante o handshake.
- Confirmações Cumulativas (ACK): O receptor envia ACKs indicando o próximo byte esperado. A ausência de um ACK dentro de um intervalo calculado (RTO) dispara a retransmissão do segmento pelo emissor.
- Somas de Verificação (Checksum): Um campo de 16 bits calculado sobre o cabeçalho, os dados e um pseudo-cabeçalho IP. Se a validação falhar no destino, o segmento é silencitamente descartado.
- Controle de Fluxo via Janela Deslizante: O receptor anuncia dinamicamente o espaço disponível em seu buffer de kernel (Receive Window). O emissor ajusta sua taxa de injeção de dados para evitar o transbordamento do buffer do par.
Anatomia do Cabeçalho TCP
O cabeçalho TCP possui um tamanho base de 20 bytes, podendo chegar a 60 bytes com a inclusão de opções. Os campos críticos incluem:
- Portas (Origem e Destino): Campos de 16 bits que identificam os processos nas extremidades.
- Offset de Dados: Indica o tamanho do cabeçalho em palavras de 32 bits, essencial para localizar o início do payload.
- Flags de Controle: Bits que ditam o propósito do segmento:
SYN: Inicia a sincronização de sequências.ACK: Valida o campo de número de confirmação.FIN: Sinaliza o fim da transmissão de dados por uma das partes.RST: Aborta a conexão imediatamente devido a erros críticos.PSH: Instrui a pilha de rede a entregar os dados à aplicação imediatamente, sem aguardar o preenchimento do buffer.URG: Indica a presença de dados fora de banda, guiados pelo Ponteiro de Urgência.
Ciclo de Vida e Máquina de Estados
Estabelecimento (Three-Way Handshake)
- O cliente envia um segmento
SYNcom seu ISN e transita paraSYN_SENT. - O servidor responde com
SYN-ACK, confirmando o ISN do cliente e enviendo seu próprio ISN, entrando emSYN_RCVD. - O cliente finaliza com um
ACKpara o ISN do servidor. Ambos transitam paraESTABLISHED.
Encerramento (Four-Way Handshake)
Como a comunicação é full-duplex, cada direção deve ser fechada independentemente. A parte que inicia o fechamento envia um FIN e entra em FIN_WAIT_1. Após a troca de ACKs e do FIN da parte passiva, o iniciador entra no estado TIME_WAIT por um período de 2MSL (Maximum Segment Lifetime), garantindo que pacotes retardatários na rede sejam drenados antes que a porta possa ser reutilizada.
API de Soquetes POSIX para TCP
A interação com a pilha de rede em sistemas Unix-like é realizada através de chamadas de sistema padronizadas. Abaixo estão as assinaturas fundamentais reestruturadas para clareza:
/**
* Cria um endpoint de comunicação (soquete).
* @param family Família de protocolos (ex: AF_INET para IPv4).
* @param type Tipo do soquete (SOCK_STREAM para TCP).
* @param protocol Protocolo específico (0 para seleção automática).
* @return Descritor de arquivo do soquete ou -1 em caso de falha.
*/
int socket(int family, int type, int protocol);
/**
* Inicia uma conexão ativa em um soquete de fluxo.
* @param sock_fd Descritor do soquete criado.
* @param target_addr Estrutura contendo IP e porta do destino.
* @param addr_len Tamanho da estrutura de endereço.
* @return 0 em caso de sucesso, -1 em caso de erro.
*/
int connect(int sock_fd, const struct sockaddr *target_addr, socklen_t addr_len);
/**
* Associa um soquete a um endereço e porta locais.
* @param sock_fd Descritor do soquete.
* @param local_addr Estrutura com o IP e porta de bind.
* @param addr_len Tamanho da estrutura.
* @return 0 em sucesso, -1 em erro.
*/
int bind(int sock_fd, const struct sockaddr *local_addr, socklen_t addr_len);
/**
* Coloca o soquete em modo de escuta passiva.
* @param sock_fd Descritor do soquete vinculado.
* @param backlog Tamanho máximo da fila de conexões pendentes.
* @return 0 em sucesso, -1 em erro.
*/
int listen(int sock_fd, int backlog);
/**
* Extrai a primeira conexão da fila e cria um novo soquete para comunicação.
* @param sock_fd Descritor do soquete de escuta.
* @param peer_addr [Saída] Endereço do cliente que conectou.
* @param addr_len [Entrada/Saída] Tamanho da estrutura de endereço.
* @return Novo descritor de arquivo para a sessão ou -1 em erro.
*/
int accept(int sock_fd, struct sockaddr *peer_addr, socklen_t *addr_len);
/**
* Transmite dados através de uma conexão estabelecida.
* @param sock_fd Descritor da sessão ativa.
* @param buffer Ponteiro para os dados a serem enviados.
* @param length Quantidade de bytes a transmitir.
* @param flags Modificadores de comportamento (ex: MSG_OOB).
* @return Número de bytes efetivamente enviados ou -1 em erro.
*/
ssize_t send(int sock_fd, const void *buffer, size_t length, int flags);
/**
* Lê dados do buffer de recebimento do soquete.
* @param sock_fd Descritor da sessão ativa.
* @param buffer Destino para os dados lidos.
* @param length Capacidade máxima do buffer de destino.
* @param flags Modificadores de leitura.
* @return Bytes lidos, 0 se a conexão foi encerrada graciosamente, ou -1 em erro.
*/
ssize_t recv(int sock_fd, void *buffer, size_t length, int flags);
Gerenciamento de Buffers de Kernel
O kernel do sistema operacional mantém buffers circulares para envio (SO_SNDBUF) e recebimento (SO_RCVBUF). O tamanho desses buffers impacta diretamente a janela TCP e, consequentemente, a vazão (throughput) da conexão. A manipulação é feita via setsockopt e getsockopt. É crucial notar que o kernel do Linux frequentemente dobra o valor solicitado pelo usuário para acomodar metadados internos de gerenciamento.
Processamento de Dados Urgentes (Out-of-Band)
O TCP permite a transmissão de dados "fora de banda" (OOB) para sinalizar eventos críticos que devem bypassar a fila de dados normais. Na implementação BSD (padrão no Linux), apenas o último byte do segmento marcado com a flag URG é tratado como dado urgente. A notificação de chegada é feita via sinal SIGURG.
Servidor com Tratamento de OOB via Sigaction
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <pthread.h>
#define LISTEN_PORT 9090
#define BUFFER_CAPACITY 1024
int active_socket;
void handle_urgent_data(int signum) {
char urgent_byte;
ssize_t bytes_read = recv(active_socket, &urgent_byte, 1, MSG_OOB);
if (bytes_read > 0) {
printf("\n[Dados Urgentes Recebidos]: %c\n", urgent_byte);
fflush(stdout);
}
}
void* process_standard_stream(void* arg) {
char stream_buffer[BUFFER_CAPACITY];
while (1) {
ssize_t received = recv(active_socket, stream_buffer, BUFFER_CAPACITY - 1, 0);
if (received <= 0) {
if (received == 0) printf("[Conexão encerrada pelo cliente]\n");
else if (errno != EINTR) perror("Erro na leitura");
break;
}
stream_buffer[received] = '\0';
printf("[Dados Normais]: %s\n", stream_buffer);
if (strncmp(stream_buffer, "exit", 4) == 0) break;
}
return NULL;
}
int main() {
int listening_socket;
struct sockaddr_in server_config, client_config;
socklen_t client_len = sizeof(client_config);
pthread_t worker_thread;
struct sigaction sa;
listening_socket = socket(AF_INET, SOCK_STREAM, 0);
int reuse = 1;
setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
memset(&server_config, 0, sizeof(server_config));
server_config.sin_family = AF_INET;
server_config.sin_addr.s_addr = INADDR_ANY;
server_config.sin_port = htons(LISTEN_PORT);
bind(listening_socket, (struct sockaddr*)&server_config, sizeof(server_config));
listen(listening_socket, 5);
printf("Aguardando conexões na porta %d...\n", LISTEN_PORT);
active_socket = accept(listening_socket, (struct sockaddr*)&client_config, &client_len);
// Configurar sigaction para SIGURG (mais robusto que signal())
sa.sa_handler = handle_urgent_data;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGURG, &sa, NULL);
fcntl(active_socket, F_SETOWN, getpid());
pthread_create(&worker_thread, NULL, process_standard_stream, NULL);
pthread_join(worker_thread, NULL);
close(active_socket);
close(listening_socket);
return 0;
}
Cliente com Envio de OOB
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define TARGET_PORT 9090
#define INPUT_LIMIT 1024
int main() {
int client_sock;
struct sockaddr_in target_server;
char user_input[INPUT_LIMIT];
client_sock = socket(AF_INET, SOCK_STREAM, 0);
memset(&target_server, 0, sizeof(target_server));
target_server.sin_family = AF_INET;
target_server.sin_port = htons(TARGET_PORT);
inet_pton(AF_INET, "127.0.0.1", &target_server.sin_addr);
if (connect(client_sock, (struct sockaddr*)&target_server, sizeof(target_server)) < 0) {
perror("Falha ao conectar");
exit(1);
}
printf("Conectado. Comandos:\n");
printf(" 'urg:<char>' para enviar dado urgente.\n");
printf(" 'exit' para finalizar.\n");
while (1) {
printf("> ");
if (!fgets(user_input, INPUT_LIMIT, stdin)) break;
user_input[strcspn(user_input, "\n")] = 0;
if (strncmp(user_input, "urg:", 4) == 0 && strlen(user_input) == 5) {
char oob_char = user_input[4];
send(client_sock, &oob_char, 1, MSG_OOB);
printf("Enviado dado urgente: %c\n", oob_char);
} else if (strcmp(user_input, "exit") == 0) {
send(client_sock, user_input, strlen(user_input), 0);
break;
} else {
send(client_sock, user_input, strlen(user_input), 0);
}
}
close(client_sock);
return 0;
}
Implementação de Timeouts em Operações de I/O
Em ambientes de produção, operações de rede bloqueantes indefinidas são inaceitáveis. O uso de SO_RCVTIMEO e SO_SNDTIMEO através de setsockopt permite definir limites máximos de espera para chamadas como recv e send. Quando o tempo expira, a chamada retorna -1 e errno é configurado para EAGAIN ou EWOULDBLOCK.
Servidor Echo Multithread
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#define SERVICE_PORT 9091
#define MAX_PAYLOAD 1024
void* handle_client_session(void* socket_ptr) {
int session_fd = *(int*)socket_ptr;
free(socket_ptr);
char payload[MAX_PAYLOAD];
pthread_detach(pthread_self());
while (1) {
ssize_t bytes_in = recv(session_fd, payload, MAX_PAYLOAD - 1, 0);
if (bytes_in <= 0) {
if (bytes_in == 0) printf("Sessão %d finalizada.\n", session_fd);
break;
}
payload[bytes_in] = '\0';
send(session_fd, payload, bytes_in, 0);
if (strstr(payload, "terminate") != NULL) break;
}
close(session_fd);
return NULL;
}
int main() {
int main_socket;
struct sockaddr_in host_addr, peer_addr;
socklen_t peer_len = sizeof(peer_addr);
pthread_t thread_id;
main_socket = socket(AF_INET, SOCK_STREAM, 0);
int opt_flag = 1;
setsockopt(main_socket, SOL_SOCKET, SO_REUSEADDR, &opt_flag, sizeof(opt_flag));
memset(&host_addr, 0, sizeof(host_addr));
host_addr.sin_family = AF_INET;
host_addr.sin_addr.s_addr = INADDR_ANY;
host_addr.sin_port = htons(SERVICE_PORT);
bind(main_socket, (struct sockaddr*)&host_addr, sizeof(host_addr));
listen(main_socket, 10);
printf("Servidor Echo ativo na porta %d\n", SERVICE_PORT);
while (1) {
int* new_fd = malloc(sizeof(int));
*new_fd = accept(main_socket, (struct sockaddr*)&peer_addr, &peer_len);
if (*new_fd < 0) {
free(new_fd);
continue;
}
pthread_create(&thread_id, NULL, handle_client_session, new_fd);
}
close(main_socket);
return 0;
}
Cliente com Controle de Timeout de Leitura
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <errno.h>
#define REMOTE_HOST "127.0.0.1"
#define REMOTE_PORT 9091
#define MSG_BUFFER 1024
#define TIMEOUT_SECONDS 5
int configure_receive_timeout(int sock, int seconds) {
struct timeval tv;
tv.tv_sec = seconds;
tv.tv_usec = 0;
return setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
}
int main() {
int net_socket;
struct sockaddr_in remote_addr;
char out_msg[MSG_BUFFER], in_msg[MSG_BUFFER];
net_socket = socket(AF_INET, SOCK_STREAM, 0);
if (configure_receive_timeout(net_socket, TIMEOUT_SECONDS) < 0) {
perror("Erro ao configurar timeout");
exit(1);
}
memset(&remote_addr, 0, sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
remote_addr.sin_port = htons(REMOTE_PORT);
inet_pton(AF_INET, REMOTE_HOST, &remote_addr.sin_addr);
if (connect(net_socket, (struct sockaddr*)&remote_addr, sizeof(remote_addr)) < 0) {
perror("Conexão recusada");
exit(1);
}
printf("Conectado. Digite 'terminate' para sair.\n");
while (1) {
printf("Mensagem: ");
if (!fgets(out_msg, MSG_BUFFER, stdin)) break;
out_msg[strcspn(out_msg, "\n")] = 0;
send(net_socket, out_msg, strlen(out_msg), 0);
if (strcmp(out_msg, "terminate") == 0) break;
ssize_t read_bytes = recv(net_socket, in_msg, MSG_BUFFER - 1, 0);
if (read_bytes < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("Timeout: Nenhuma resposta em %d segundos.\n", TIMEOUT_SECONDS);
} else {
perror("Erro de leitura");
}
break;
} else if (read_bytes == 0) {
printf("Servidor desconectou.\n");
break;
}
in_msg[read_bytes] = '\0';
printf("Resposta: %s\n", in_msg);
}
close(net_socket);
return 0;
}