Introdução

O TCP tem um papel extremamente importante no que se diz a transporte de informação confiável. Essa necessidade existe pois o protocolo IP não consegue garantir a entrega (e muito menos ordenação) dos pacotes e, para muitos serviços, essas características são essenciais. Para cumprir essa necessidade, o TCP implementa mecanismos de garantia de entrega e ordenação dos pacotes.

Princípios da transferência confiável de dados

Para compreendermos como o protocolo TCP funciona, precisamos começar criar abstrações sobre como uma comunicação confiável deve funcionar. Para fins didáticos, iremos utilizar das abstrações de RDT (Reliable Data Transfer).

RDT 1.0 - O cenário perfeito

Esse é um cenário perfeito, onde não existe perda ou corrupção de informações/pacotes. Nesse âmbito, o remetente pode enviar quantos pacotes quiser para destinatário, e existe a garantia da entrega e ordenação desses pacotes. Num canal perfeitamente confiável, o destinatário não precisa fornecer informação nenhuma ao remetente, já que nada pode dar errado.

RDT 2.0 - Erros de bits

Um modelo um pouco mais realista, sempre vai existir a possibilidade que os dados sejam corrompidos durante o tráfego até o destinatário. Devido a essa complicação, surge a necessidade do destinatário informar ao remetente se ele recebeu o pacote corretamente ou corrompido de alguma maneira. Isso seria equivalente a alguém respondendo “OK”, e “NÃO ENTENDI” diante de um dialogo.

Assim, o remetente envia o pacote ao destinatário, que responderá com ACK (acknowledged) caso tenha recebido o pacote íntegro, ou NAK (not acknowledged). Assim, o remetente, baseado na resposta do destinatário, saberá se ele deve reenviar o pacote ou encaminhar o outro.

Esse método funciona, mas estamos desconsiderando um ponto importante: os pacotes ACK e NAK podem estar corrompidos. Para isso, surge o RDT 2.1.

RDT 2.1 - Erros de bits em ACK e NAK

Continuando a analogia do dialogo, podemos imaginar que a corrupção dos pacotes ACK ou NAK num dialogo seria equivalente a pessoa que estivesse falando não entendesse o “OK” ou “NÃO ENTENDI”. Nesse cenário, essa pessoa poderia responder com um “VOCÊ PODE REPETIR?”, mas isso também poderia ser corrompido, e a outra pessoa poderia responder com um “VOCÊ PODE REPETIR?“… Fica claro que isso está entrando num caminho difícil.

Então, para se evitar isso, podemos apenas reenviar os pacotes quando os pacotes ACK e NAK são corrompidos. Entretanto, isso introduz pacotes duplicados.

Pacotes duplicados são um problema grave no nosso protocolo, pois pode corromper todo o pacote final. Imaginemos o seguinte cenário:

sequenceDiagram
    Remetente->>Destinatario: PKT [0]
    Destinatario->>Remetente: ACK
    Remetente->>Destinatario: PKT [1]
    Destinatario->>Remetente: ACK
    Note left of Remetente: Pacote ACK perdido. Retransmitindo
    Remetente->>Destinatario: PKT [1]
    Note right of Destinatario: Recebe PKT [1]
    Note right of Destinatario: Estava esperando o PKT [2]
    Note right of Destinatario: ARQUIVO FINAL CORROMPIDO

RDT 2.2 - Pacotes duplicados

Para contornar o problema gerado pelo RDT 2.1, devemos utilizar uma implementação de números de sequência, que identificarão qual pacote será marcado como acknowledged. No RDT 2.2, esses números de sequência serão limitados apenas aos números 0 e 1, e o destinatário poderá responder com ACK 0 ou ACK 1, se referindo a qual pacote ele está sinalizando o recebimento.

sequenceDiagram
    Remetente->>Destinatario: PKT 0
    Destinatario->>Remetente: ACK 0
    Remetente->>Destinatario: PKT 1
    Destinatario->>Remetente: ACK 1
    Note left of Remetente: Pacote ACK perdido. Retransmitindo
    Remetente->>Destinatario: PKT 1
    Note right of Destinatario: Recebe PKT 1
    Note right of Destinatario: Estava esperando o PKT 0
    Note right of Destinatario: Pacote descartado
    Note right of Destinatario: Reenvia ACK 1
    Destinatario->>Remetente: ACK 1

RDT 2.3 - Removendo o NAK

Para finalizar a verão do RDT 2.x, podemos remover a necessidade do NAK, pois podemos sinalizar que não recebemos um pacote íntegro ao reenviar novamente o ACK referente ao último pacote que recebemos com integridade.

RDT 3.0

Entretanto, ainda falta um último problema que deve ser abordado nesse cenário: pacotes perdidos. O que o remetente (ou o destinatário) farão quando o pacote for completamente perdido? Para isso, é necessário introduzir o timeout no nosso sistema. O timeout serve justamente para reenviar pacotes possivelmente perdidos e, com isso, dar continuidade no protocolo.

O RDT 3.0 funciona?

Sim! O RDT 3.0 funciona perfeitamente. Mas existe um grande problema: ele é muito lento, pois não suporta paralelismo.

Paralelismo em transferência confiável de dados

Para sanar o problema da baixa velocidade do RDT 3.0, o paralelismo é introduzido ao protocolo TCP. Ele serve justamente para aumentar a quantidade de pacotes enviados simultaneamente ao destinatário. Para isso, o número de sequência também é alterado, e passa a representar o último conjunto de 8 bytes recebido com sucesso.

Para utilizar o paralelismo, é necessário, acima de qualquer coisa, estabelecer uma conexão. Para isso, existe uma apresentação de 3 vias (3-way handshake), que é criada justamente para “combinar” valores de variáveis entre o emissor o destinatário. Tais variáveis se referem ao buffer de envio da recepção e o tamanho máximo do segmento, para que todos os pacotes enviados do remetente sejam propriamente utilizados pelo destinatário.