Explorando o Ciclo de Vida da Comunicação TCP: Handshake, Transferência de Dados e Desconexão

A comunicação via TCP (Transmission Control Protocol) é um processo colaborativo entre cliente e servidor, envolvendo um ciclo de vida completo para estabelecer, manter e encerrar conexões de forma confiável. Este artigo detalha as etapas cruciais: o estabelecimento da conexão através do "three-way handshake", a transferência bidirecional de dados e a liberação da conexão com o "four-way handshake".

O Ciclo de Comunicação TCP

O "Three-Way Handshake": Estabelecendo a Conexão

O processo de três vias garante que ambos os lados estejam prontos para se comunicar.

Perspectiva do Cliente (Iniciador)

  1. SYN (Synchronize): O cliente envia um pacote SYN com um número de sequência inicial (ex: seq=x) para o servidor, indicando seu desejo de iniciar uma conexão. O cliente entra no estado SYN_SENT.
  2. SYN-ACK (Synchronize-Acknowledge): Aguarda a resposta do servidor. Ao receber um pacote SYN-ACK (com seq=y e ack=x+1), o cliente confirma que o servidor está pronto.
  3. ACK (Acknowledge): O cliente envia um pacote ACK (com ack=y+1) para o servidor. A conexão agora está no estado ESTABLISHED.

Perspectiva do Servidor (Receptor]

  1. LISTEN: O servidor está escutando por conexões de entrada.
  2. SYN Received: Ao receber o SYN do cliente, o servidor aloca recursos e entra no estado SYN_RCVD.
  3. SYN-ACK: O servidor responde com um pacote SYN-ACK (seq=y, ack=x+1), indicando sua prontidão e confirmando o pacote do cliente.
  4. ESTABLISHED: Após receber o ACK final do cliente (ack=y+1), o servidor também entra no estado ESTABLISHED.

A Necessidade das Três Vias

As três vias são essenciais para:

  • Confirmação Mútua: Garantir que ambos os lados possam enviar e receber dados.
  • Prevenção de Conexões Inválidas: Evitar que pacotes SYN antigos e atrasados estabeleçam conexões espúrias com o servidor.
  • Alocação Eficiente de Recursos: O servidor só aloca recursos após ter uma confirmação inicial do cliente.

Transferência de Dados: Um Fluxo Bidirecional

Uma vez estabelecida a conexão, os dados fluem em ambas as direções simultaneamente (full-duplex).

Exemplo de Transferência de Dados

Lado do Cliente:


// Envio de requisição pelo cliente
escrever(socket_cliente, dados_requisicao, tamanho_requisicao);

// Recebimento da resposta do servidor
ler(socket_cliente, buffer_resposta, tamanho_buffer);
   

Lado do Servidor:


// Recebimento da requisição do cliente
ler(socket_conexao, buffer_requisicao, tamanho_buffer);

// Processamento e envio da resposta
escrever(socket_conexao, dados_resposta, tamanho_resposta);
   

Características importantes durante a transferência de dados incluem controle de fluxo (via janelas deslizantes), controle de congestionamento e garantia de entrega confiável através de números de sequência e retransmissões.

O "Four-Way Handshake": Encerrando a Conexão

A desconexão também é um processo ordenado para garantir que todos os dados tenham sido trnasmitidos.

Lado Ativo (Ex: Cliente Iniciando o Fechamento)

  1. FIN (Finish): O cliente envia um pacote FIN, indicando que não enviará mais dados. Entra em FIN_WAIT_1.
  2. ACK: Aguarda o ACK do servidor. Ao receber, entra em FIN_WAIT_2.
  3. FIN: Recebe o FIN do servidor.
  4. ACK Final: Envia um ACK para o FIN do serviodr, entrando no estado TIME_WAIT.

Lado Passivo (Ex: Servidor Recebendo o Fechamento)

  1. ACK: Recebe o FIN do cliente e responde imediatamente com um ACK. Entra em CLOSE_WAIT.
  2. CLOSE: Após processar e enviar todos os dados pendentes, o servidor chama close().
  3. FIN: Envia seu próprio pacote FIN. Entra em LAST_ACK.
  4. ACK: Recebe o ACK final do cliente e entra em CLOSED.

Por Que Quatro Vias?

Devido à natureza full-duplex do TCP, cada lado deve sinalziar independentemente que terminou de enviar dados. Um FIN de uma parte não impede que a outra parte envie seus últimos dados antes de também enviar seu FIN.

O estado TIME_WAIT no lado que iniciou o fechamento é crucial para garantir que o último ACK seja recebido e para limpar pacotes antigos que possam estar vagando pela rede.

Tratamento de Exceções

O TCP possui mecanismos para lidar com falhas:

  • Falhas no Handshake: Timeouts de SYN e ataques de SYN Flood.
  • Falhas na Transferência: Perda de pacotes (resolvida com retransmissão) e congestionamento de rede (resolvido com algoritmos de controle).
  • Falhas na Desconexão: Perda de pacotes FIN ou travamentos abruptos (detectados por mecanismos como Keepalive).

Exemplo de Implementação (Servidor e Cliente em C)

Servidor TCP


#include &ltstdio.h&gt
#include &ltstdlib.h&gt
#include &ltstring.h&gt
#include &ltunistd.h&gt
#include &ltarpa/inet.h&gt
#include &ltsys/socket.h&gt

