Guia Prático de Programação TCP Sockets no Linux

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_INET define IPv4 e SOCK_STREAM define 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:

  1. SYN: O cliente envia um pedido de sincronização.
  2. SYN-ACK: O servidor responde confirmando o recebimento e solicitando sincronização mútua.
  3. 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.

Tags: C TCP/IP Linux network-programming sockets

Publicado em 6-21 22:11