Nesta prática, vamos implementar um protótipo de um protocolo compatível com o TCP. Focaremos na ponta do servidor. Este roteiro te guiará em passos para que você construa aos poucos uma implementação completa.
Ao longo de anos oferecendo essa disciplina, eu observo que os estudantes sempre preferem entregar o T1 em Python, então vamos assumir que vocês vão continuar usando a mesma linguagem. Caso decida usar outra linguagem, você terá de adaptar também os testes.
Sua implementação deve ser realizada no arquivo tcp.py
, que já veio com um esqueleto em cima do qual você vai construir o seu código. Para ajudar na sua implementação, você pode chamar as funções e usar os valores que já vieram declarados no arquivo tcputils.py. Pode ser útil, também, consultar a página sobre o formato do segmento TCP na Wikipedia. Os slides do livro-texto relevantes para este trabalho são os do Capítulo 3.
Para testar seu código, execute ./run-grader
. Cada um dos testes vai usar a sua implementação como uma biblioteca, verificando se ela apresenta o comportamento esperado.
Implemente o código necessário para aceitar uma conexão. O código base já veio com um if
para verificar se chegou um segmento com a flag SYN
. Como vimos no slide 80 do livro, isso significa que um cliente está querendo abrir uma nova conexão. Você deve responder ao SYN
com um SYN+ACK
para aceitar a conexão.
Os locais sugeridos para preencher seu código são no método _rdt_rcv
da classe Servidor
, ou então no construtor da classe Conexao
.
Lembre-se que, como resposta, o cliente vai enviar um segmento contendo a flag ACK
que, além disso, também já pode conter os primeiros dados enviados pelo cliente. Mas disso nós vamos tratar no próximo passo.
O recebimento de segmentos no TCP é similar ao GBN, descrito no slide 49 do livro. Ao tratar um segmento proveniente da camada de rede, assegure-se que ele não é duplicado e que foi recebido em ordem antes de repassá-lo para a camada de aplicação.
Por simplicidade, confirme cada segmento que for recebido corretamente mandando um segmento com a flag ACK
e com payload vazio.
Preencha o método _rdt_rcv
da classe Conexao
para implementar essa funcionalidade.
O funcionamento básico do envio de segmentos pelo TCP é descrito no slide 67 do livro. Vamos quebrar a implementação em duas partes.
Nesta primeira parte, vamos nos preocupar somente com o evento "dados recebidos da camada de aplicação acima". Ou seja, mantenha uma contagem correta do número de sequência a ser inserido no segmento e construa o segmento corretamente, enviando-o em seguida para a camada de rede.
Preencha o método enviar
da classe Conexao
para implementar essa funcionalidade.
Por simplicidade, mantenha a flag de ACK
sempre ligada nos segmentos que você enviar, incluindo corretamente o acknowledgement number correspondente ao próximo byte que você espera receber. Note que essa contagem refere-se ao funcionamento da nossa ponta como receptor, e não como transmissor, mas aqui misturamos os papéis.
Antes de prosseguirmos com a segunda parte da implementação da máquina de estados de envio, sugerimos dar uma pausa e tratar antes do fechamento de conexões. Assim, você terá um protótipo básico que poderá ser usado para conversar, por exemplo, com o nc
, desde que você esteja em uma rede ideal (sem perdas, com banda sobrando, etc.)
O fechamento de conexões é descrito no slide 83 do livro. Uma das pontas escolhe fechar a conexão, enviando um segmento com flag FIN
. A outra ponta confirma o recebimento do FIN
mandando um ACK
. Para que o encerramento da conexão ocorra de forma limpa, a outra ponta deve eventualmente fazer a mesma coisa (fechar a conexão enviando um FIN
).
Por simplicidade, você pode assumir que o cliente é o primeiro a pedir para fechar a conexão.
Quando receber o pedido de fechamento, faça o callback da classe Conexao ser chamado passando como o argumento dados
uma string de bytes vazia (b''
), para sinalizar o fechamento da conexão para a camada de aplicação, além de responder corretamente com um ACK
.
Quando o método fechar
da classe Conexao for chamado, envie um segmento com flag FIN
.
Se você implementou todos os passos até aqui, você deve ser capaz de executar o arquivo PYTHONPATH=grader ./exemplo_integracao.py
em um sistema Linux (leia antes os comentários no início do arquivo) e testá-lo conectando-se a ele com o comando nc localhost 7000
.
Vamos, agora, voltar ao slide 67 do livro para implementar a segunda parte do transmissor. Faça a sua implementação ser capaz de lidar com perda de pacotes. Para isso, você vai precisar de um timer. Há um exemplo no código, mas ele está posicionado no local errado: você deve movê-lo para os locais do código onde realmente é necessário iniciar um timer. Por enquanto, você pode usar um valor constante (não use um valor muito grande!) para o intervalo do timer.
Além disso, você precisará tratar os ACK
s que chegarem pelo método _rdt_rcv
da classe Conexao
.
Calcule o TimeoutInterval
de acordo com as equações dos slides 62 e 63 e passe a usar esse valor como intervalo dos timers que você criar.
Para medir o SampleRTT
, meça o tempo que se passa (por exemplo, com time.time()
) entre você enviar um segmento e receber o ACK
dele. Não leve em conta segmentos que estiverem sendo retransmitidos, apenas aqueles que estiverem sendo transmitidos pela primeira vez.
Quando você medir o primeiro SampleRTT
de uma conexão, em vez de usar as equações, inicialize os valores atribuindo EstimatedRTT
e DevRTT
com SampleRTT
e SampleRTT/2
, respectivamente, como sugerido pela RFC 2988.
Implemente um mecanismo de controle de congestionamento simplificado similar ao AIMD (descrito no slide 96 do livro). Quando uma conexão for aberta, comece com uma janela de 1 MSS. Sempre que você receber ACK
de uma janela inteira, aumente a janela de mais 1 MSS. Se ocorrer um timeout no timer, reduza a janela pela metade.
O arquivo exemplo_integracao.py
vem com um exemplo de camada de aplicação que faz eco, ou seja, envia de volta para o cliente tudo que receber dele.
Se você tiver feito o Trabalho 1, experimente adaptá-lo para usar o TCP que você escreveu agora no Trabalho 2 (de acordo com o exemplo_integracao.py
) e depois executar PYTHONPATH=grader ./servidor
. Talvez você pegue erros de implementação que não tenham sido detectados pelos testes! Se isso acontecer, entre em contato com o professor para que possamos melhorar os testes para edições futuras da disciplina.