#define PORTA 8080
#define TAMANHO_BUFFER 1024
#define BACKLOG 5

void lidar_com_cliente(int descriptor_conexao, struct sockaddr_in endereco_cliente) {
   char buffer[TAMANHO_BUFFER];
   ssize_t bytes_lidos;
   
   printf("Cliente %s:%d conectado\n", 
          inet_ntoa(endereco_cliente.sin_addr), 
          ntohs(endereco_cliente.sin_port));
   
   while ((bytes_lidos = read(descriptor_conexao, buffer, sizeof(buffer) - 1)) > 0) {
       buffer[bytes_lidos] = '\0';
       printf("Recebido de %s:%d: %s\n", 
              inet_ntoa(endereco_cliente.sin_addr),
              ntohs(endereco_cliente.sin_port),
              buffer);
       
       char resposta[TAMANHO_BUFFER];
       snprintf(resposta, sizeof(resposta), "Servidor recebeu: %s", buffer);
       
       write(descriptor_conexao, resposta, strlen(resposta));
       printf("Resposta enviada.\n");
   }
   
   if (bytes_lidos == 0) {
       printf("Cliente %s:%d desconectou.\n",
              inet_ntoa(endereco_cliente.sin_addr),
              ntohs(endereco_cliente.sin_port));
   } else {
       perror("Erro na leitura");
   }
   
   close(descriptor_conexao);
   printf("Conexão encerrada.\n\n");
}

int main() {
   int socket_escuta, descriptor_conexao;
   struct sockaddr_in addr_servidor, addr_cliente;
   socklen_t tamanho_cliente = sizeof(addr_cliente);
   
   if ((socket_escuta = socket(AF_INET, SOCK_STREAM, 0)) &lt 0) {
       perror("Falha na criação do socket");
       exit(EXIT_FAILURE);
   }
   printf("Socket criado.\n");
   
   int opt = 1;
   setsockopt(socket_escuta, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
   
   memset(&addr_servidor, 0, sizeof(addr_servidor));
   addr_servidor.sin_family = AF_INET;
   addr_servidor.sin_addr.s_addr = INADDR_ANY;
   addr_servidor.sin_port = htons(PORTA);
   
   bind(socket_escuta, (struct sockaddr*)&addr_servidor, sizeof(addr_servidor));
   printf("Porta %d vinculada.\n", PORTA);
   
   listen(socket_escuta, BACKLOG);
   printf("Aguardando conexões...\n\n");
   
   while (1) {
       if ((descriptor_conexao = accept(socket_escuta, (struct sockaddr*)&addr_cliente, &tamanho_cliente)) &lt 0) {
           perror("Falha no accept");
           continue;
       }
       
       lidar_com_cliente(descriptor_conexao, addr_cliente);
   }
   
   close(socket_escuta);
   return 0;
}
   

Cliente TCP


#include &ltstdio.h&gt
#include &ltstdlib.h&gt
#include &ltstring.h&gt
#include &ltunistd.h&gt
#include &ltarpa/inet.h&gt
#include &ltsys/socket.h&gt

#define IP_SERVIDOR "127.0.0.1"
#define PORTA 8080
#define TAMANHO_BUFFER 1024

void comunicar_com_servidor(int socket_cliente) {
   char buffer[TAMANHO_BUFFER];
   ssize_t n;
   
   printf("Conectado ao servidor. Digite 'quit' para sair.\n");
   
   while (1) {
       printf("Mensagem: ");
       fgets(buffer, sizeof(buffer), stdin);
       buffer[strcspn(buffer, "\n")] = 0;
       
       if (strcmp(buffer, "quit") == 0) {
           printf("Fechando conexão...\n");
           break;
       }
       
       write(socket_cliente, buffer, strlen(buffer));
       printf("Enviado: %s\n", buffer);
       
       n = read(socket_cliente, buffer, sizeof(buffer) - 1);
       if (n &lt 0) {
           perror("Erro na leitura");
           break;
       } else if (n == 0) {
           printf("Servidor desconectou.\n");
           break;
       } else {
           buffer[n] = '\0';
           printf("Resposta do servidor: %s\n", buffer);
       }
   }
}

int main() {
   int socket_cliente;
   struct sockaddr_in addr_servidor;
   
   if ((socket_cliente = socket(AF_INET, SOCK_STREAM, 0)) &lt 0) {
       perror("Falha na criação do socket");
       exit(EXIT_FAILURE);
   }
   printf("Socket criado.\n");
   
   memset(&addr_servidor, 0, sizeof(addr_servidor));
   addr_servidor.sin_family = AF_INET;
   addr_servidor.sin_port = htons(PORTA);
   
   inet_pton(AF_INET, IP_SERVIDOR, &addr_servidor.sin_addr);
   
   printf("Conectando a %s:%d...\n", IP_SERVIDOR, PORTA);
   if (connect(socket_cliente, (struct sockaddr*)&addr_servidor, sizeof(addr_servidor)) &lt 0) {
       perror("Falha na conexão");
       close(socket_cliente);
       exit(EXIT_FAILURE);
   }
   printf("Conectado com sucesso!\n");
   
   comunicar_com_servidor(socket_cliente);
   
   printf("Fechando socket...\n");
   close(socket_cliente);
   
   return 0;
}
   

Tags: TCP handshake four-way handshake Network Programming sockets

Publicado em 7-8 17:08