Prof V Vargas, IST Jogo do Galo (Resolução) Page 1 of 39 Projecto ...
Prof V Vargas, IST Jogo do Galo (Resolução) Page 1 of 39 Projecto ...
Prof V Vargas, IST Jogo do Galo (Resolução) Page 1 of 39 Projecto ...
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 1 <strong>of</strong> <strong>39</strong><br />
<strong>Projecto</strong> de S<strong>of</strong>tware "Cliente/Servi<strong>do</strong>r"<br />
<strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>)<br />
{<strong>Jogo</strong>Do<strong>Galo</strong>-r.<strong>do</strong>c}<br />
Nota introdutória: este <strong>do</strong>cumento tem fundamentalmente <strong>do</strong>is objectivos, a saber:<br />
1. apresentar uma Especificação de um Protocolo, concretamente o <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (que foi o enuncia<strong>do</strong><br />
<strong>do</strong> <strong>Projecto</strong> Cliente-Servi<strong>do</strong>r em 2004-2005). Na prática, ela resume-se ao Anexo-A (Para bom entende<strong>do</strong>r:<br />
submeti<strong>do</strong> o enuncia<strong>do</strong> de um <strong>Projecto</strong>, aguarda-se que os estudantes entreguem, como Especificação, algo<br />
análogo às 9 páginas por que se desenrola o Anexo-A abaixo). As páginas que o precedem destinam-se tãosomente<br />
a apresentar a reflexão que conduziu àquela Especificação – começan<strong>do</strong> por introduzir conceitos a<br />
propósito: Cliente e Servi<strong>do</strong>r, Primitivas, Diagramas Temporais, PDUs, sockets TCP/UDP, Arquitectura de<br />
Comunicações, Diagramas de Esta<strong>do</strong>, Eventos e Predica<strong>do</strong>s, Tabelas de Esta<strong>do</strong>, etc..<br />
2. apresentar uma meto<strong>do</strong>logia para a Codificação desse Protocolo. Na prática, ela ocupa o Anexo-B.<br />
I - Preliminares: Servi<strong>do</strong>r e Clientes co-residentes<br />
Considere-se, para simplificar, uma Associação Recreativa com um único computa<strong>do</strong>r (Unix) e uma Sala de<br />
<strong>Jogo</strong> com uns quantos Terminais.<br />
Quan<strong>do</strong> um sócio quer jogar (o <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong>), senta-se a um Terminal, procede ao login - e continua invocan<strong>do</strong><br />
o programa Joga<strong>do</strong>r e digitan<strong>do</strong> Start. Depois, só tem que aguardar que surja um outro sócio... e que também ele<br />
corra o programa Joga<strong>do</strong>r (num outro Terminal, é claro), todavia digitan<strong>do</strong> agora Join.<br />
Para que as "coisas" funcionem, bastará então que esteja corren<strong>do</strong> um "media<strong>do</strong>r" entre os sócios, que aceite as<br />
jogadas (Play) nos tecla<strong>do</strong>s e lhes apresente nos ecrãs a evolução <strong>do</strong> tabuleiro...<br />
Para um conhece<strong>do</strong>r de Unix, será natural (fig. JdGrIa):<br />
- associar a cada sócio um processo Cliente - cria<strong>do</strong><br />
quan<strong>do</strong> ele invocou o programa Joga<strong>do</strong>r;<br />
- e conceber um processo Servi<strong>do</strong>r que materialize o<br />
"Media<strong>do</strong>r" supracita<strong>do</strong> - e que deverá ter si<strong>do</strong> cria<strong>do</strong><br />
antes mesmo de os sócios correrem o Joga<strong>do</strong>r:<br />
provavelmente, logo no power-on <strong>do</strong> computa<strong>do</strong>r.<br />
Trata-se de um cenário simples: um só computa<strong>do</strong>r,<br />
com um Servi<strong>do</strong>r e um conjunto de Clientes residin<strong>do</strong> nesse mesmo computa<strong>do</strong>r.<br />
II -Preliminares: Primitivas de Comunicação<br />
Servi<strong>do</strong>r e Clientes necessitam comunicar entre si. Em particular, aquan<strong>do</strong> de uma jogada (Play),<br />
- o Cliente necessita comunicar ao Servi<strong>do</strong>r o trio GId/Line/Column digita<strong>do</strong> pelo sócio;<br />
- e o Servi<strong>do</strong>r necessita comunicar a ambos os Clientes a subsequente posição <strong>do</strong> tabuleiro.<br />
Pressuposto que tais informações (GId/Line/Column e Board) serão comunicadas através de mensagens, é<br />
pertinente a pergunta: Como se processará a comunicação entre<br />
Servi<strong>do</strong>r e Clientes?<br />
Uma solução será fazê-lo por memória partilhada (fig. JdG-rIb):<br />
entre cada Cliente e o Servi<strong>do</strong>r, o Sistema Operativo reserva um par<br />
de memórias FIFO específicas:<br />
- uma, dita de saída, onde o Cliente escreve mensagens para o<br />
Servi<strong>do</strong>r ler,<br />
- e outra, dita de entrada, onde o Servi<strong>do</strong>r escreve mensagens para o Cliente ler.<br />
À "composição de uma mensagem e sua escrita", e à "sua posterior leitura e interpretação", é tradicional, no<br />
mun<strong>do</strong> <strong>do</strong>s protocolos de comunicação, atribuir a designação de primitivas.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 2 <strong>of</strong> <strong>39</strong><br />
Concretamente, quan<strong>do</strong> um sócio digitar "Play GId Line Column" (fig. JdG-rIc).<br />
- o respectivo Cliente Joga<strong>do</strong>r deverá escrever na sua memória de saída aquele trio GId/Line/Column (isto<br />
designan<strong>do</strong>-se de primitiva PLAY.req)<br />
- e, subsequentemente, o Servi<strong>do</strong>r deverá proceder à sua leitura (isto designan<strong>do</strong>-se de primitiva PLAY.ind)<br />
Os sufixos ".req" e ".ind" acrescenta<strong>do</strong>s a PLAY são as mnemónicas de request e indication.<br />
Posteriormente, o Servi<strong>do</strong>r valida a jogada, e reporta-o ao Cliente:<br />
- o Servi<strong>do</strong>r deverá escrever na memória de entrada <strong>do</strong> Cliente a sua conclusão, Ok ou Sorry (isto designan<strong>do</strong>se<br />
de primitiva PLAY.rsp)<br />
- e, subsequentemente, o Cliente deverá proceder à sua leitura (isto designan<strong>do</strong>-se de primitiva PLAY.cnf).<br />
Os sufixos ".rsp" e ".cnf" são as mnemónicas de response e confirmation.<br />
Se a jogada for válida, o Servi<strong>do</strong>r prossegue actualizan<strong>do</strong> o tabuleiro e divulgan<strong>do</strong>-o aos Clientes em causa:<br />
- o Servi<strong>do</strong>r deverá escrever na memória de entrada de cada Cliente <strong>do</strong> par de sócios em causa a posição <strong>do</strong><br />
tabuleiro (isto designan<strong>do</strong>-se de primitiva BOARD.req)<br />
- e, subsequentemente, esse Cliente deverá proceder à sua leitura (isto designan<strong>do</strong>-se de primitiva BOARD.ind)<br />
- após o que apresentará no respectivo ecrã a posição <strong>do</strong> tabuleiro.<br />
Repare-se: qualquer um, Cliente ou Servi<strong>do</strong>r, pode desencadear uma primitiva.<br />
- o Cliente recorre a PLAY.req para escrever uma Mensagem (GId/Line/Column) na sua memória de saída -<br />
que o Servi<strong>do</strong>r lê recorren<strong>do</strong> a PLAY.ind; subsequentemente, o Servi<strong>do</strong>r recorre a PLAY.rsp para escrever outra<br />
Mensagem (Ok ou Sorry) na memória de entrada <strong>do</strong> Cliente - que este lê recorren<strong>do</strong> a PLAY.cnf;<br />
- o Servi<strong>do</strong>r recorre a BOARD.req para escrever outra Mensagem (posição <strong>do</strong> tabuleiro) na memória de<br />
entrada <strong>do</strong> Cliente - que este lê recorren<strong>do</strong> a BOARD.ind.<br />
A primitiva PLAY diz-se confirmada - no senti<strong>do</strong> de que ao ".req" inicial se sucede uma - e uma só! - ".cnf"<br />
local. Com BOARD, isso não acontece: trata-se de uma primitiva não-confirmada.<br />
Para caracterizar cabalmente uma primitiva é então necessário especificar:<br />
- qual das partes a emite,<br />
- se é, ou não, confirmada;<br />
- quais os seus parâmetros (no caso, GId/Line/Column, Resulta<strong>do</strong> (Ok ou Sorry) e Board).<br />
Adiante, serão consideradas outras primitivas mais.<br />
III - Preliminares: Diagramas Temporais<br />
A troca de mensagens entre os intervenientes (Clientes e Memórias de Comunicação) pode esquematizar-se por<br />
um assim denomina<strong>do</strong> Diagrama Temporal. Nele, os intervenientes são representa<strong>do</strong>s por riscas verticais; o tempo<br />
corre verticalmente, de cima para baixo; pelo que a troca de Mensagens vem a ser representada por "setas" oblíquas<br />
(cf. JdG-rIIa, que figura a troca de Mensagens aquan<strong>do</strong> de uma Jogada, Play, bem sucedida).
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 3 <strong>of</strong> <strong>39</strong><br />
Três reparos:<br />
I. Uma "seta" estabelece o movimento físico de bits, de uma área da RAM para outra; é delimitada por uma<br />
ponta e uma cauda. Marcam os instantes em que respectivamente o último e o primeiro bits de uma Mensagem<br />
foram escritos (ou li<strong>do</strong>s) na Memória partilhada.<br />
II. O Diagrama não tem escala:<br />
Por um la<strong>do</strong>, desconhece-se a velocidade <strong>do</strong> processa<strong>do</strong>r; pelo que não se sabe a inclinação das setas.<br />
Por outro la<strong>do</strong>, não se conhece a carga <strong>do</strong> Computa<strong>do</strong>r no momento; pelo que se ignora o tempo de espera<br />
até se começar a escrever (ou ler) uma Mensagem. Por ex., desconhece-se quanto tempo decorre entre a escrita<br />
na Memória <strong>do</strong> resulta<strong>do</strong> Ok e a subsequente escrita da posição <strong>do</strong> tabuleiro.<br />
III. Os Diagramas dão conta somente <strong>do</strong> movimento físico das mensagens - <strong>do</strong> espaço de endereços dum<br />
processo para o <strong>do</strong> outro através das memórias partilhadas. São omissos relativamente aos sinais de controlo<br />
troca<strong>do</strong>s, cf. JdG-rIIc, que esquematiza uma possível sucessão de acontecimentos ao invocar-se PLAY.req:<br />
1. O Cliente preenche a mensagem num buffer seu - após o que invoca a SystemCall de escrita na memória<br />
de Saída;<br />
2. O conteú<strong>do</strong> <strong>do</strong> buffer é transferi<strong>do</strong> para essa memória;<br />
3. Com uma SystemCall, o Cliente solicita ao Sistema<br />
Operativo: alerte o Servi<strong>do</strong>r, que tem algo naquela memória;<br />
4. O Sistema Operativo notifica o Servi<strong>do</strong>r: há algo para ler…<br />
5. O Servi<strong>do</strong>r invoca a SystemCall de leitura daquela memória;<br />
6. O conteú<strong>do</strong> da memória é transferi<strong>do</strong> para um buffer <strong>do</strong><br />
Servi<strong>do</strong>r; ele pode agora proceder ao "parse" desse buffer: concretamente, extrai dele o trio GId/Line/Column.<br />
7. Com uma SystemCall, solicita ao Sistema Operativo: alerte o Cliente, que a memória de Saída já foi lida (e<br />
por conseguinte está disponível para nova escrita);<br />
8. O Sistema Operativo notifica o Cliente: a memória de Saída está livre…<br />
PLAY.req e PLAY.ind envolvem os passos 1-3 e 5-7. Os sinais 4 e 8 destinam-se a evitar underflow e overflow:<br />
PLAY.ind é desencadea<strong>do</strong> no sinal 4; e PLAY.req pode consumar-se somente após o sinal 8.<br />
IV - Preliminares: Mensagens e PDUs<br />
A troca de Mensagens entre Cliente e Servi<strong>do</strong>r inicia-se sempre pela invocação de primitivas .req ou .rsp; ela<br />
volve-se na escrita de sequência de bits nas memórias partilhadas entre ambos. A essas sequências de bits, é<br />
tradicional, no mun<strong>do</strong> <strong>do</strong>s protocolos de comunicação, designar de PDUS (Protocol Data Units). Deverão exibir<br />
uma estrutura (variável) que, no mínimo, inclua os valores <strong>do</strong>s parâmetros pertinentes àquelas primitivas.<br />
Por exemplo,<br />
- a sequência PLAY.req → PLAY.ind deverá envolver uma PDU, seja cPlay-Pdu, veiculan<strong>do</strong> (sob a forma de<br />
três int) o trio GId/Line/Column digita<strong>do</strong> no Cliente;<br />
- a sequência PLAY.rsp → PLAY.cnf deverá envolver uma PDU, seja sPlay-Pdu, veiculan<strong>do</strong> (sob a forma de<br />
um Boolean[+String]) a decisão <strong>do</strong> Servi<strong>do</strong>r (Ok ou Sorry[+Reason]);<br />
- e a sequência BOARD.req → BOARD.ind deverá envolver uma PDU, seja sBoard-Pdu, veiculan<strong>do</strong> (sob a<br />
forma de uma lista de ints e/ou chars) a posição <strong>do</strong> tabuleiro.<br />
Os prefixos "c" (em cPlay-Pdu) e "s" (em sPlay-Pdu e sBoard-Pdu) especificam qual das partes (Cliente ou<br />
Servi<strong>do</strong>r) preencheu a PDU escrita na memória partilhada.<br />
A estrutura de uma PDU fica especificada quan<strong>do</strong> se sabe:<br />
- quais os campos que a compõem,<br />
- se são, ou não, opcionais - e qual a respectiva ordem no seio da PDU;<br />
- por cada campo, qual o seu tamanho, tipo e valores aceitáveis: int, long, char, string, boolean, etc.<br />
Para a generalidade das Aplicações, há um campo imprescindível na estrutura da PDU: é o assim denomina<strong>do</strong><br />
Código <strong>do</strong> Tipo de PDU. Para o compreender, e ten<strong>do</strong> por pano de fun<strong>do</strong> o Diagrama Temporal acima,<br />
considerem-se, por ex., PLAY.cnf e BOARD.ind. No caso em apreço, pode afirmar-se que, para o Cliente que as<br />
recebe, são, ambas, previsíveis: acontecem quan<strong>do</strong>, ten<strong>do</strong> invoca<strong>do</strong> PLAY.req, ele recolhe o que o Servi<strong>do</strong>r
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 4 <strong>of</strong> <strong>39</strong><br />
escreveu na sua memória de entrada. PLAY.cnf é expectável - pois PLAY é confirmada. Quanto a BOARD.ind,<br />
também ela será expectável - se PLAY.cnf certificar que GId/Line/Column era váli<strong>do</strong>!<br />
A ter em conta, porém, a eventualidade <strong>do</strong> Servi<strong>do</strong>r abortar o <strong>Jogo</strong>, essa previsibilidade já não é tão certa: é<br />
possível que, em vez de PLAY.cnf ou BOARD.ind, o Cliente venha a receber um, seja, ABORT.ind. É então<br />
natural a pergunta: como é que o Cliente "sabe" que se trata de PLAY.cnf, BOARD.ind ou outra "coisa" qualquer?<br />
A resposta intui-se: sem ajudas, não o sabe… Convém, por conseguinte, um artifício, aliás trivial: que, na<br />
estrutura da PDU, se reserve um campo (conjunto de bits) para codificar o tipo de PDU em jogo.<br />
Isso significa que o Cliente sabe qual a primitiva a invocar somente após proceder à leitura na memória de<br />
entrada <strong>do</strong> Código <strong>do</strong> Tipo de PDU (Enuncia<strong>do</strong> análogo vale para o Servi<strong>do</strong>r). Aliás, será também esse código que<br />
lhe dirá como interpretar o resto <strong>do</strong> conteú<strong>do</strong> daquela memória: quais os campos e respectivos valores em presença.<br />
A tabela adiante apresenta, para cada Sequência de primitivas, o Tipo de PDU envolvida, um seu possível Código,<br />
e os demais campos que a constituem.<br />
Sequência de Tipo de Código <strong>do</strong><br />
Campos<br />
Primitivas<br />
PDU Tipo de PDU<br />
da PDU<br />
PLAY.req, PLAY.ind cPlay-Pdu 8 GId, Line, Column<br />
PLAY.rsp, PLAY.cnf sPlay-Pdu 9 Ok ou Sorry+Reason<br />
BOARD.req, BOARD.ind sBoard 10 GId, Board<br />
ABORT.req, ABORT.ind sAbort 6 GId, Reason<br />
Por ex., quan<strong>do</strong> um joga<strong>do</strong>r digita Play 4 2 3, o respectivo Cliente deverá escrever na sua memória de saída a<br />
PDU (e admitin<strong>do</strong> que os campos em jogo são to<strong>do</strong>s eles inteiros codifica<strong>do</strong>s em 8-bits):<br />
Código <strong>do</strong> Tipo de PDU (cPlay-Pdu):8 GId:4 Line:2 Column:3<br />
PDU: 0000 1000 00000100 0000 0010 0000 0011<br />
Esse coman<strong>do</strong> pode ser váli<strong>do</strong>: será o caso de o jogo "4" estar decorren<strong>do</strong>, e de ainda estar livre a posição<br />
linha=2,coluna=3; nesse caso, o Servi<strong>do</strong>r deverá escrever na memória de entrada daquele Cliente a PDU:<br />
Código <strong>do</strong> Tipo de PDU (sPlay-Pdu):9 GId:4 Result:Ok<br />
PDU: 0000 1001 00000100 0000 0000<br />
Mas pode suceder que o coman<strong>do</strong> não seja váli<strong>do</strong> - porque, por ex., a posição referida está já preenchida; então,<br />
a PDU devolvida pelo Servi<strong>do</strong>r será:<br />
Código <strong>do</strong> Tipo de PDU (sPlay-Pdu):9 GId:4 Result:Sorry Reason<br />
PDU: 0000 1001 00000100 0000 0001 Posição já preenchida<br />
Para bom entende<strong>do</strong>r: o Result é codifica<strong>do</strong> como 0 (Ok) ou 1 (Sorry); e, neste último caso, a PDU transporta<br />
uma mensagem explicativa <strong>do</strong> erro detecta<strong>do</strong>. Em alternativa, Sorry poderia veicular o código desse erro - bastan<strong>do</strong><br />
ao Cliente deter a lista <strong>do</strong>s códigos de erro possíveis - e correspondentes mensagens na língua materna <strong>do</strong> joga<strong>do</strong>r...<br />
Para o caso de o Result ser Ok, o Servi<strong>do</strong>r deve enviar o Tabuleiro; uma solução possível poderá ser:<br />
Código <strong>do</strong> Tipo de PDU (sBoard-Pdu):6 GId:4 Result:Tabuleiro<br />
PDU: 0000 0110 00000100 LXOLXOLLL<br />
(em que, por ex., LXO significa que as posições da linha são, respectivamente, "Livre", "X" e "O").<br />
A concluir a abordagem a este cenário (Servi<strong>do</strong>r e Clientes co-residentes), apresenta-se o esquema da<br />
Arquitectura <strong>do</strong> Sistema de Comunicações (fig. JdG-rIVb):<br />
Ambos os interlocutores apresentam uma mesma Arquitectura:<br />
1) um Elemento de Utiliza<strong>do</strong>r, que:<br />
- no Cliente, procede à leitura no tecla<strong>do</strong> e escrita no ecrã de cada terminal;
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 5 <strong>of</strong> <strong>39</strong><br />
- no Servi<strong>do</strong>r, procede à gestão <strong>do</strong> jogo, à escrita e leitura de ficheiros em disco, etc..<br />
- em ambos os casos, invoca as primitivas de Comunicação referidas.<br />
2) um Módulo de Comunicações, que executa as Primitivas referidas: procede ao empacotamento e<br />
desempacotamento de PDUs e à sua escrita e leitura nas Memórias partilhadas. Cabe-lhe, em particular, a<br />
invocação das chamadas ao Sistema Operativo relacionadas com o acesso a Memórias Partilhadas, com a<br />
Sincronização nesse acesso (para que, por ex., o Servi<strong>do</strong>r faça uma leitura só após uma escrita pelo Cliente), etc.…<br />
V - Servi<strong>do</strong>r e Clientes em distintos computa<strong>do</strong>res<br />
É altura de afrontar o cenário mais geral: ao menos um sócio - ou pela sua provecta idade ou por estar <strong>do</strong>ente -<br />
não pode deslocar-se fisicamente à Associação Recreativa. Fica em casa… mas, felizmente, dispõe de um<br />
"Magalhães" (per<strong>do</strong>e-se a publicidade ao made in Portugal) - e a tentação não se faz demorar muito: que impede<br />
usá-lo para embarcar num <strong>Jogo</strong> remoto através da Internet com o computa<strong>do</strong>r naquela Associação? O cenário é<br />
agora outro: ao menos um Cliente reside num computa<strong>do</strong>r distinto daquele onde corre o Servi<strong>do</strong>r (fig. JdG-rIIb).<br />
Como responder agora à pergunta: Como se processará a comunicação entre o Servi<strong>do</strong>r e esse Cliente?<br />
Memória partilhada já não pode ser solução: o que existe agora entre ambos é uma porção da Internet, isto é, um<br />
intrinca<strong>do</strong> maior ou menor de linhas e nós, com as suas memórias, processa<strong>do</strong>res, modems, procedimentos, etc..<br />
Pelo que a escrita e leitura de uma Mensagem em uma memória partilhada há-de agora ser substituída pelo<br />
"envio de uma Mensagem para a Internet e posterior recepção dessa Mensagem". Fica apenas por saber como<br />
levar a cabo esse tal envio e essa tal recepção…<br />
Ora acontece que os computa<strong>do</strong>res já hoje vêem equipa<strong>do</strong>s de s<strong>of</strong>tware vocaciona<strong>do</strong> para este envio/recepção:<br />
trata-se, concretamente <strong>do</strong> assim designa<strong>do</strong> s<strong>of</strong>tware TCP/UDP (e IP). Agora, já não se fala em memória partilhada<br />
- mas nuns objectos especiais ditos de sockets: em lugar de se escrever e ler numa memória partilhada, escreve-se e<br />
lê-se em um socket (fig. JdG-rIIIa).
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 6 <strong>of</strong> <strong>39</strong><br />
Pressuposto que Cliente e Servi<strong>do</strong>r dispõem, cada um, <strong>do</strong>s sockets necessários à comunicação entre ambos,<br />
intui-se o que acontece quan<strong>do</strong> um joga<strong>do</strong>r digita Play; grosso mo<strong>do</strong>,<br />
1. o Módulo de Comunicações <strong>do</strong> Cliente constrói a cPlay-Pdu, e escreve-a no seu socket;<br />
2. o s<strong>of</strong>tware TCP/UDP local encarrega-se de a enviar para a Internet;<br />
3. a Internet encaminha essa cPlay-Pdu até ao Servi<strong>do</strong>r;<br />
4. o s<strong>of</strong>tware TCP/UDP <strong>do</strong> Servi<strong>do</strong>r recolhe-a da Internet e apresenta-a no seu socket;<br />
5. o Módulo de Comunicações <strong>do</strong> Servi<strong>do</strong>r lê no socket a cPlay-Pdu e interpreta-a…<br />
VI - Arquitectura de Comunicações <strong>do</strong> "<strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong>"<br />
A substituição da escrita/leitura em Memória partilhada pela escrita/leitura em sockets impõe uma pergunta:<br />
pretende-se preservar a Arquitectura esquematizada acima (Elemento Utiliza<strong>do</strong>r + Módulo de Comunicações), pois<br />
sócios haverá que continuarão a ir jogar nos terminais da Associação Recreativa… mas, para contemplar sócios<br />
aceden<strong>do</strong> remotamente, que adaptações lhe deverão ser feitas? A resposta exige uma revisão às funcionalidades<br />
<strong>of</strong>erecidas pelo s<strong>of</strong>tware TCP/UDP:<br />
Do ponto de vista <strong>do</strong> envio e recepção de da<strong>do</strong>s, TCP e UDP remetem para <strong>do</strong>is tipos de serviços, a que estão<br />
associa<strong>do</strong>s duas famílias de Chamadas ao Sistema:<br />
- em UDP, o envio e recepção de da<strong>do</strong>s logra-se com a invocação de sendto e recvfrom. Ambos operam sobre<br />
Blocos de octetos: com um sendto, envia-se o Bloco - e, para o receber na sua totalidade, basta um recvfrom. O<br />
serviço não é, porém, lá muito robusto: após sendto, o Bloco pode perder-se e não chegar ao destino…<br />
- em TCP, o envio e recepção de da<strong>do</strong>s logra-se com a invocação de write e read. Ambos operam sobre<br />
Sequências de octetos: com um ou mais write, envia-se uma Sequência - e, para a receber na sua totalidade, podem<br />
ser necessários um ou mais read, num número que até pode não ser<br />
exactamente igual ao número de write. O serviço é, porém, muito mais<br />
robusto que UDP: após o write, é muitíssimo menor a probabilidade de a<br />
Sequência não chegar ao seu destino - e muitíssimo maior a probabilidade<br />
de os octetos serem recebi<strong>do</strong>s pela mesmíssima ordem em que foram<br />
envia<strong>do</strong>s. …<br />
Podem agora deduzir-se as adaptações a fazer-se (fig. JdG-rIIIb):<br />
1. PLAY e BOARD, concebidas no cenário "Servi<strong>do</strong>r e Clientes coresidentes",<br />
acedem às PDUs de forma atómica: quan<strong>do</strong> o Cliente invoca<br />
PLAY.req, a consequência é a construção de uma cPlay-Pdu - em uma<br />
Memória partilhada que deve ter a dimensão suficiente (em número de<br />
octetos) para a conter na sua totalidade. Então, quan<strong>do</strong> o Servi<strong>do</strong>r invoca<br />
PLAY.ind, fá-lo assumin<strong>do</strong> que vai ler e interpretar toda essa PDU.<br />
2. No ambiente sockets, esta assumpção não pode ser feita:<br />
- se se usar sockets-UDP, o sendto da cPlay-Pdu não garante que o<br />
Servi<strong>do</strong>r a venha a ler: pode perder-se …<br />
- e, se se usar sockets-TCP, o write da cPlay-Pdu não garante que com um read o Servi<strong>do</strong>r a venha a ler na<br />
sua totalidade…<br />
Isso sugere, para o "<strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong>", a arquitectura de comunicações apresentada em JdG-rIVa (no Anexo-A.1).<br />
Parêntesis: O Nível "Transparência "<br />
Tal Arquitectura inclui um assim denomina<strong>do</strong> nível "Transparência". A propósito, abra-se um parêntesis para<br />
tranquilizar o leitor. Considere, por ex., PLAY.req - que deve levar ao envio de uma cPlay-Pdu, de N=4 octetos. A<br />
delimitação resume-se ao seguinte:<br />
- proceder ao write de um certo número L de octetos (que neste Documento se assume ser 4) que veicule o<br />
tamanho (no caso, 4) daquela PDU;<br />
- e só então executar o write da própria cPlay-Pdu.<br />
A Internet acaba por vir a transportar um conjunto - designe-se de PDUNT - de, no total, L+N octetos:
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 7 <strong>of</strong> <strong>39</strong><br />
PDU-length ocupan<strong>do</strong> L=4 octetos, com o tamanho, N, da PDUMC<br />
PDUMC ocupan<strong>do</strong> N octetos<br />
É possível olhar para este conjunto da seguinte maneira: designe-se de PDUMC uma PDU genérica produzida<br />
pelo Módulo de Comunicações; tu<strong>do</strong> se passa como se o Nível Transparência a houvera encapsula<strong>do</strong> em uma<br />
outra, dita de PDUNT. Em alternativa aos <strong>do</strong>is write, o nível Transparência pode então prefaciar a cPlay-Pdu com<br />
um campo PDU-length de L octetos, e proceder a um único write de to<strong>do</strong> o conjunto.<br />
Então, o nível Transparência <strong>do</strong> Servi<strong>do</strong>r, quan<strong>do</strong> lhe chegar essa PDUNT, deverá passar por duas etapas:<br />
- primeiramente, lê L octetos, para saber qual o tamanho, N (no caso, 4), da PDUMC que chegou;<br />
- após o que, com um ou mais read, irá len<strong>do</strong> os octetos seguintes, até perfazer N octetos. No caso em apreço, só<br />
então será a hora de se executar PLAY.ind - passan<strong>do</strong> duma só vez to<strong>do</strong>s os octetos que compõem a cPlay-Pdu.<br />
Repare-se: ambos, Cliente e Servi<strong>do</strong>r, registam o mesmo número (três) de Níveis de Protocolo de<br />
Comunicações. Entretanto,<br />
- visto de mais perto, o nível TCP/UDP acaba por (dever) ser considera<strong>do</strong> como uma Pilha de Níveis de<br />
Protocolos de Comunicações (Físico, DataLink, Network e Transporte);<br />
- pode eliminar-se o nível Transparência - pelo expediente de incluir na estrutura das PDUs um campo<br />
"Tamanho da PDU" e, consequentemente, adaptan<strong>do</strong> o Módulo de Comunicações;<br />
- e nada impede que o Desenha<strong>do</strong>r <strong>do</strong> Sistema decida repartir o Módulo de Comunicações por (Sub-)Níveis de<br />
protocolos (por ex., para fazer criptografia, compactar da<strong>do</strong>s, re-sincronizar, etc.)<br />
VII - Primitivas de acesso aos Níveis de Protocolo<br />
VII.1: As Primitivas de acesso ao Nível TCP/UDP não deverão constituir novidade: write e read, etc.:<br />
Primitiva invocada por: Parâmetros<br />
connect Cliente Target (HostName + Port)<br />
accept Servi<strong>do</strong>r & Source (HostName + Port)<br />
write (e sendto)<br />
read (e recvfrom)<br />
close<br />
Cliente, Servi<strong>do</strong>r Socket, Buffer, Buffer-length, Flags (e Target)<br />
Socket, Buffer, & Buffer-length, Flags (e & Source)<br />
Socket<br />
select (puramente local) Cliente, Servi<strong>do</strong>r Domain, Type, Protocol<br />
VII.2: Quanto às Primitivas de acesso ao Nível Transparência, bastarão, no que toca à transferência de PDUs,<br />
as seguintes (fig. JdG-rIVc):<br />
Primitiva Parâmetros<br />
TCP.req (e UDP.req) Socket, Buffer, Buffer-length (e Target)<br />
TCP.ind (e UDP.ind) Socket, Buffer, & Buffer-length (e & Source)<br />
em que a discriminação TCP versus UDP selecciona qual o mo<strong>do</strong> como a comunicação se deve fazer:<br />
respectivamente por write+read ou sendto+recvfrom.<br />
VII.3: Já quanto às Primitivas de acesso ao Módulo de<br />
Comunicações, isso encontra-se em aberto. Na continuação <strong>do</strong> que se já<br />
discutiu acima, uma solução é começar por associar a cada coman<strong>do</strong><br />
(Start, Join, Play, List e Abort) uma ou mais Primitivas específicas:<br />
A Start, associa-se a primitiva START; ela é desencadeada pelo<br />
Cliente, e é confirmada: após o Utiliza<strong>do</strong>r digitar Start, ele deve receber<br />
uma resposta - que, em caso de sucesso, provém <strong>do</strong> Servi<strong>do</strong>r.<br />
De maneira análoga, a Join, Play e List associam-se respectivamente as primitivas JOIN, PLAY e L<strong>IST</strong>. Todas<br />
elas são desencadeadas pelo Cliente e, com excepção da última, são confirmadas.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 8 <strong>of</strong> <strong>39</strong><br />
Em caso de sucesso de Play, o Servi<strong>do</strong>r deve comunicar a subsequente "posição <strong>do</strong> Tabuleiro" a ambos os<br />
joga<strong>do</strong>res. Isso convida a conceber uma outra Primitiva, BOARD; ela é desencadeada pelo Servi<strong>do</strong>r (após<br />
responder a Play) e não é confirmada.<br />
Quanto a List, o Servi<strong>do</strong>r deve comunicar a Listagem <strong>do</strong>s <strong>Jogo</strong>s: GIds e respectivos joga<strong>do</strong>res. Isso convida a<br />
conceber outra Primitiva, GAMEL<strong>IST</strong>; é desencadeada pelo Servi<strong>do</strong>r e não é confirmada.<br />
Quanto ao Abort, existem três cenários:<br />
1. um joga<strong>do</strong>r digita Abort;<br />
2. o Servi<strong>do</strong>r acaba com um <strong>Jogo</strong> aborta<strong>do</strong> por algum fulano que nem sequer precisa estar a jogar:<br />
3. o Servi<strong>do</strong>r aborta um <strong>Jogo</strong> porque o perío<strong>do</strong> máximo entre coman<strong>do</strong>s expirou.<br />
Isso convida a conceber uma primitiva, ABORT, que pode ser desencadeada por qualquer uma das partes; e que,<br />
se for desencadeada por um Cliente e não for bem sucedida, deverá ser replicada por uma primitiva ERROR.<br />
Estas conclusões encontram-se sumariadas no Anexo-A.2 - num quadro que exibe, para cada Coman<strong>do</strong>, as<br />
primitivas associadas, quem as desencadeou e os respectivos parâmetros.<br />
Ressalve-se, nesse quadro, o parâmetro Gf. Surge para responder à interrogação: como "emparelhar" com um<br />
Start a posterior réplica (Ok ou Sorry) que o Servi<strong>do</strong>r irá devolver?<br />
- é que, por um la<strong>do</strong>, o GId é atribuí<strong>do</strong> pelo Servi<strong>do</strong>r: aquan<strong>do</strong> de START.req, ele não é ainda conheci<strong>do</strong>;<br />
- de outra parte, aceita-se que o utiliza<strong>do</strong>r possa digitar "de rajada" vários Start - mesmo sem antes ter recebi<strong>do</strong><br />
a réplica ao primeiro deles.<br />
Gf é escolhi<strong>do</strong> pelo Cliente, envia<strong>do</strong> na cStart-Pdu e depois "ecoa<strong>do</strong>" pelo Servi<strong>do</strong>r na subsequente réplica,<br />
sStart-Pdu. Repare-se, entretanto, que se poderá dispensar Gf: bastan<strong>do</strong> para tal que o Servi<strong>do</strong>r responda a cStart-<br />
Pdus pela mesmíssima ordem em que ele as for receben<strong>do</strong>…<br />
VIII - Diagramas Temporais das Primitivas<br />
A etapa seguinte é desenhar diagramas temporais para as primitivas concebidas. Eis alguns:<br />
VIII .T - Nível Transparência<br />
Apresentam-se <strong>do</strong>is Diagramas esquematizan<strong>do</strong> a sucessão de eventos desencadeada por uma TCP.req invocada<br />
pelo Módulo de Comunicações:<br />
JdG-rVa: O Nível Transparência adiciona o PDU-length e invoca write; o s<strong>of</strong>tware TCP envia o correspondente<br />
Segmento para a Internet; à sua chegada ao receptor remoto, o Nível Transparência invoca read; o PDU-length é<br />
removi<strong>do</strong>, e sinaliza<strong>do</strong> o Módulo de Comunicações - poden<strong>do</strong> então emitir-se TCP.ind.<br />
O Diagrama para UDP.req e UDP.ind é análogo - a diferença consistin<strong>do</strong> em que, agora, não é adiciona<strong>do</strong> nem<br />
removi<strong>do</strong> nenhum PDU-length, e o par write/read vem a ser substituí<strong>do</strong> por sendto/recvfrom.<br />
JdG-rVb: O cenário versa a eventualidade de o s<strong>of</strong>tware TCP/UDP achar que os da<strong>do</strong>s que acompanham o<br />
write são demasia<strong>do</strong> extensos para caberem num Segmento só: acabam por ser envia<strong>do</strong>s vários Segmentos para a<br />
Internet. No interlocutor remoto, isso dará azo a vários read… mas a presença <strong>do</strong> PDU-length conduz a que seja<br />
emitida uma única TCP.ind: esta só é invocada quan<strong>do</strong> a PDU já foi aí totalmente recebida…
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 9 <strong>of</strong> <strong>39</strong><br />
VIII .C - Módulo de Comunicações<br />
Um primeiro cenário respeita a Start, cf. JdG-rVIa. Considera-se que já existe uma conexão-TCP estabelecida<br />
entre o Cliente e o Servi<strong>do</strong>r; aquele executa START.req; esta é "ecoada" no Servi<strong>do</strong>r como START.ind. Ocorre<br />
sucesso: o Servi<strong>do</strong>r responde com START.rsp(Ok), que é "ecoada" no Cliente como START.cnf(Ok).<br />
Seguin<strong>do</strong> a evolução pari passu: um joga<strong>do</strong>r digita Start; o Elemento de Utiliza<strong>do</strong>r lê o tecla<strong>do</strong>, e invoca<br />
START.req; o Módulo de Comunicações constrói a cStart-Pdu, e invoca TCP.req; o Nível Transparência faz o<br />
que lhe compete… No Servi<strong>do</strong>r, o Módulo de Comunicações invoca TCP.ind, após o que lê a cStart-Pdu, e<br />
passa os respectivos valores ao Elemento de Utiliza<strong>do</strong>r. Este procede à actualização das suas estruturas de<br />
Da<strong>do</strong>s e devolve Start.rsp; o resto intui-se - e está contempla<strong>do</strong> no Diagrama…<br />
Nota: Se, eventualmente, já se esgotou o número de <strong>Jogo</strong>s simultâneos, então o Servi<strong>do</strong>r responde com<br />
START.rsp(Sorry), que é "ecoada" no Cliente como START.cnf(Sorry).<br />
No que segue, e por comodidade,<br />
1. TCP.req/TCP.ind e UDP.req/UDP.ind ficam implícitas: apenas se referem as cPlay-Pdu, sPlay-Pdu,<br />
etc.. As coisas são figuradas como se fosse o próprio Módulo de Comunicações a acrescentar/remover o<br />
PDU-length - e ele mesmo a invocar as primitivas write/read de acesso ao TCP.<br />
2. Ficam também implícitas as primitivas START.req, START.ind, etc. - no senti<strong>do</strong> de que se apresenta a<br />
evolução <strong>do</strong> conjunto Elemento-de-Utiliza<strong>do</strong>r+Módulo-de-Comunicações (e não cada um em separa<strong>do</strong>).<br />
Com isso, perde-se uma certa coerência: a referência ao TCP é feita em termos de primitivas (write,<br />
read, etc.) - que não em termos das correspondentes PDUs (segmentos); já a referência ao Módulo de<br />
Comunicações é feita em termos das suas PDUs - que não em termos das correspondentes primitivas.<br />
3. Omite-se o sufixo -Pdu nos nomes das PDUs, tanto nos Diagramas como nas Tabelas dele derivadas.<br />
Para exemplificar, em JdG-rVIf (em Anexo) apresenta-se o Diagrama Temporal correspondente ao já<br />
figura<strong>do</strong> em JdG-rVIa, mas agora refeito ten<strong>do</strong> implícitas todas as primitivas excepto write e read.<br />
No Anexo-A.3, apresentam-se outros Diagramas Temporais concernin<strong>do</strong> o <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong>. Deverá ser trivial para<br />
o leitor congeminar outros cenários, nomeadamente:<br />
- Quanto o Abort é invoca<strong>do</strong> pelo Sócio, ainda não existe a necessária conexão-TCP com o Servi<strong>do</strong>r;<br />
- O Cliente de um (ou mais) <strong>Jogo</strong>s em curso s<strong>of</strong>re um crash; o Servi<strong>do</strong>r detecta-o e reporta-o aos pertinentes<br />
Clientes;<br />
- Ambos os joga<strong>do</strong>res invocam Abort…<br />
- Um <strong>do</strong>s joga<strong>do</strong>res invoca Start, outro invoca Join - e, precisamente nesse momento, o primeiro invoca<br />
Abort (fenómeno dito de colisão de Join e Abort).
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 10 <strong>of</strong> <strong>39</strong><br />
Nível Transparência, Diagrama <strong>do</strong> Receptor:<br />
IX - Preliminares: Diagramas de Esta<strong>do</strong>s<br />
A propósito <strong>do</strong> nível Transparência, apresentou-se o cenário JdG-rVb. Quiçá o leitor tenha repara<strong>do</strong>: a acção<br />
empreendida pelo receptor de read não é sempre a mesma: invoca TCP.ind somente ao terceiro read (fig. JdGrVc).<br />
Recorde-se:<br />
1. O Módulo de Comunicações compõe uma PDUMC, seja, por ex., a cPlay-Pdu 01 04 02 03(16), de N=4 bytes - e<br />
invoca TCP.req;<br />
2. o Nível Transparência prefacia-a com o PDU-length - onde inscreve o valor N -, resultan<strong>do</strong> a PDUNT 00 00<br />
00 04 01 04 02 03(16) e invoca write;<br />
3. o receptor começa por invocar read - para ler o tal PDU-length - fican<strong>do</strong> assim a conhecer o número de bytes<br />
da PDUMC;<br />
4. invoca então, um segun<strong>do</strong> read - para ler esses bytes - mas porventura nem to<strong>do</strong>s terão chega<strong>do</strong> já da Internet;<br />
terá então que esperar que o s<strong>of</strong>tware TCP lhe anuncie a chegada de mais bytes;<br />
5. quan<strong>do</strong> isso acontecer, invoca um terceiro read - com o que acaba de ler os bytes que faltavam… É então<br />
altura - e só então - de passar a PDUMC ao Módulo de Comunicações, através de TCP.ind.<br />
O receptor precisa, portanto, em cada momento, de<br />
memorizar o que já aconteceu (Enuncia<strong>do</strong> análogo vale<br />
para o transmissor). Isto remete para aquilo a que é<br />
tradicional, no mun<strong>do</strong> <strong>do</strong>s protocolos de comunicação,<br />
atribuir a designação de Esta<strong>do</strong>s.<br />
No cenário em causa, é pacífica a existência de <strong>do</strong>is<br />
Esta<strong>do</strong>s no receptor:<br />
- de um la<strong>do</strong>, o Esta<strong>do</strong> wLength: precede a chegada <strong>do</strong><br />
primeiro byte da PDUMC - e que só é aban<strong>do</strong>na<strong>do</strong> quan<strong>do</strong><br />
forem recebi<strong>do</strong>s to<strong>do</strong>s os (4) bytes <strong>do</strong> PDU-length;<br />
- por outro la<strong>do</strong>, o Esta<strong>do</strong> wLast: onde se ingressa com<br />
a chegada <strong>do</strong> último byte <strong>do</strong> PDU-length - e que perdura<br />
até chegar o último <strong>do</strong>s N bytes que compõem a PDUMC.<br />
Para bom entende<strong>do</strong>r, o receptor não permanece<br />
sempre no mesmo Esta<strong>do</strong>: ele irá evoluin<strong>do</strong> de um para<br />
outro Esta<strong>do</strong> - à medida que se forem suceden<strong>do</strong> eventos (no caso, os read após a chegada de bytes).<br />
Repare-se no prefixo w nos nomes <strong>do</strong>s Esta<strong>do</strong>s: recordam a palavra wait. Um Esta<strong>do</strong> não é defini<strong>do</strong> pelo<br />
que se "faz" nesse Esta<strong>do</strong>: é preferível pensá-lo como perío<strong>do</strong> de tempo em que não se faz nada! - senão<br />
esperar por algum evento, para decidir alguma acção - e, quan<strong>do</strong> é executada, assume-se que é instantânea!<br />
Nomeadamente,<br />
- wLength é o Esta<strong>do</strong> em que se espera que acabem de ser li<strong>do</strong>s<br />
to<strong>do</strong>s os (4) bytes <strong>do</strong> PDU-length; e, quan<strong>do</strong> isso acontecer, a leitura<br />
desse PDU-length é instantânea - tal como o é a passagem a wLast;<br />
- wLast é o Esta<strong>do</strong> em que se espera que acabe de ser li<strong>do</strong> o último<br />
byte da PDUMC; e, quan<strong>do</strong> isso acontecer, a invocação de TCP.ind é<br />
instantânea - tal como o é o regresso a wLength.<br />
A evolução entre os vários Esta<strong>do</strong>s pode ser representada de uma<br />
maneira apelativa (uma imagem vale mais que mil palavras…) por um<br />
Diagrama de Esta<strong>do</strong>s (fig. JdG-rVd). Neste,<br />
- os Esta<strong>do</strong>s são representa<strong>do</strong>s por círculos/elipses, etiqueta<strong>do</strong>s pelos<br />
seus Nomes, no caso wLength e wLast;<br />
- para representar as transições possíveis entre os Esta<strong>do</strong>s, usam-se<br />
"setas"; cada uma delas dirige-se <strong>do</strong> Esta<strong>do</strong> actual para o seguinte.<br />
- tais setas são etiquetadas por um rótulo com a estrutura<br />
Evento [,Predica<strong>do</strong>] / Acção
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 11 <strong>of</strong> <strong>39</strong><br />
No caso em apreço,<br />
- Primeiramente, há que ler os bytes que perfazem o PDU-length; depois, há que ler os N bytes que constituem o<br />
corpo da PDUMC. Pelo que será pacífica a concepção de uma Variável de Esta<strong>do</strong>, seja NMax, que memorize o<br />
número máximo de bytes expectáveis no Esta<strong>do</strong> em causa.<br />
- Esse valor NMax será fixa<strong>do</strong> (em 4 ou PDU-length) quan<strong>do</strong> se ingressa em wLength ou wLast - após o que irá<br />
decrescen<strong>do</strong> até zero, que é a condição de progresso para o outro Esta<strong>do</strong>. Pelo que será pacífica a concepção de um<br />
predica<strong>do</strong>, seja p1, que é o resulta<strong>do</strong> lógico (TRUE ou FALSE) da seguinte afirmação: "acabou de se ler os bytes<br />
que faltavam";<br />
- o Evento é o termo da execução <strong>do</strong> read invoca<strong>do</strong> no seguimento <strong>do</strong> sinal que anuncia a chegada de bytes da<br />
Internet;<br />
- a Acção é uma das seguintes: "iniciação de NMax", "decremento de NMax" e "invocação de TCP.ind".<br />
Concretamente (fig. JdG-rVd), admita-se que o receptor ingressou em wLength - ten<strong>do</strong> fixa<strong>do</strong> NMax=4.<br />
- se for sinalizada a chegada de bytes, o receptor invoca read (solicitan<strong>do</strong> a leitura de, até, 4 bytes); NMax é<br />
decrementa<strong>do</strong> <strong>do</strong> número de bytes efectivamente li<strong>do</strong>s;<br />
- se p1 não se verificar (~p1 representa "ainda não se acabou de ler os bytes que faltavam, 0
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 12 <strong>of</strong> <strong>39</strong><br />
O leitor poderá conferir que, em ambos os Esta<strong>do</strong>s, o evento read, quan<strong>do</strong> p1=FALSE, conduz à mesma<br />
Acção, a saber: "Decrementa NMax". Isso sugere o colapso desses Esta<strong>do</strong>s em um único, seja wData (fig. JdGrVf),<br />
pelo artifício de acrescentar uma Variável de Esta<strong>do</strong> mais, seja B, com o seguinte comportamento:<br />
- é iniciada a -1;<br />
- é fixada no tamanho, N, da PDUMT, quan<strong>do</strong> enfim se tiverem<br />
li<strong>do</strong> os primeiros 4 bytes;<br />
- e é reposta em -1 quan<strong>do</strong> se tiverem li<strong>do</strong> to<strong>do</strong>s os N bytes<br />
dessa PDU.<br />
Agora, existe somente um Esta<strong>do</strong> e, obviamente, uma seta<br />
apenas. Aquilo que muda é a Acção a executar aquan<strong>do</strong> <strong>do</strong> read:<br />
irá depender de p1 e de um novo predica<strong>do</strong>, a saber, p2 - que<br />
deverá ser o resulta<strong>do</strong> lógico da seguinte afirmação: "B é igual a -<br />
1".<br />
Com menos Esta<strong>do</strong>s, e menos setas, adivinha-se que o<br />
subsequente código será mais simples de desenhar, corrigir e<br />
manter…<br />
Nota: o objectivo da precedente discussão é somente a abordagem a conceitos como sejam: Esta<strong>do</strong>s,<br />
Transições, Predica<strong>do</strong>s, Acções, Diagramas e Tabelas de Esta<strong>do</strong>. Pelo que o Diagrama apresenta<strong>do</strong> em JdGrVd<br />
não há-de entender-se como definitivo. De facto, ele não funciona!<br />
- por um la<strong>do</strong>, considere-se, nomeadamente, o caso de, em wLast, chegarem NMax+4 bytes; no Diagrama, fazse<br />
uma transição para wLength; todavia, não terá escapa<strong>do</strong> ao leitor que, nessa situação, se deveria permanecer<br />
em wLast! e, se só tivessem chega<strong>do</strong> NMax+3 bytes, transitar-se-ia para wLength - mas NMax deveria ser inicia<strong>do</strong><br />
a 1!<br />
- por outro la<strong>do</strong>, pode configurar-se read como bloqueante - isto é: quan<strong>do</strong> se executa read, o Sistema<br />
Operativo "suspende" o programa - sem sequer conferir se o utiliza<strong>do</strong>r digitou entretanto algo no tecla<strong>do</strong>…<br />
Uma solução (já entrevista no comentário que acompanha JdG-rIIc) é: que o read recolha num buffer tu<strong>do</strong> o<br />
que chegou da rede e se analise depois esse buffer - não deven<strong>do</strong> ser difícil ao leitor alterar o Diagrama de<br />
Esta<strong>do</strong>s em conformidade…<br />
- enfim, está-se pressupon<strong>do</strong> que o read se volve sempre na leitura de ao menos um byte… Ora, tal não é<br />
verdade: se tiver havi<strong>do</strong> close por parte <strong>do</strong> interlocutor remoto, ou o seu crash, o read volve-se em 0 bytes…<br />
Nível Transparência, Diagrama <strong>do</strong> Transmissor:<br />
Ao receptor (<strong>do</strong> Cliente ou Servi<strong>do</strong>r), há-de então associar-se um<br />
Diagrama de Esta<strong>do</strong>s… mas, ao transmissor, também! Porquê? A<br />
interrogação é legítima: no fim de contas, de que é que necessita o<br />
transmissor recordar-se? não lhe bastará, quan<strong>do</strong> lhe é passada uma<br />
PDU, prefaciá-la de um PDU-length e invocar write?<br />
Não é bem assim! Quan<strong>do</strong> se invoca write, designan<strong>do</strong> um buffer<br />
com 4+N bytes, nem sempre isso conduz, de facto, ao envio para a<br />
Internet de to<strong>do</strong>s eles: o s<strong>of</strong>tware TCP pode limitar-se a enviar apenas<br />
os primeiros w bytes (com w≤4+N) - pelo que é preciso invocar outro<br />
write (sobre os restantes N-w bytes); e quiçá seja necessário outro(s)<br />
mais - até terem si<strong>do</strong> envia<strong>do</strong>s to<strong>do</strong>s os 4+N bytes.<br />
Com estes consideran<strong>do</strong>s, é pacífica a existência de <strong>do</strong>is Esta<strong>do</strong>s no<br />
transmissor (fig. JdG-rVg):<br />
- de um la<strong>do</strong>, o Esta<strong>do</strong> wFirst: é aquele que precede a invocação de<br />
TCP.req;<br />
- por outro la<strong>do</strong>, o Esta<strong>do</strong> wNet: é aquele onde se ingressa quan<strong>do</strong> o número de bytes escritos (com write) é<br />
inferior ao total a enviar.<br />
Quanto às restantes componentes <strong>do</strong> Diagrama de Esta<strong>do</strong>s,<br />
- agora, será pacífica a concepção de uma Variável de Esta<strong>do</strong>, seja R, que memorize o número de bytes que<br />
resta escrever;
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 13 <strong>of</strong> <strong>39</strong><br />
- esse valor R será fixa<strong>do</strong> (em 4+N) quan<strong>do</strong> se ingressa em wNet - após o que irá decrescen<strong>do</strong> até zero, que é a<br />
condição de reingresso em wFirst. Pelo que será pacífica a concepção de um predica<strong>do</strong>, seja p4, que é o resulta<strong>do</strong><br />
lógico (TRUE ou FALSE) da seguinte afirmação: "acabou de se enviar os bytes que faltavam, R==0";<br />
- o Evento pode ser um <strong>do</strong>s <strong>do</strong>is seguintes: a invocação de TCP.req e o termo, Swrite, da execução de um write;<br />
- a Acção é uma das seguintes: "iniciação de R", "decremento de R" e "invocação de write de bytes com destino<br />
à Internet".<br />
Concretamente (fig. JdG-rVg), admita-se que o transmissor ingressou em wFirst - ten<strong>do</strong> fixa<strong>do</strong> R=0.<br />
- se for invocada TCP.req, o transmissor acrescenta o PDU-length, fixa R=4+N e invoca write (solicitan<strong>do</strong> a<br />
escrita de R bytes); R é depois decrementa<strong>do</strong> <strong>do</strong> número de bytes efectivamente escritos;<br />
- se p4 se verificar, o Esta<strong>do</strong> permanece sen<strong>do</strong> wFirst (seta a Este de wFirst);<br />
- caso contrário ("0
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 14 <strong>of</strong> <strong>39</strong><br />
X.A - Preliminares: a Arquitectura Modular <strong>do</strong> Elemento de Utiliza<strong>do</strong>r<br />
A. No Cliente,<br />
- a leitura <strong>do</strong> tecla<strong>do</strong> e a impressão no ecrã estão a cargo <strong>do</strong> Gestor da Interface;<br />
- o estabelecimento e fecho da conexão estão a cargo <strong>do</strong> Gestor da Conexão;<br />
- o Gestor dum <strong>Jogo</strong> (GM, Game Manager) dedica-se à evolução de um jogo de acor<strong>do</strong> com:<br />
- os coman<strong>do</strong>s <strong>do</strong> utiliza<strong>do</strong>r;<br />
- os sinais (accept e close) provin<strong>do</strong>s <strong>do</strong> Gestor da Conexão;<br />
- as PDUs recebidas da Rede.<br />
- o Boss: conforme ao coman<strong>do</strong> digita<strong>do</strong> ou PDU recebida, sinaliza-o ao pertinente Gestor de <strong>Jogo</strong>.<br />
No Diagrama, esse sinal é prefixa<strong>do</strong> por '-'. Eventualmente, esse Gestor poderá devolver (return) alguma<br />
indicação,como seja: SIGPIPE (a conexão fin<strong>do</strong>u) ou End (um <strong>Jogo</strong> GId acabou); ela vem prefixada por '+'<br />
Para não sobrecarregar os Diagrama de Esta<strong>do</strong>s com predica<strong>do</strong>s "PDU ou Coman<strong>do</strong> inváli<strong>do</strong>",<br />
- a entrega de um coman<strong>do</strong> ao Boss é feita através de um Filtro de Coman<strong>do</strong>s que o verifica, alertan<strong>do</strong> o<br />
utiliza<strong>do</strong>r para algum erro nele detecta<strong>do</strong>;<br />
- uma PDU recebida é de imediato passada por um Filtro de PDUs, que a rejeita se for achada inválida.<br />
Considere-se um coman<strong>do</strong> genérico. O Gestor da Interface lê-o, valida-o - e, se for o caso, alerta para eventuais<br />
erros de sintaxe; se não for o caso, reporta-o ao Boss. Este também o valida - reportan<strong>do</strong>, se for o caso, eventuais<br />
erros (por ex., "Já se atingiu o número máximo de jogos" ou "O GId não existe"); se não for o caso, prossegue:<br />
- se o coman<strong>do</strong> começa um jogo novo (Start ou Join), reserva-lhe um descritor livre, seja Gf, e invoca sobre ele<br />
o Gestor de <strong>Jogo</strong>, passan<strong>do</strong> Start ou Join como argumento;<br />
Adiante, far-se-á recurso à expressão "corre E em X" para significar "o Boss invoca o Gestor <strong>do</strong> <strong>Jogo</strong> X,<br />
passan<strong>do</strong>-lhe E como argumento"; a frase anterior terminaria, então: "seja Gf, e corre esse coman<strong>do</strong> em Gf".<br />
- se se trata de Play, corre Play sobre o GId menciona<strong>do</strong>;<br />
- se se trata de Abort, transmite cAbort-Pdu; ademais, corre Abort nos GIds que respeitam a um <strong>Jogo</strong> local;<br />
- enfim, se se trata de List, envia a cList-Pdu.<br />
Para os casos <strong>do</strong>s coman<strong>do</strong>s Start, Join e Abort, o Boss contacta o Gestor de Conexão - que, se o não tiver<br />
feito ainda, enviará connect ao Servi<strong>do</strong>r. Quan<strong>do</strong> o accept chegar, o Gestor da Conexão passa-o ao Boss - que o irá<br />
correr em to<strong>do</strong>s os GIds; enuncia<strong>do</strong> análogo vale para a chegada de close da Rede.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 15 <strong>of</strong> <strong>39</strong><br />
Enquanto não chega o close, uma PDU que venha da Rede é entregue ao Boss; este valida-a e<br />
- se se trata de sGameList-Pdu ou sError-Pdu, passa o respectivo conteú<strong>do</strong> ao Gestor da Interface;<br />
- nos restantes casos, corre essa PDU no Gf ou GId a que ela diz respeito.<br />
Para bom entende<strong>do</strong>r: o Boss centra-se em eventos que não afectam nenhum jogo local (List ou mesmo Abort),<br />
ou que poderão afectar a mais que um, quiçá mesmo a to<strong>do</strong>s (close ou mesmo connect e Abort). Os Diagramas<br />
adiante têm então que cobrir a ambos, Boss e Gestor de <strong>Jogo</strong>, GM.<br />
B. No Servi<strong>do</strong>r,<br />
- a leitura e a escrita em disco estão a cargo <strong>do</strong> Gestor de Ficheiros;<br />
- o estabelecimento e fecho das conexões estão a cargo <strong>do</strong> Gestor de Conexões;<br />
- o Gestor de <strong>Jogo</strong> dedica-se à evolução de um jogo de acor<strong>do</strong> com:<br />
- as PDUs recebidas da Rede;<br />
- o expirar <strong>do</strong> Relógio TTTL;<br />
- os sinais (close) provin<strong>do</strong>s <strong>do</strong> Gestor da Conexões.<br />
- o Boss:<br />
- conforme à PDU recebida ou Relógio em causa, sinaliza-o ao(s) pertinente(s) Gestor(es) de <strong>Jogo</strong>(s).<br />
Considere-se a chegada de um connect. O Gestor de Conexões reserva um descritor para a nova conexão - e<br />
devolve accept. Um eventual close que venha da Rede é entregue ao Boss - que o irá sinalizar aos Gestores <strong>do</strong>s<br />
<strong>Jogo</strong>s que essa conexão suporta.<br />
Enquanto não chega o close, uma PDU que venha da Rede é entregue ao Boss, acompanha<strong>do</strong> da sua<br />
proveniência; este valida-a e<br />
- se se trata de cList-Pdu, devolve sGameList-Pdu;<br />
- se se trata de um cStart-Pdu, procura um descritor livre, seja GId, e corre a cStart-Pdu sobre ele;<br />
- nos restantes casos, determina o GId a que a PDU diz respeito, e corre a PDU sobre ele.<br />
Eventualmente, a PDU pode respeitar a mais que um par Cliente/Servi<strong>do</strong>r; é o caso de cPlay-Pdu e cAbort-Pdu.<br />
Pelo que levam à tomada de decisões (envio de sBoard-Pdu e sAbort-Pdu) que afectam outro par Cliente/Servi<strong>do</strong>r.<br />
Para bom entende<strong>do</strong>r: o Boss centra-se em eventos que não afectam nenhum Cliente específico (List), ou que<br />
envolvem mais que um jogo ou Cliente (close, cPlay-Pdu, cAbort-Pdu e o expirar <strong>do</strong> relógio TTTL).<br />
X.B - Eventos no <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong><br />
Uma primeira questão é: quais os Eventos que provocam alterações em um par Cliente-Servi<strong>do</strong>r? Eventos são<br />
algo inespera<strong>do</strong>, "aleatorio". Consideran<strong>do</strong> a Arquitectura acima, o conjunto Elemento de Utiliza<strong>do</strong>r + Módulo de<br />
Comunicações deve ser sensível a três Classes de eventos:<br />
1. aqueles ocorri<strong>do</strong>s "em cima" - nomeadamente:<br />
- o "input" digita<strong>do</strong> pelo joga<strong>do</strong>r (Start, Join, etc.);<br />
- a tomada de decisões pelo Servi<strong>do</strong>r, a saber: o envio de Ok/Sorry, ou <strong>do</strong> Tabuleiro ou Listagem de jogos, o<br />
aborto de um <strong>Jogo</strong>, o fecho da conexão, etc.<br />
2. aqueles ocorri<strong>do</strong>s "em baixo" -, nomeadamente:<br />
- para passar as PDUs provenientes da Internet: cStart-Pdu, cList-Pdu, etc.<br />
- para sinalizar fenómenos detecta<strong>do</strong>s pelo s<strong>of</strong>tware TCP/UDP, como seja:<br />
- a chegada de connect/accept ou close;<br />
3. os restantes - nomeadamente:<br />
- os que correspondem ao expirar de relógios, como seja: aquele, TConnext, associa<strong>do</strong> ao connect, e aquele,<br />
TimeToLive (TTTL), associa<strong>do</strong> à recepção de cStart-Pdu, cJoin-Pdu e cPlay-Pdu;<br />
- iniciação, eventualmente após crash da própria máquina…<br />
- o "signal" provoca<strong>do</strong> pelo write em um socket-TCP fecha<strong>do</strong> (SIGPIPE)…<br />
- a indicação "End" provocada pelo acabar de um <strong>Jogo</strong>…<br />
No Anexo-A.4a, condensaram-se num quadro os eventos significativos para cada parte: Cliente e Servi<strong>do</strong>r.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 16 <strong>of</strong> <strong>39</strong><br />
Nota: por comodidade, o texto adiante assume sincronismo entre a chegada de PDUs e a subsequente<br />
tomada de decisões <strong>do</strong> Servi<strong>do</strong>r. Por outras palavras: quan<strong>do</strong>, por ex., chega uma cList-Pdu ao Servi<strong>do</strong>r,<br />
assume-se que o tempo que ele demora a validar a PDU e a decidir enviar a Listagem de <strong>Jogo</strong>s é zero! A<br />
consequência disso é: haven<strong>do</strong> que, na Tabela de Esta<strong>do</strong>s, dedicar uma linha ao evento "chegada de cList-<br />
Pdu", essa mesma linha cobre o evento "o Servi<strong>do</strong>r decidiu enviar uma sGameList-Pdu com a Listagem".<br />
X.C - Esta<strong>do</strong>s no <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong><br />
Para a execução <strong>do</strong>s coman<strong>do</strong>s Start, Join, Play e Abort, exige-se uma conexão-TCP entre Cliente e Servi<strong>do</strong>r.<br />
Em ordem ao seu estabelecimento, o Cliente precisa enviar connect - deven<strong>do</strong> o Servi<strong>do</strong>r responder-lhe com<br />
accept. Com este pano de fun<strong>do</strong>, serão pacíficas as asserções seguintes:<br />
No Boss <strong>do</strong> Cliente, convirá definir três Esta<strong>do</strong>s, com os significa<strong>do</strong>s adiante:<br />
- closed: ainda não se iniciou o estabelecimento de uma conexão-TCP com o Servi<strong>do</strong>r;<br />
- wAccept: ten<strong>do</strong> o joga<strong>do</strong>r invoca<strong>do</strong> Start, Join ou Abort, o Cliente enviou connect, está aguardan<strong>do</strong> accept;<br />
- cConnected: já foi recebi<strong>do</strong> accept após ter-se envia<strong>do</strong> connect.<br />
No Servi<strong>do</strong>r, convirá, por cada Cliente, definir <strong>do</strong>is Esta<strong>do</strong>s, com os significa<strong>do</strong>s adiante<br />
- wConnect: ainda não se concluiu o estabelecimento de uma conexão-TCP com esse Cliente;<br />
- sConnected: já foi envia<strong>do</strong> accept após se ter recebi<strong>do</strong> connect.<br />
X.D - Diagramas de Esta<strong>do</strong>s no <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong><br />
Nos Anexos A.4b e A.4c, listam-se Variáveis de Esta<strong>do</strong> e Predica<strong>do</strong>s que convirá definir para o <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong>.<br />
Diagramas de Esta<strong>do</strong> no Cliente: o Boss<br />
Em JdG-rXc (no Anexo-A.4b), apresenta-se o Diagrama de Esta<strong>do</strong>s <strong>do</strong> Boss - para os casos em que é<br />
irrelevante o Esta<strong>do</strong> em que ele se encontra:<br />
- o joga<strong>do</strong>r digita Start ou Join, mas o Cliente recorda-se que ele já embarcou em 4 <strong>Jogo</strong>s (p10): apresenta<br />
Sorry no ecrã …<br />
- o joga<strong>do</strong>r digita List; pelo que se fixa ListReqed=TRUE e se envia a cList-Pdu; em caso de sucesso, virá a<br />
receber a sGameList-Pdu, viabilizan<strong>do</strong> a impressão no ecrã da listagem <strong>do</strong>s <strong>Jogo</strong>s em curso (no Sistema).<br />
Em JdG-rXd (no Anexo-A.4b), apresenta-se o Diagrama <strong>do</strong> Boss para o caso p10=FALSE:<br />
- em closed, o joga<strong>do</strong>r pode digitar Start ou Join. O Cliente:<br />
- determina o menor valor Gf cujo Role seja -1, incrementa Gc; corre o coman<strong>do</strong> digita<strong>do</strong> sobre Gf;<br />
- envia connect ao Servi<strong>do</strong>r, arma o TimeOut Tc e transita para wAccept;<br />
- em closed, o joga<strong>do</strong>r pode também digitar Abort. O Cliente:<br />
- salvaguarda em AList o(s) GId(s) associa<strong>do</strong>s a Abort;<br />
- envia connect ao Servi<strong>do</strong>r, arma o TimeOut Tc e transita para wAccept;<br />
- em closed, o joga<strong>do</strong>r pode digitar Play.<br />
- o Cliente apresenta Sorry no ecran;<br />
- em wAccept, há que prever o caso de o joga<strong>do</strong>r ser o Lucky Luke (precisamente ele! - que dispara mais rápi<strong>do</strong><br />
que a própria sombra…): ten<strong>do</strong> já digita<strong>do</strong> Start, Join ou Abort, e antes de chegar accept, invoca um desses três<br />
coman<strong>do</strong>s…<br />
O Cliente procede como em closed - com a excepção de que, agora, já não envia connect nem arma o Tc.<br />
- em wAccept, o joga<strong>do</strong>r pode digitar Play.<br />
- o Cliente apresenta Sorry no ecran;<br />
- em wAccept, o Cliente pode receber accept. Cancela Tc e:<br />
Se a AList não estiver vazia, isto é, o joga<strong>do</strong>r digitou um ou mais Abort (p11):
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 17 <strong>of</strong> <strong>39</strong><br />
- por cada GId nela salvaguarda<strong>do</strong>, envia a correspondente cAbort-Pdu;<br />
- para cada GId que for um <strong>Jogo</strong> local (na prática, um que tenha si<strong>do</strong> associa<strong>do</strong> a um Join), corre Abort<br />
sobre ele;<br />
- enfim, esvazia a AList;<br />
Se Gc>0 (p17), o Cliente corre accept em cada um <strong>do</strong>s Gs:<br />
- na prática, consoante seja S ou J, assim o Gestor de <strong>Jogo</strong>s envia a cStart-Pdu ou a cJoin-Pdu;<br />
Na cStart-Pdu, deve inscrever-se o pertinente Gf, que deve ser ecoa<strong>do</strong> pelo Servi<strong>do</strong>r na<br />
correspondente sStart-Pdu<br />
Enfim:<br />
- se algum <strong>do</strong>s Role não é -1 (p17), transita para cConnected;<br />
- caso contrário (~p17), envia close, efectua cInit (inicia o Cliente) e transita para closed.<br />
- em wAccept, o Timeout pode expirar (o accept não vem ou atrasou-se demasia<strong>do</strong>):<br />
Imprime um Warning, envia close, corre Abort em cada um <strong>do</strong>s Gs{}, efectua cInit e transita para closed;<br />
- em wAccept, pode receber-se close:<br />
Cancela Tc, imprime um Warning, corre Abort em cada um <strong>do</strong>s Gs{}, efectua cInit e transita para closed;<br />
- em cConnected, o joga<strong>do</strong>r pode digitar Start, Join ou Abort.<br />
O Cliente procede como em wAccept; no caso de Abort envia de imediato a cAbort-Pdu.<br />
- em cConnected, pode receber-se close:<br />
O Cliente imprime um Warning, corre Abort em cada um <strong>do</strong>s Gs{}, efectua cInit e transita para closed;<br />
- em cConnected, o joga<strong>do</strong>r pode digitar Play.<br />
- se o <strong>Jogo</strong> referi<strong>do</strong> não está em curso (p13), apresenta Sorry no ecran; caso contrário, corre Play nesse GId;<br />
- em cConnected, podem ser recebidas sStart-Pdu e sJoin-Pdu, sPlay-Pdu e sBoard-Pdu e sAbort-Pdu e sError-<br />
Pdu;<br />
- se a sJoin-Pdu, sPlay-Pdu ou sBoard-Pdu se referir a GId que não está em curso localmente (p13), é<br />
ignorada; caso contrário, corre-se essa PDU sobre esse GId;<br />
- se sStart-Pdu se referir a Gf que não está em curso (p22), é ignorada; caso contrário corre-se a sStart-Pdu<br />
sobre esse Gf;<br />
- se se receber sAbort-Pdu,<br />
- se algum GId que transportar ainda estiver em curso, corre-se sAbort-Pdu sobre esse GId;<br />
- se se receber sError-Pdu, é apresentada a Mensagem no ecran.<br />
Eventualmente, algumas das Acções submetidas ao Gestor de um <strong>Jogo</strong> GId (como sejam -Abort e -sBoard)<br />
poderão sinalizar, com +End, o seu termo. A ser o caso, decrementa-se Gc e fixa-se Role=-1 em GId.<br />
Diagramas de Esta<strong>do</strong>s no Cliente: o Gestor dum <strong>Jogo</strong><br />
A partir <strong>do</strong>s Diagramas Temporais, o desenho <strong>do</strong> Gestor dum <strong>Jogo</strong> num Cliente é pacífico. Antes de mais,<br />
atribuem-se etiquetas, nas verticais que representam o Cliente, aos momentos de espera, isto é, àqueles que<br />
precedem eventos significativos. Como ex., em JdG-rVIh re-desenha-se a fig JdG-rVIg (que esquematiza o começo<br />
dum jogo entre <strong>do</strong>is joga<strong>do</strong>res, W e), todavia agora refeita com essas etiquetas (sublinhadas):
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 18 <strong>of</strong> <strong>39</strong><br />
Na prática, cada etiqueta acaba por ser o Nome de um Esta<strong>do</strong>; concretamente:<br />
- antes de Start (ou de Join), ambos os Clientes se encontram no Esta<strong>do</strong> closed;<br />
- quan<strong>do</strong> o joga<strong>do</strong>r W digita Start, o Cliente invoca connect e transita para wAccept (espera accept);<br />
- quan<strong>do</strong> o accept chega, o Cliente envia cStart-Pdu e transita para wsStart (espera a sStart-Pdu);<br />
- com a chegada de sStart-Pdu, o Cliente transita para wsBoard (espera a sBoard-Pdu);<br />
- quan<strong>do</strong> o joga<strong>do</strong>r E digita Join, o Cliente invoca connect e transita para wAccept (espera accept);<br />
- quan<strong>do</strong> o accept chega, o Cliente envia cJoin-Pdu e transita para wsJoin (espera a sJoin-Pdu);<br />
- com a chegada de sJoin-Pdu, o Cliente transita para wPlay (espera uma jogada, Play);<br />
- quan<strong>do</strong> o joga<strong>do</strong>r E digita uma jogada Play, o Cliente envia cPlay-Pdu e transita para wsPlay;<br />
- com a chegada de sPlay-Pdu, o Cliente transita para wsBoard;<br />
- com a chegada de sBoard-Pdu a E, o Cliente mantém-se em wsBoard;<br />
- com a chegada de sBoard-Pdu a W, o Cliente transita para wPlay;<br />
- quan<strong>do</strong> o joga<strong>do</strong>r W digita uma jogada Play, o Cliente envia cPlay-Pdu e transita para wsPlay;<br />
- com a chegada de sPlay-Pdu, o Cliente transita para wsBoard;<br />
- com a chegada de sBoard-Pdu a W, o Cliente mantém-se em wsBoard;<br />
- com a chegada de sBoard-Pdu a E, o Cliente transita para wPlay;<br />
-etc…<br />
Repare-se:<br />
- em ambos os Clientes, o envio de connect é acompanha<strong>do</strong> da transição para wAccept; mas, quan<strong>do</strong> o accept<br />
chega, W envia cStart-Pdu e E envia cJoin-Pdu. Para diferenciar este comportamento, convém uma variável-de-<br />
Esta<strong>do</strong>, Role; é iniciada a -1 e, ao ingressar em wAccept, W fixa-a em Role=S e E fixa-a em Role=J;<br />
- <strong>do</strong> mesmo mo<strong>do</strong>, com a chegada <strong>do</strong> tabuleiro, em wsBoard, a vez de jogar virá a caber somente a um <strong>do</strong>s<br />
joga<strong>do</strong>res; para o efeito, convém uma variável-de-Esta<strong>do</strong>, MyTurn; é iniciada a FALSE ou TRUE, consoante Role<br />
for S ou J, e, sempre que um Cliente recebe o tabuleiro, ela deve mudar de valor.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 19 <strong>of</strong> <strong>39</strong><br />
Com o que fica dito, é pacífico o desenho <strong>do</strong> Diagrama de Esta<strong>do</strong>s correspondente ao Diagrama Temporal<br />
acima, cf. JdG-rXa:<br />
Repare-se:<br />
- o esta<strong>do</strong> inicial é closed;<br />
- com o coman<strong>do</strong> Start (e pressuposto que já se enviou connect), fixa-se Role e transita-se para wAccept;<br />
dever-se-á salvaguardar MyName (e, se for o caso de Join, GId);<br />
- com a chegada de accept, e consoante o valor de Role, envia-se cStart-Pdu ou cJoin-Pdu, e transita-se<br />
respectivamente para wsStart e wsJoin;<br />
- e assim por diante…<br />
Deve sublinhar-se: este é o Diagrama de Esta<strong>do</strong>s de um <strong>Jogo</strong>. Um droga<strong>do</strong> <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> poderá ter<br />
embarca<strong>do</strong> em vários jogos - e a cada um corresponde um Diagrama assim. Num deles, o Cliente poderá encontrarse<br />
no Esta<strong>do</strong> wPlay e em outro encontrar-se, por ex., em wsBoard ou wsJoin..<br />
Entretanto, deve também sublinhar-se: JdG-rXa teve em conta apenas o Diagrama Temporal em JdG-rVIg -<br />
pelo que se pode temer que esteja incompleto! Para o completar, a filos<strong>of</strong>ia é a mesma:<br />
- inscrever, nos demais Diagramas Temporais, etiquetas nomean<strong>do</strong> os Esta<strong>do</strong>s - isto é, as situações de espera -,<br />
e repercutir no Diagrama de Esta<strong>do</strong>s acima o comportamento que aqueles Diagramas determinam;<br />
- de qualquer mo<strong>do</strong>, e porquanto quiçá não se tenham desenha<strong>do</strong> Diagramas Temporais para todas as situações<br />
passíveis de ocorrer, repetir até à exaustão a seguinte meto<strong>do</strong>logia:<br />
a. interrogar-se: para cada Esta<strong>do</strong>, quais os eventos passíveis de ocorrer?<br />
b. investigar: para cada um desses eventos, qual o comportamento que o Cliente deve observar?<br />
c. e repercutir no Diagrama de Esta<strong>do</strong>s acima esse comportamento.<br />
Com o que fica dito, será pacífico o desenho final <strong>do</strong> Gestor de um <strong>Jogo</strong> em JdG-rXb (no Anexo A.4b):<br />
Diagramas de Esta<strong>do</strong> no Servi<strong>do</strong>r: o Boss<br />
Em JdG-rIXd (no Anexo-A.4c), apresenta-se o Diagrama de Esta<strong>do</strong>s <strong>do</strong> Boss para o caso em que:<br />
- se recebe a cList-Pdu; pelo que é enviada a sGameList-Pdu, com a listagem <strong>do</strong>s <strong>Jogo</strong>s em Curso: GId e<br />
respectivos Names;<br />
Em JdG-rIXe (no Anexo-A.4c), apresenta-se o Diagrama <strong>do</strong> Boss para os restantes casos:<br />
- em wConnect, pode receber-se connect de um socket…<br />
- o Servi<strong>do</strong>r deverá iniciar a respectiva SockGIds e devolver accept; e o Esta<strong>do</strong> dessa conexão deverá<br />
transitar para sConnected.<br />
- em sConnected, o Servi<strong>do</strong>r pode receber cStart-Pdu…<br />
- o Servi<strong>do</strong>r deverá, se já existem 4 jogos em curso (p10), devolver sStart-Pdu(Sorry,Gf);
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 20 <strong>of</strong> <strong>39</strong><br />
- caso contrário, deverá determinar o valor GId cujo All[GId].Names[S] esteja vazio e tenha si<strong>do</strong> menos<br />
recentemente usa<strong>do</strong>, e correr cStart-Pdu sobre esse GId;<br />
- em sConnected, o Servi<strong>do</strong>r pode receber cJoin-Pdu…<br />
- o Servi<strong>do</strong>r deverá, se o GId referi<strong>do</strong> não estiver em curso ou se já forem conheci<strong>do</strong>s os seus <strong>do</strong>is joga<strong>do</strong>res<br />
(p31), devolver sJoin-Pdu(Sorry);<br />
- caso contrário, deverá correr cJoin-Pdu sobre esse GId.<br />
- em sConnected, o Servi<strong>do</strong>r pode receber cPlay-Pdu…<br />
- o Servi<strong>do</strong>r deverá, se o GId referi<strong>do</strong> não estiver em curso (p32), devolver sJoin-Pdu(Sorry);<br />
- caso contrário, deverá correr cPlay-Pdu sobre esse GId.<br />
- em sConnected, o Servi<strong>do</strong>r pode receber cAbort-Pdu…<br />
O Servi<strong>do</strong>r varre os GIds referi<strong>do</strong>s: para cada um,<br />
- se não estiver em curso (p36), devolve sError-Pdu(Reason);<br />
- caso contrário, deverá correr -Abort sobre esse GId;<br />
- em sConnected, pode receber-se close:<br />
Varre a SockGIds, procuran<strong>do</strong> <strong>Jogo</strong>s em curso sobre a conexão: por cada um, corre -Abort sobre ele;<br />
- em sConnected, aquan<strong>do</strong> <strong>do</strong> processamento de cPlay-Pdu, cAbort-Pdu, expirar <strong>do</strong> Timer ou close, o Boss<br />
poderá receber <strong>do</strong> Gestor de <strong>Jogo</strong>s uma notificação, +End, de que um <strong>Jogo</strong> acabou. Deverá verificar, para cada uma<br />
das conexões que o suportam, se ele "era o último <strong>Jogo</strong>"; se o for (e não estiver ainda fechada), executa close sobre<br />
essa conexão.<br />
Diagramas de Esta<strong>do</strong>s no Servi<strong>do</strong>r: o Gestor dum <strong>Jogo</strong><br />
Seguin<strong>do</strong> a mesma filos<strong>of</strong>ia seguida para o Gestor dum <strong>Jogo</strong> no Cliente, pode agora traçar-se o Diagrama de<br />
Esta<strong>do</strong>s <strong>do</strong> Gestor dum <strong>Jogo</strong> no Servi<strong>do</strong>r, cf. JdG-rIXc (no Anexo-A.4c):<br />
XI - Tabela de Esta<strong>do</strong>s <strong>do</strong> Nível Superior <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong><br />
Nos Anexos A.4d e A.4e, apresentam-se as Tabelas de Esta<strong>do</strong> <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> - naturalmente extraídas <strong>do</strong>s<br />
Diagramas de Esta<strong>do</strong> precedentes. Ocasionalmente,<br />
- é invoca<strong>do</strong> Sorry, significan<strong>do</strong> que deve ser apresentada uma mensagem de Alerta no ecran;<br />
- nas quadrículas vazias dever-se-á invocar um procedimento Trace, que deverá inscrever num ficheiro "log" o<br />
evento ocorri<strong>do</strong>, para se poder reflectir sobre ele: pode ser um erro a corrigir necessariamente…<br />
XII - Estrutura das PDUs <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong><br />
Do que fica dito, será pacífico estruturar as PDUs, cf. Anexo-A.5.<br />
Elas incluem um conjunto de Campos - cuja estrutura poderá ser, por ex.:<br />
MyName, Reason: sequências de códigos Ascii de caracteres terminadas por ‘/0’.<br />
Nota: a adição <strong>do</strong> PDU-length para efeitos de transparência/delimitação-de-PDUs garante que o receptor sabe<br />
onde terminam, nomeadamente, a cStart-Pdu e a cJoin-Pdu – pelo que é desnecessário terminar MyName com ‘/0’.<br />
Enuncia<strong>do</strong> análogo vale para os campos Reason em sStart-Pdu, sJoin-Pdu, sPlay-Pdu e sAbort-Pdu.<br />
Mas isso já não é bem assim para o caso de sError-Pdu – pois que, em virtude da cAbort-Pdu a que ele se deve,<br />
pode referir mais que um GId. Neste caso, é mesmo necessário delimitar o campo Reason – intercalan<strong>do</strong> um ‘/0’<br />
antes da subsequente sucessão de GIds.<br />
GId, Gf, Line e Column: um octeto cada um, veiculan<strong>do</strong> um inteiro.<br />
Nota: esta representação é problemática: o Cliente e o Servi<strong>do</strong>r podem representar inteiros de maneira<br />
diferente; por ex., 01(16) pode, para um deles, significar o inteiro “1”, e, para o outro, significar "128". Na prática, o
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 21 <strong>of</strong> <strong>39</strong><br />
programa<strong>do</strong>r-em-C deve recorrer às funções htons (ou htonl) e ntohs (ou ntohl): antes de enviar um inteiro, deve<br />
convertê-lo na representação “net”, e, após recebê-lo, convertê-lo para a representação “host” local.<br />
Symbol: o código Ascii de “X” ou “O”<br />
Board: <strong>of</strong>erecem-se várias possibilidades; eis algumas delas:<br />
B.1 Uma sequência de Xs e Os, e Ls (estes últimos para representar casas-Livres). Por ex., o tabuleiro figura<strong>do</strong><br />
em JdG-rVIIb seria representa<strong>do</strong> por LXOLXLLLL: sucessivamente, assinala-se o conteú<strong>do</strong> das três casas da<br />
primeira linha, depois o das três casas da segunda linha, etc..<br />
B.2 Uma sequência de trios símbolo-linha-coluna – contemplan<strong>do</strong> apenas as casas já ocupadas. Para o mesmo<br />
tabuleiro, seria agora: X12O13X22. Este méto<strong>do</strong> seria mais eficiente que o anterior só até à terceira jogada,<br />
exclusive.<br />
B.3 Uma sequência X[LinhaColuna…]O[LinhaColuna…]. Para o mesmo tabuleiro, seria<br />
agora X1222O13. Repare-se que isto “funciona” pois nenhuma linha tem o valor O.<br />
List: Para representar a Listagem <strong>do</strong>s <strong>Jogo</strong>s já “Started”, uma possibilidade seria: uma<br />
sucessão de trios (ou apenas tuplos) consistin<strong>do</strong> em:<br />
GId, segui<strong>do</strong> de SName e, eventualmente, <strong>do</strong> JName<br />
Será esta representação problemática? Repare-se: os primeiros <strong>do</strong>is campos recebi<strong>do</strong>s serão GId (um inteiro) e<br />
SName (termina<strong>do</strong> por ‘/0’). Que virá a seguir? Se o byte a seguir ao ‘/0’ é um número (entre 1 e 4), então trata-se<br />
de um novo GId, caso contrário, trata-se <strong>do</strong> JName.<br />
Este raciocínio já não é isento de erros se o número máximo de jogos for, por ex., de 256 (e não apenas 4)! É<br />
que o byte após o ‘/0’ pode então ser interpreta<strong>do</strong> como um número ou como o código Ascii de um caracter (o das<br />
Maiúsculas estende-se entre 65 e 90)! Para que o Cliente saiba discernir entre os <strong>do</strong>is significa<strong>do</strong>s, há pois que<br />
acrescentar algo… Discutem-se adiante quatro possibilidades:<br />
More-Flag:<br />
No fun<strong>do</strong>, quan<strong>do</strong> o Cliente acaba de ler o ‘/0’ que finda o primeiro MyName, assalta-o uma dúvida: haverá<br />
ainda mais algum campo MyName? O Servi<strong>do</strong>r pode esclarecê-lo: logo após o ‘/0’, acrescenta uma flag, com <strong>do</strong>is<br />
valores possíveis, ‘1’, e ‘0’, significan<strong>do</strong> respectivamente que há/não-há mais nenhum campo MyName. Então,<br />
pela inspecção dessa flag, o Cliente sabe se, sim ou não, se deve preparar para ler um segun<strong>do</strong> campo MyName:<br />
Concretamente, para veicular a informação GId=2{Costa}GId=3{Luís,Catarina}, poderia enviar-se:<br />
2 Costa ‘/0’ (MF=)0 3 Luis ‘/0’ (MF=)1 Catarina ‘/0’<br />
Field-length:<br />
Repare-se: acima, é necessário enviar o termina <strong>do</strong>r ‘/0’: senão, o Cliente não sabe discernir nem a dimensão<br />
<strong>do</strong>s campos MyName (no ex., preenchi<strong>do</strong>s com Costa, Luís e Catarina) nem, por consequência, o local onde se<br />
encontra a More-Flag. Se em nomes de pessoas (sem acentos e outros quiproquós) isso será pacífico, já se torna<br />
problemático se se enviasse, por ex., a fotografia <strong>do</strong>s joga<strong>do</strong>res: poderia acontecer que, no seio daqueles três<br />
campos, viesse a aparecer o byte ‘/0’ – com a consequência óbvia: uma recepção errónea… Para o evitar, uma<br />
solução é: preceder MyName de um byte veiculan<strong>do</strong> o seu comprimento. Agora, a PDU para o caso acima seria:<br />
2 5 Costa 0 3 4 Luis 8 Catarina<br />
Repare-se: a seguir a “Costa” aparece 0, que significa: o campo seguinte (o segun<strong>do</strong> MyName) espraia-se por 0<br />
bytes, isto é: não existe.<br />
Termina<strong>do</strong>r e Character-stuffing:<br />
O méto<strong>do</strong> anterior de delimitação de um campo s<strong>of</strong>re de duas vulnerabilidades:<br />
- por um la<strong>do</strong>, MyName não pode exceder 255 caracteres; mas isso, dirá o leitor, e com razão, resolve-se<br />
reservan<strong>do</strong> para o Field-length mais que um byte: por ex., se lhe forem dedica<strong>do</strong>s <strong>do</strong>is bytes, esse campo poderá<br />
estender-se até cerca de 64 Kcaracteres…<br />
- por outro la<strong>do</strong>, para preencher o Field-length é necessário conhecer de antemão a dimensão <strong>do</strong> campo em<br />
questão; ora, isso nem sempre acontece. Ou mais precisamente: para o conhecer, há que gastar um certo tempo –
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 22 <strong>of</strong> <strong>39</strong><br />
atrasan<strong>do</strong> portanto o começo <strong>do</strong> envio da informação. Uma solução é utilizar um caracter termina<strong>do</strong>r a justapor ao<br />
campo, seja ‘t’. Agora, a PDU para o ex. acima seria:<br />
2 Costa t t 3 Luis t Catarina t<br />
Esse méto<strong>do</strong> é problemático se o campo em questão (MyName) puder conter esse termina<strong>do</strong>r - a consequência<br />
será óbvia: uma recepção errónea…Para o superar, uma solução é: quan<strong>do</strong> o ‘t’ deve ser interpreta<strong>do</strong> como<br />
termina<strong>do</strong>r, e apenas então, o Servi<strong>do</strong>r deve precedê-lo de um sinal, seja ‘s’. Mas, dir-se-á: e se aquele campo<br />
contiver precisamente a sequência ‘st’? – é que a consequência será óbvia: uma recepção errónea… Uma solução,<br />
dita de character stuffing, é: se um ‘s’ deve ser entendi<strong>do</strong> como byte de da<strong>do</strong>s (e não como byte de sinalização), ele<br />
deve ser duplica<strong>do</strong>. Com isso, a PDU para o ex. acima seria, antes:<br />
2 Cossta st st 3 Luiss st Catarina st<br />
Repare-se: os 's' em Costa e Luis aparecem em duplica<strong>do</strong>; e o 't' em Catarina não é precedi<strong>do</strong> de 's'. O Cliente<br />
seguirá uma filos<strong>of</strong>ia óbvia: se o ‘s’ aparecer duplica<strong>do</strong>, considera o primeiro como sen<strong>do</strong> “de da<strong>do</strong>s”, e remove o<br />
segun<strong>do</strong>; mas, se não aparecer duplica<strong>do</strong>, deve ser entendi<strong>do</strong> como “sinal” preceden<strong>do</strong> o termina<strong>do</strong>r.<br />
Field-type:<br />
Os três méto<strong>do</strong>s acima concernem a dimensão <strong>do</strong> campo JName. Mas há um ponto de vista alternativo: no<br />
fun<strong>do</strong>, o Cliente pretende saber se o byte após o primeiro MyName deve ser li<strong>do</strong> como GId ou como início de outro<br />
MyName; por outras palavras, o Cliente pretende saber qual o tipo de informação a ler: inteiro ou caracter? Eis<br />
uma solução: preceder cada valor de um Field-type.<br />
No exemplo entre mãos, podem definir-se os seguintes Field-types:<br />
GId-type=1 SName-type=2 JName-type=3<br />
Então, para veicular a informação GId=2{Costa}GId=3{Luís,Catarina], poderia enviar-se<br />
GId-type GId SName-type Length Value JName-type Length Value<br />
1 2 2 5 Costa<br />
1 3 2 4 Luis 3 8 Catarina<br />
O Cliente limita-se a, antes de recolher um valor, determinar o que está len<strong>do</strong>: um GId ou um MyName…<br />
Abra-se um parêntesis: os campos MyName acabam por ser representa<strong>do</strong>s por uma assim denominada<br />
estrutura TLV (Type/Lenth/Value). Intui-se que será a mais indicada para situações em que os campos não<br />
seguem necessariamente uma ordem pré-definida ou são opcionais.<br />
XIII - Apêndice: o Coman<strong>do</strong> List<br />
O coman<strong>do</strong> List (fig JdG-rVIe) foi trata<strong>do</strong> admitin<strong>do</strong> que o número máximo de <strong>Jogo</strong>s em curso é 4 - pelo que a<br />
listagem enviada numa sGameList-Pdu cabe dentro <strong>do</strong>s limites aceitáveis por UDP (algo menos que 64 Kbytes)…<br />
É então legítima a pergunta: e se aquele número puder ser bem maior? Claramente, a resposta por parte <strong>do</strong> Servi<strong>do</strong>r<br />
terá que se repartir por várias PDUs… - e, a manter-se o suporte "UDP", isso obriga a cuida<strong>do</strong>s extra: quiçá nem<br />
todas elas cheguem ao Cliente, e as que o fizerem talvez não o façam pela ordem em que foram transmitidas...<br />
É importante proporcionar ao joga<strong>do</strong>r a maior rapidez na visualização da listagem; para o efeito, convirá que, à<br />
medida que as PDUs cheguem ao Cliente, sejam de imediato apresentadas no ecran - sem por conseguinte esperar<br />
por aquelas que foram transmitidas anteriormente. Para que essa listagem seja coerente, cada PDU deverá veicular<br />
um conjunto fecha<strong>do</strong> de <strong>Jogo</strong>s - em vez de, a respeito de um GId, uma PDU referir o SName, e o respectivo<br />
JName seja referi<strong>do</strong> somente na PDU seguinte: é que não se sabe se alguma das PDUs se perderá pelo caminho…<br />
Para o efeito, deve ser o Elemento de Utiliza<strong>do</strong>r - que não o Módulo de Comunicações - a repartir a Listagem<br />
em várias PDUs (fig JdG-rVIi )…
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 23 <strong>of</strong> <strong>39</strong><br />
E há que definir a estrutura da sGameList-Pdu, por ex.,<br />
- o código da sGameList-Pdu, 12;<br />
- uma sequência de:<br />
- um GId, sob a forma de um inteiro;<br />
- um SName-Length, representan<strong>do</strong> o comprimento <strong>do</strong> SName;<br />
- esse SName;<br />
- um JName-Length, representan<strong>do</strong> o comprimento <strong>do</strong> JName associa<strong>do</strong> ao Join;<br />
- esse JName (se o JName-length for não-nulo);<br />
- uma Flag "MG", que será "1" se, e apenas se, a seguir ao <strong>Jogo</strong> corrente, se segue a especificação de<br />
mais outro <strong>Jogo</strong>.<br />
Eis um exemplo:<br />
12 4 5 Costa 8 Catarina 1 5 4 Joao 0 1 6 4 Luis 5 Maria 0<br />
Nota: o enuncia<strong>do</strong> postula que a Listagem seja suportada por UDP; se fora suportada por TCP, poder-se-ia<br />
acrescentar às PDUs algo que ajudasse o Cliente a perceber quan<strong>do</strong> é que a Listagem termina. Da discussão<br />
precedente, adivinham-se quatro possibilidades:<br />
- terminar cada sGameList-Pdu com uma MoreFlag - que só é 0 na última sGameList-Pdu…<br />
- incluir na primeira sGameList-Pdu um campo veiculan<strong>do</strong> o número de sGameList-Pdu que se irão enviar…<br />
- findar a última sGameList-Pdu com um termina<strong>do</strong>r de listagem, seja 'T', sinalizá-lo com 's' - e aplicar<br />
character stuffing…<br />
- conceber um novo tipo de PDUs, seja sEnd-Pdu, com o código 13, sem quaisquer parâmetros - e enviá-la logo<br />
após a última sGameList-Pdu…
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 24 <strong>of</strong> <strong>39</strong><br />
Anexo A: Especificação <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong><br />
A.1: Arquitectura de Comunicações<br />
A arquitectura de comunicações <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> des<strong>do</strong>bra-se em (fig JdG-rIVa):<br />
- um nível inferior, materializa<strong>do</strong> no par TCP/UDP, a cargo <strong>do</strong> Sistema Operativo;<br />
- um nível Transparência, ou de delimitação de PDUs, que converte invocações ("ecos" de PLAY.req,<br />
PLAY.ind, etc.) orientadas às PDUs em invocações (write, read) orientadas ao "Fluxo de octetos" - e vice-versa;<br />
- o Módulo de Comunicações, que empacota as PDUs e as passa ao nível Transparência - e vice-versa;<br />
- o elemento utiliza<strong>do</strong>r, no nível superior, que faz a interface:<br />
- no Cliente, com o terminal e o ecran através de que o Utiliza<strong>do</strong>r interage;<br />
- no Servi<strong>do</strong>r, com o sistema de ficheiros onde se regista a evolução <strong>do</strong>s eventos;<br />
- em ambos os casos, com as Comunicações - invocan<strong>do</strong> PLAY.req, PLAY.ind, etc...<br />
A.2: Primitivas de Comunicação<br />
Com. Primitiva .req por: Parâmetros<br />
Start START.req, START.ind Cliente MyName, Gf<br />
START.rsp, START.cnf Gf, Resulta<strong>do</strong>: Ok, GId, YourSymbol<br />
Ou Gf, Sorry + Reason]<br />
Join JOIN.req, JOIN.ind Cliente MyName, GId<br />
JOIN.rsp, JOIN.cnf GId, Resulta<strong>do</strong>: Ok, YourSymbol: X<br />
Ou GId, Sorry [+ Reason]<br />
Play PLAY.req, PLAY.ind Cliente GId, Line, Column<br />
PLAY.rsp, PLAY.cnf GId, Resulta<strong>do</strong>: Ok<br />
Ou GId, Sorry [+ Reason]<br />
BOARD.req, BOARD.ind Servi<strong>do</strong>r GId, Posição <strong>do</strong> <strong>Jogo</strong>;<br />
List L<strong>IST</strong>.req, L<strong>IST</strong>.ind<br />
(para os <strong>do</strong>is joga<strong>do</strong>res) Continue ou End + Winner: Name ou None<br />
Cliente<br />
GAMEL<strong>IST</strong>.req, GAMEL<strong>IST</strong>.ind Servi<strong>do</strong>r None ou GId/Name(s) [,…]<br />
Abort ABORT.req, ABORT.ind Cliente GId [,…]<br />
ABORT.req, ABORT.ind Servi<strong>do</strong>r<br />
(para os <strong>do</strong>is joga<strong>do</strong>res)<br />
GId<br />
ERROR.req, ERROR.ind Servi<strong>do</strong>r GId, Reason<br />
A.3 - Diagramas Temporais<br />
Start:<br />
Start é executa<strong>do</strong> sobre TCP. Existem <strong>do</strong>is cenários possíveis para a sua execução:<br />
1. já existe uma conexão-TCP estabelecida entre o Cliente e o Servi<strong>do</strong>r.<br />
Para tal cenário, apresentam-se <strong>do</strong>is diagramas: em to<strong>do</strong>s, começa-se por executar, no Cliente, START.req;<br />
ela é "ecoada" no Servi<strong>do</strong>r como START.ind.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 25 <strong>of</strong> <strong>39</strong><br />
JdG-rVIf: existe sucesso em Start: O Servi<strong>do</strong>r responde com START.rsp(Ok), que é "ecoada" no Cliente<br />
como START.cnf(Ok).<br />
JdG-rVIb: não existe sucesso em Start: No entrementes, ocorreu um crash <strong>do</strong> Servi<strong>do</strong>r, pelo que o Cliente,<br />
ao detectá-lo, gera localmente Sorry.<br />
2. ainda não existe nenhuma conexão entre o Cliente e o Servi<strong>do</strong>r.<br />
Para tal cenário, apresentam-se <strong>do</strong>is diagramas; em ambos, começa-se por estabelecer uma conexão-TCP<br />
entre o Cliente e o Servi<strong>do</strong>r.<br />
JdG-rVIc: o accept é devolvi<strong>do</strong> e recebi<strong>do</strong> - e executa-se START.req…<br />
JdG-rVId: o Servi<strong>do</strong>r não está liga<strong>do</strong>… pelo que não chega qualquer accept; este cenário pode ser detecta<strong>do</strong><br />
com um relógio, arma<strong>do</strong> aquan<strong>do</strong> da leitura <strong>do</strong> Start: ao fim de um certo perío<strong>do</strong> de tempo, ele expira - e pode<br />
dar-se a (má) notícia ao sócio…<br />
(Bem entendi<strong>do</strong>, se o accept chegar, aquele relógio é cancela<strong>do</strong>)<br />
Join:<br />
Join é também executa<strong>do</strong> sobre TCP. Os respectivos Diagramas acabam por ser análogos aos de Start.<br />
Play:<br />
Play é também executa<strong>do</strong> sobre TCP - e somente pode ser executa<strong>do</strong> sobre um <strong>Jogo</strong> já lança<strong>do</strong> com Start +<br />
Join. Deve ser pacífico traçar os respectivos Diagramas…
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 26 <strong>of</strong> <strong>39</strong><br />
JdG-rVIg: O Diagrama representa o começo de um jogo entre <strong>do</strong>is joga<strong>do</strong>res: um deles invoca Start, o outro<br />
invoca Join. O último faz então uma jogada (Play) - após o que o tabuleiro é divulga<strong>do</strong> a ambos; então, o primeiro<br />
digita, também ele, a sua jogada (Play)… Adiante-se que se utiliza extBoard para sinalizar uma decisão (o envio<br />
<strong>do</strong> Tabuleiro) desencadeada por um evento externo ao par Client/Servi<strong>do</strong>r afecta<strong>do</strong> por essa decisão<br />
List:<br />
List é executa<strong>do</strong> sobre UDP.<br />
JdG-rVIe: existe sucesso em List.<br />
JdG-rVIj: existe insucesso em List. O Servi<strong>do</strong>r ainda não foi activa<strong>do</strong>, pelo que o Cliente não aprsenta<br />
qualquer listagem.<br />
Abort:<br />
Abort é também executa<strong>do</strong> sobre TCP. Os Diagramas que se seguem contemplam alguns cenários possíveis.<br />
Adiante-se que neles se utiliza extAbort para sinalizar uma decisão <strong>do</strong> Servi<strong>do</strong>r (nomeadamente, o envio de<br />
sAbort-Pdu) desencadeada por um evento externo ao par Client/Servi<strong>do</strong>r afecta<strong>do</strong> por essa decisão.<br />
JdG-rVIIIa: Um <strong>do</strong>s joga<strong>do</strong>res, quiçá por achar que houve empate (ou porque está em má posição e não gosta<br />
de perder…) invoca Abort. O Servi<strong>do</strong>r sinaliza-o ao outro joga<strong>do</strong>r.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 27 <strong>of</strong> <strong>39</strong><br />
JdG-rVIIIb: Algum "brincalhão", que não um <strong>do</strong>s fulanos jogan<strong>do</strong>, faz-lhes uma partida de Carnaval: invoca<br />
Abort. Na prática, o Servi<strong>do</strong>r recebe uma cAbort-Pdu, e sinaliza-o aos Clientes desses fulanos.<br />
JdG-rVIIIc: No decorrer de um jogo, expira o Timeout após a última PDU recebida no Servi<strong>do</strong>r; este sinaliza-o<br />
aos Clientes <strong>do</strong>s <strong>do</strong>is joga<strong>do</strong>res.<br />
JdG-rVIIId: Alguém, que não um <strong>do</strong>s joga<strong>do</strong>res, invoca Abort - mas sem sucesso (por ex., porque o <strong>Jogo</strong><br />
referi<strong>do</strong> já havia termina<strong>do</strong>, entretanto). O Servi<strong>do</strong>r sinaliza-lhe isso mesmo.<br />
JdG-rVIIIe: O Servi<strong>do</strong>r s<strong>of</strong>re um crash. Os Clientes (já com conexões estabelecidas com o Servi<strong>do</strong>r), ao<br />
detectá-lo, geram localmente a Indicação de Aborto <strong>do</strong> Jog<br />
Eventos "de cima"<br />
Eventos "de baixo"<br />
Chegada de PDUs<br />
A.4a: Lista de Eventos<br />
Cliente Servi<strong>do</strong>r<br />
Coman<strong>do</strong>s <strong>do</strong> Joga<strong>do</strong>r:<br />
Start, Join, Abort, Play, List<br />
sStart-Pdu, sJoin-Pdu<br />
sPlay-Pdu, sBoard-Pdu<br />
sGameList-Pdu<br />
sAbort-Pdu, sError-Pdu<br />
Tomada de Decisões:<br />
Ok ou Sorry+Reason<br />
Board, GameList, Error<br />
cStart-Pdu, cJoin-Pdu,<br />
cPlay-Pdu, cList-Pdu,<br />
cAbort-Pdu<br />
Sinais oriun<strong>do</strong>s <strong>do</strong> TCP accept, close connect, close<br />
Eventos "internos" Iniciação <strong>do</strong> Cliente Iniciação <strong>do</strong> Servi<strong>do</strong>r<br />
Relógio TConnect<br />
Relógio TTTL<br />
End, SIGPIPE<br />
End, SIGPIPE
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 28 <strong>of</strong> <strong>39</strong><br />
JdG- rXc e JdG-rXd: o Boss no Cliente<br />
JdG-rXb: o Gestor de um <strong>Jogo</strong> no Cliente<br />
A.4b: Diagramas de Esta<strong>do</strong> <strong>do</strong> Cliente<br />
Variáveis de Esta<strong>do</strong>:<br />
- Gc: contabiliza o número de <strong>Jogo</strong>s que o Cliente tem em curso (pode ter até 4);<br />
- uma Tabela de Descritores, Gs[4], <strong>do</strong>s <strong>Jogo</strong>s em curso (até um máximo de 4); memoriza, por cada um,<br />
- Role, inicia<strong>do</strong> a -1, que conterá S=0 ou J=1 consoante tenha si<strong>do</strong> executa<strong>do</strong> Start ou Join;<br />
- Name, que será o argumento MyName associa<strong>do</strong> a Start ou Join;<br />
- o GId (GameId) atribuí<strong>do</strong> pelo Servi<strong>do</strong>r;<br />
- o Símbolo local (X ou O) atribuí<strong>do</strong> pelo Servi<strong>do</strong>r;<br />
- MyTurn, que será TRUE se e somente se a próxima Jogada couber ao joga<strong>do</strong>r local.<br />
- State, que regista o Esta<strong>do</strong> actual <strong>do</strong> <strong>Jogo</strong>.<br />
- uma Lista, AList, conten<strong>do</strong> os GIds associa<strong>do</strong>s a coman<strong>do</strong>s Abort;
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 29 <strong>of</strong> <strong>39</strong><br />
- ListReqed: será TRUE se e somente se foi enviada a cList-Pdu;<br />
Inicialmente, há que invocar-se cInit, que deverá fixar Gc=0, ListReqed=FALSE, e iniciar AList e Gs.<br />
Predica<strong>do</strong>s:<br />
- p10, valor lógico da afirmação "Gc é igual a 4";<br />
- p11, valor lógico de "AList não está vazia";<br />
- p12, valor lógico de "um GId assinala<strong>do</strong> num Abort ou sAbort-Pdu é local";<br />
- p13, valor lógico de "GId assinala<strong>do</strong> em Play ou s-Pdu não está em curso";<br />
- p14, valor lógico de "Line assinalada em Play inválida";<br />
- p15, valor lógico de "Column assinalada em Play inválida ";<br />
- p17, valor lógico de "após o cicloA,existem algum Gs{} com Role≠-1";<br />
- end, valor lógico de "a jogada termina o <strong>Jogo</strong>";<br />
- p19, valor lógico de "o GId assinala<strong>do</strong> num Join não é váli<strong>do</strong>";<br />
- p21, valor lógico de "MyName assinala<strong>do</strong> num Join não é váli<strong>do</strong>";<br />
- p22, valor lógico de "Gf assinala<strong>do</strong> em sStart-Pdu não está em curso";<br />
- p38, valor lógico de "não há <strong>Jogo</strong>s em curso sobre esta conexão";<br />
JdG-rIXd e JdG-rIXe: o Boss no Servi<strong>do</strong>r<br />
JdG-rIXc: o Gestor de um <strong>Jogo</strong> no Servi<strong>do</strong>r<br />
A.4c: Diagramas de Esta<strong>do</strong> <strong>do</strong> Servi<strong>do</strong>r<br />
Variáveis de Esta<strong>do</strong>:<br />
- Gc: contabiliza o número de <strong>Jogo</strong>s que o Servi<strong>do</strong>r tem em curso (pode ter até 4);
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 30 <strong>of</strong> <strong>39</strong><br />
- uma Tabela de Descritores, All[4], <strong>do</strong>s <strong>Jogo</strong>s em curso (até um máximo de 4); memoriza, por cada um,<br />
- Names [S..J] que serão os argumentos MyName associa<strong>do</strong>s a Start e Join,<br />
no que segue abrevia<strong>do</strong>s para SName e JName, respectivamente;<br />
- Sockets [SSock.. JSock] que serão os identifica<strong>do</strong>res das conexões por onde chegaram cStart e cJoin;<br />
- STurn, que será SSock ou JSock consoante a próxima Jogada couber ao emissor <strong>do</strong> Start ou ao <strong>do</strong> Join.<br />
- Board, a posição actual <strong>do</strong> Tabuleiro.<br />
- uma Lista, SockGIds, por cada Conexão estabelecida, referin<strong>do</strong> os GIds que a utilizam;<br />
Inicialmente, deve invocar-se o procedimento sInit, que deverá fixar Gc=0.<br />
Predica<strong>do</strong>s:<br />
- p10, valor lógico da afirmação "Gc é igual a 4";<br />
- p31, valor lógico de "o GId assinala<strong>do</strong> num cJoin-Pdu não é váli<strong>do</strong>";<br />
- p32, valor lógico de "o GId assinala<strong>do</strong> num cPlay-Pdu não é váli<strong>do</strong>";<br />
- p33, valor lógico de "Line assinalada em cPlay-Pdu inválida";<br />
- p34, valor lógico de "Column assinalada em cPlay-Pdu inválida ";<br />
- p35, valor lógico de "não é a vez deste joga<strong>do</strong>r" ou seja: "Sock ≠ STurn" ;<br />
- p36, valor lógico de "o GId assinala<strong>do</strong> em cAbort-Pdu não está em curso";<br />
- p37, valor lógico de "a conexão para o Cliente deixou de ter <strong>Jogo</strong>s em curso";<br />
- p38, valor lógico de "não há <strong>Jogo</strong>s em curso sobre esta conexão";<br />
Boss no Cliente:<br />
A.4d: Tabelas de Esta<strong>do</strong> <strong>do</strong> Cliente<br />
closed WAccept cConnected<br />
Start Gc++,Tc,connect,-Start{Gf} Gc++,-Start{Gf} Gc++,-Start{Gf}<br />
Join Gc++,Tc,connect,-Join{Gf} Gc++,-Join{Gf} Gc++,-Join{Gf}<br />
Play Sorry Sorry p13:Sorry<br />
~p13:-Play{GId}<br />
Abort AList++{GId},Tc,connect AList++{GId} cAbort;<br />
p12:-Abort{GId}<br />
sStartPdu -p22:-sStart{Gf}<br />
sJoinPdu -p13:-sJoin{GId}<br />
sPlayPdu -p13:-sPlay{GId}<br />
sBoardPdu -p13:-sBoard{GId}<br />
sAbortPdu -p38:Abort{GId}<br />
sErrorPdu Sorry<br />
accept ~Tc;<br />
p11: ciclo{AList}:{cAbort};<br />
p17:ciclo{Gs}:-accept<br />
⇒cConnected<br />
-p17:close<br />
close<br />
⇒closed<br />
~Tc;Sorry;ciclo{Gs}:-Abort;<br />
⇒closed<br />
TConnect Sorry; ciclo{Gs}:-Abort; close;<br />
⇒closed<br />
Esta<strong>do</strong> <strong>do</strong> Boss: Qualquer<br />
List ListRqed=T,cListPdu<br />
Sorry;<br />
⇒closed
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 31 <strong>of</strong> <strong>39</strong><br />
Gestor dum <strong>Jogo</strong> {Gf/GId} no Cliente:<br />
closed wAccept<br />
accept Role==J: cJoin<br />
⇒wsJoin<br />
Role==S: cStart<br />
⇒wsStart<br />
Start Role{Gf}=S<br />
⇒wAccept<br />
Join Role{Gf}=J<br />
⇒wAccept<br />
Abort +End<br />
⇒closed<br />
wsStart wsJoin wPlay wsPlay wsBoard<br />
Play Sorry p33∨p34:Sorry<br />
~p33∧~p34:cPlay<br />
⇒wsPlay<br />
Sorry Sorry<br />
sStart Ok: MyTurn=F<br />
⇒wsBoard<br />
Sorry: +End<br />
⇒closed<br />
sJoin Ok:MyTurn=T<br />
⇒wPlay<br />
Sorry: +End<br />
⇒closed<br />
sPlay Ok:<br />
⇒wsBoard<br />
Sorry:<br />
⇒wPlay<br />
sBoard end:+End<br />
⇒closed<br />
MyTurn:MyTurn=F<br />
⇒wsBoard<br />
~MyTurn:MyTurn=T<br />
⇒wPlay<br />
Abort +End<br />
+End<br />
+End<br />
+End +End<br />
⇒closed ⇒closed ⇒closed ⇒closed ⇒closed<br />
A.4e: Tabelas de Esta<strong>do</strong> <strong>do</strong> Servi<strong>do</strong>r<br />
Adiante, apresentam-se várias Tabelas. Adiante-se que, na coluna à esquerda, as PDUs (e close) vêem<br />
acompanhadas de uma referência à conexão por onde chegaram ao Servi<strong>do</strong>r).<br />
Boss no Servi<strong>do</strong>r:<br />
wcStart sConnected<br />
cStart<br />
p10:sStart{SSock,Gf,Sorry,Reason}<br />
{SSock}<br />
~p10: Gc++,-cStart{GId,SSock}<br />
cJoin<br />
{JSock}<br />
p31:sJoin{JSock,GId,Sorry,Reason}<br />
~p31:-cJoin{GId,JSock}<br />
cPlay<br />
p32:sPlay{Sock,GId,Sorry,Reason}<br />
{Sock}<br />
~p32: -cPlay {Sock,GId}<br />
cList sGameList {GameList} sGameList{GameList}<br />
connect Accept
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 32 <strong>of</strong> <strong>39</strong><br />
close<br />
{Sock}<br />
cAbort<br />
{Sock}<br />
Gestor dum <strong>Jogo</strong> {GId} no Servi<strong>do</strong>r:<br />
⇒sConnected<br />
ciclo{SockGIds}: -Abort{Sock,GId}<br />
⇒wcStart<br />
ciclo{GIds em cAbort}:<br />
p36:sError{Sock,Reason,GId}<br />
~p36: -Abort{Sock,GId}<br />
wcStart wcJoin wcPlay<br />
-cStart<br />
{SSock}<br />
sStart{SSock,Ok},TTTL ⇒wcJoin<br />
-cJoin<br />
{JSock}<br />
sJoin{JSock,Ok},STurn=J Sock,TTTL ⇒wcPlay<br />
-cPlay<br />
sPlay{Sock,Sorry} Sock≠STurn: sPlay{Sock,GId,Sorry}<br />
{Sock}<br />
Sock==STurn:<br />
end: ~TTTL, sPlay{Sock,GId,Ok},<br />
sBoard{SSock..JSock,GId,end}, +End<br />
⇒wcStart<br />
~end: sPlay{Sock,GId,Ok},<br />
sBoard{SSock..JSock,GId,~end},<br />
STurn =~STurn,TTTL<br />
TTTL sAbort,+End {GId}<br />
sAbort{SSock..JSock,GId}, +End<br />
⇒wcStart<br />
⇒wcStart<br />
-Abort<br />
{Sock}<br />
~SSock: ~TTTL, sAbort{SSock}, +End<br />
⇒wcStart<br />
~TTTL; +End,<br />
~SSock: sAbort{SSock,GId} ~JSock: sAbort{JSock,GId}<br />
⇒wcStart<br />
A5: Estrutura das PDUs<br />
PDU Cód Parâmetros PDU Cód Parâmetros<br />
cStart 1 Gf MyName<br />
sStart 2 Gf GId Symbol<br />
cJoin 3 GId MyName<br />
cAbort 5 GId GId…<br />
cPlay 8 GId Line Column<br />
sJoin<br />
2 Gf 0 Reason<br />
4 GId 1 Symbol<br />
4 GId 0 Reason<br />
sAbort 6 GId Reason<br />
sError 7 Reason GId GId…<br />
sPlay 9 GId Sorry Reason<br />
9 GId Ok<br />
sBoard 10 GId Board<br />
cList 11 sGameList 12 GameList
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 33 <strong>of</strong> <strong>39</strong><br />
Anexo B: Codificação <strong>do</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong><br />
Convirá recapitular a meto<strong>do</strong>logia percorrida a propósito <strong>do</strong> desenho <strong>do</strong> "<strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong>":<br />
- num primeiro passo, abor<strong>do</strong>u-se a sua Arquitectura de Comunicações…<br />
e, logo de seguida, a Arquitectura Modular <strong>do</strong> Elemento de Utiliza<strong>do</strong>r;<br />
- o passo seguinte centrou-se num elenco das Primitivas de Comunicação…<br />
e no desenho <strong>do</strong>s respectivos Diagramas Temporais - apresentan<strong>do</strong> as situações normais, e outras não tão<br />
normais assim: crashs, etc..<br />
- a partir daí, "deduziu-se" um conjunto de Diagramas Temporais…<br />
e, deles, chegou-se enfim às Tabelas de Esta<strong>do</strong>.<br />
Quan<strong>do</strong> - e somente quan<strong>do</strong>! - tais Tabelas estiverem especificadas, será hora de passar à codificação <strong>do</strong><br />
protocolo nalguma linguagem de programação! Atalhos eventualmente toma<strong>do</strong>s (como sejam a codificação antes<br />
da especificação) são sinónimo de desperdício de tempo, mormente na fase de debug/teste…<br />
A codificação faz-se a partir das Tabelas de Esta<strong>do</strong>! Adiante, e para não dispersar a atenção, a discussão ir-se-á<br />
centrar na Tabela <strong>do</strong> Gestor dum <strong>Jogo</strong> {GId} no Servi<strong>do</strong>r (no Anexo A.4e); fica todavia subentendi<strong>do</strong>: para<br />
completar o desenho de toda a Aplicação, é preciso, uma a uma, atender a todas as Tabelas a que se chegou!<br />
B.1: Acções Elementares<br />
Um relance de olhos àquela Tabela de Esta<strong>do</strong>s permite fazer um elenco das assim designadas Acções<br />
elementares: aparecem no seio das quadrículas, e são assim chamadas por não serem redutíveis a outras mais<br />
elementares. São elas:<br />
Acção Elementar Significa<strong>do</strong> Parâmetros<br />
sStart Preencher e enviar a sStart-PDU Socket, Gf, Resulta<strong>do</strong> [+Reason ou GId+Symbol]<br />
sJoin Preencher e enviar a sJoin-PDU Socket, GId, Resulta<strong>do</strong> [+Reason ou Symbol]<br />
sPlay Preencher e enviar a sPlay-PDU Socket, GId, Resulta<strong>do</strong> [+Reason]<br />
sBoard Preencher e enviar a sBoard-PDU Socket, GId, Board [+end]<br />
sAbort Preencher e enviar a sAbort-PDU Socket, GId, Reason<br />
TTTL Armar o relogio TimeToLive GId<br />
~TTTL Cancelar o relogio TimeToLive GId<br />
+End Sinalizar ao Boss o termo <strong>do</strong> <strong>Jogo</strong><br />
Uma primeira etapa na codificação consiste, precisamente, em estabelecer, par i passu, um elenco de<br />
procedimentos elementares, um para cada uma das supracitadas Acções Elementares. No caso, e na suposição de se<br />
utilizar a linguagem-C, ele poderá resolver-se nos procedimentos:<br />
int sStart ( Socket, Gf, Result, Reason, GId, Symbol ) ;<br />
int sJoin ( Socket, GId, Result, Reason, Symbol ) ;<br />
int sPlay ( Socket, Resulta<strong>do</strong> , Reason ) ;<br />
int sBoard ( Socket, Board ) ;<br />
int sAbort ( Socket, GId, Reason) ;<br />
int FireTTL ( GId ) ;<br />
int CancelTTL ( GId ) ;<br />
Um ponto obrigatório nesta etapa - porquanto há que codificar sStart, sJoin, etc. - é o estabelecimento definitivo<br />
da estrutura das PDUs - se é que isso ainda não foi feito. Um outro ponto é escrever aqueles procedimentos<br />
elementares recorren<strong>do</strong>:<br />
- às primitivas <strong>of</strong>erecidas pelo nível inferior (no caso, sendto e write)…<br />
- e (nomeadamente nos casos que envolvem a gestão <strong>do</strong> relógio) às chamadas ao sistema operativo específicas<br />
ao computa<strong>do</strong>r em que se há-de executar o protocolo.<br />
B.2: Acções Atómicas:<br />
Codificadas as acções elementares, é altura de considerar aquilo que se poderá designar de Acções Atómicas:<br />
acções que preenchem cada uma das quadrículas da Tabela em causa, e assim chamadas por não deverem nunca
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 34 <strong>of</strong> <strong>39</strong><br />
ser interrompidas. Pressuposto que no esta<strong>do</strong>-S acontece o evento-E, as correspondentes acções a tomar devem ser<br />
executadas e o esta<strong>do</strong>-seguinte assumi<strong>do</strong> - sem quebra <strong>do</strong> fluxo de controlo <strong>do</strong> programa: somente depois será hora<br />
de atender a algum outro evento.<br />
Na prática, ir-se-ão constituir tantas Acções Atómicas quantas as quadrículas da Tabela de Esta<strong>do</strong>s. Ei-las,<br />
deven<strong>do</strong> o leitor reparar no nome escolhi<strong>do</strong> para as designar: sempre o Nome-dum-Evento e o Nome-dum-Esta<strong>do</strong> -<br />
uni<strong>do</strong>s pela partícula Over.<br />
wcStart wcJoin wcPlay<br />
-cStart cStartOverwcStart cStartOverwcJoin cStartOverwcPlay<br />
-cJoin cJoinOverwcStart cJoinOverwcJoin cJoinOverwcPlay<br />
-cPlay cPlayOverwcStart cPlayOverwcJoin cPlayOverwcPlay<br />
TTTL TTLOverwcStart TTLOverwcJoin TTLOverwcPlay<br />
-Abort AbortOverwcStart AbortOverwcJoin AbortOverwcPlay<br />
O passo seguinte intui-se: trata-se de, quadrícula a quadrícula, elaborar as Acções Atómicas a partir <strong>do</strong>s<br />
verdadeiros tijolos que são as Acções Elementares; chegar-se-á a 15 procedimentos:<br />
int cStartOverwcStart ( )<br />
{sStart (S Sock, Gf, Ok, NULL , GId, Symbol ) ; FireTTL ( GId ) ; State=wcJoin ; return (0)}<br />
int cJoinOverwcJoin ( )<br />
{sJoin (JSock, GId, Ok, Symbol ) ; STurn= JSock; FireTTL ( GId ) ; State=wcPlay ; return (0)}<br />
int AbortOverwcJoin ( )<br />
{if ( Sock != SSock) {<br />
sAbort (Sock, GId, Reason) ; CancelTTL ( GId ) ; State=wcStart ; return ( End ) ; }}<br />
etc.<br />
Por mor de simplificação, foram omiti<strong>do</strong>s alguns detalhes, nomeadamente os Argumentos <strong>do</strong>s procedimentos.<br />
Entretanto, repare o leitor: a terminar cada um deles, e quan<strong>do</strong> existe mudança de Esta<strong>do</strong> para um outro Esta<strong>do</strong>,<br />
ocorre uma instrução da forma genérica State=NextState.<br />
B.3: O Núcleo <strong>do</strong> Protocolo<br />
Codificadas as Acções <strong>do</strong> protocolo, é hora de abordar o núcleo (kernel). Será pacífico entendê-lo como um<br />
ciclo "eterno"… Por cada iteração,<br />
- começa-se por aguardar o próximo evento;<br />
- aconteci<strong>do</strong> algum evento, determina-se a Acção Atómica a tomar quan<strong>do</strong> acontece o evento sucedi<strong>do</strong>;<br />
- invoca-se essa Acção;<br />
- no final, volta-se a aguardar o próximo evento…<br />
Em linguagem-C, ter-se-ia algo <strong>do</strong> género:<br />
gKeepGoing = TRUE ;<br />
<strong>do</strong> { /* Ciclo eterno, até gKeepGoing==FALSE */<br />
ActiveSocketCount = WaitNextEvent ( ) ; /* Aguarda o proximo evento */<br />
Action = GetHandler ( ActiveSocketCount , & Off ) ; /* Determina a Acção a executar */<br />
if ( Action ) RetVal = Action ( & Off ) ; /* Executa a Acção Atomica */<br />
} while ( gKeepGoing ) ;<br />
Adiante, apr<strong>of</strong>unda-se esta abordagem. Em particular, no Kernel constam <strong>do</strong>is novos procedimentos:<br />
WaitNextEvent e GetHandler. Como codificá-los?<br />
B.4: WaitNextEvent<br />
WaitNextEvent pode codificar-se de uma maneira assaz simples: mediante a invocação de uma chamada ao<br />
Sistema Operativo, select…
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 35 <strong>of</strong> <strong>39</strong><br />
ActiveSocketCount = select ( Width , & ReadFds , NULL , NULL , TimeOut ) ;<br />
Na prática, está-se requeren<strong>do</strong> ao Sistema Operativo que bloqueie o processo: ele fica suspenso até que ocorra<br />
um evento significativo - ou, mais formalmente: até que fique "ready" um "file-descriptor", no senti<strong>do</strong> de que se<br />
pode proceder a "read" nesse file-descriptor sem temer ficar bloquea<strong>do</strong>. O tempo de espera pode ser indefini<strong>do</strong> ou<br />
limita<strong>do</strong> por um TimeOut. A instrução após o select é executada somente após tal evento ocorrer. Tais filedescriptors<br />
podem ser os <strong>do</strong> tecla<strong>do</strong>, de sockets ou pipes.<br />
B.5: Handler de um Evento:<br />
Após WaitNextEvent, o Kernel invoca GetHandler. Considere-se, por exemplo, que o evento ocorri<strong>do</strong><br />
foi a chegada de uma PDU cStart; acontece que WaitNextEvent apenas informa que chegou mais uma PDU –<br />
porém sem discernir qual foi! Há, pois, que, primeiro que tu<strong>do</strong>, analisar essa PDU - para saber de que tipo é –, e<br />
somente depois se poderá invocar o pertinente Procedimento (no caso, cStartOver… )… Por outras palavras: as<br />
Acções Atómicas acima não podem ser invocadas directamente aquan<strong>do</strong> da ocorrência de um evento – há que fazêlo<br />
a partir dum procedimento genérico de tratamento desse evento, designemo-lo de Handler <strong>do</strong> evento, como seja:<br />
- OnUserCommand, Handler de um coman<strong>do</strong> <strong>do</strong> Utiliza<strong>do</strong>r (Start, Join, Abort, Play, List);<br />
- OnUDPData, Handler de uma PDU chegan<strong>do</strong> por um socket-UDP;<br />
- OnConnect, Handler de um connect (chegan<strong>do</strong> ao socket-listen <strong>do</strong> Servi<strong>do</strong>r);<br />
- OnAccept, Handler de um accept (chegan<strong>do</strong> ao socket <strong>do</strong> Cliente por onde se emitiu connect);<br />
- OnTCPData, Handler de uma PDU chegan<strong>do</strong> por um socket-TCP (cStart, sStart; etc.);<br />
- OnTimeout, Handler <strong>do</strong> expirar de um relógio.<br />
- OnSignal, Handler de um signal (SIGINT, SGALRM, SIGPIPE ou outro).<br />
Intui-se então que GetHandler devolve um destes Handlers – e que é ele quem vem a invocar a pertinente<br />
Acção Atómica.<br />
B.6: GetHandler<br />
GetHandler pode codificar-se de uma maneira assaz simples: mediante um Teste ao valor de retorno de<br />
select, ActiveSocketCount:<br />
- se for 0, isso significa tão somente que, desde que se invocou select, passou um tempo TimeOut - sem que<br />
nenhum <strong>do</strong>s file-descriptors tenha fica<strong>do</strong> "ready". Conclusão: GetHandler deve devolver OnTimeout.<br />
- se for -1, e errno!= EINTR, então houve erro; mas, se errno=EINTR, então houve uma interrupção - de que o<br />
expirar <strong>do</strong> relógio (posto a correr com alarm) é exemplo. Conclusão: GetHandler pode devolver NULL - ou<br />
algo mais preciso, com a designação genérica OnErr ou OnEintr…<br />
- se 1≤ ActiveSocketCount, e estiver On o bit em ReadFds correspondente ao file-descriptor <strong>do</strong> tecla<strong>do</strong>,<br />
isso significa que alguém premiu o tecla<strong>do</strong>. Conclusão: GetHandler deve devolver OnUserCommand.<br />
- se 1≤ ActiveSocketCount, e estiver On o bit em ReadFds correspondente ao file-descriptor de algum<br />
socket, isso significa que houve acitividade nesse socket: chegou algum Segmento da Internet (ou o socket ficou<br />
closed). Conclusão: GetHandler deve devolver, consoante o caso: OnUDPData, OnConnect ou OnTCPData…<br />
- e devolver, em Off, o socket em que houve actividade.<br />
B.7: Associação de Acções Genéricas a Sockets<br />
O procedimento select executa-se sobre uma máscara, seja MasterMask, de tamanho Width. Para que as<br />
coisas aconteçam como se disse, é necessário – antes da invocação de Kernel:<br />
- um procedimento, seja InitMasterMask, de inicialização dessa máscara;<br />
- a serem usadas comunicações por UDP, um procedimento, seja StartNetAccess(SOCK_DGRAM), de<br />
inicialização das comunicações-UDP – que devolva um UDPSocket para tais comunicações;<br />
- a serem usadas comunicações por TCP, um procedimento, seja StartNetAccess(SOCK_STREAM), de<br />
inicialização das comunicações-TCP – que devolva um ListenSocket para servir eventuais connects;<br />
- por cada posição na máscara a usar, uma instrução, seja AddToMasterMask, que a associe a uma das Acções<br />
de inicialização supracitadas; eis exemplos:
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 36 <strong>of</strong> <strong>39</strong><br />
AddToMasterMask ( gKeyboard , OnUserCommand , … ) ;<br />
AddToMasterMask ( UDPSocket , OnUDPData , … ) ;<br />
AddToMasterMask ( ListenSocket , OnConnect , … ) ;<br />
em que gKeyboard = fileno ( stdin ) ;<br />
Repare-se que AddToMasterMask pode vir a ser invocada mais tarde, nomeadamente aquan<strong>do</strong> da chegada de<br />
um connect - a que o Servi<strong>do</strong>r respondeu com accept:<br />
AddToMasterMask ( DataSocket , OnTCPData , … ) ;<br />
Com estes cuida<strong>do</strong>s, a chegada de, por exemplo, uma PDU cStart desbloqueia WaitNextEvent; a<br />
subsequente invocação de GetHandler devolve OnTCPData (e o socket Off em que ela foi recebida); intui-se que<br />
este deverá começar por invocar read sobre Off, analisar o tipo de PDU e, enfim, invocar a pertinente Acção<br />
Atómica (que deverá ser uma das seguintes: cStartOverwcStart, cStartOverwcJoin ou cStartOverwcPlay)…<br />
B.8: A Tabela de Acções Atomicas:<br />
Detecta<strong>do</strong> o tipo da PDU recebida, intui-se que a eleição da pertinente Acção atómica específica, como seja<br />
cStartOverwcJoin, envolve um fluxo assaz monótono:<br />
Determinação de TypeOfPDU (por análise da PDU recebida) ;<br />
if (TypeOfPDU == cStart ) {<br />
if ( State == wcStart )<br />
cStartOverwcStart ( ) ;<br />
else if ( State == wcJoin)<br />
cStartOverwcJoin ( ) ;<br />
else…<br />
}<br />
else if (TypeOfPDU == cJoin ) {<br />
}<br />
else if (TypeOfPDU == cPlay ) {<br />
}<br />
etc..<br />
Em vez da sequência if / else, pode-se utilizar uma sequência de switch / case, como seja:<br />
switch (TypeOfPDU) {<br />
case cStart :<br />
switch ( State ) {<br />
case wcStart :<br />
cStartOverwcStart ( ) ;<br />
break ;<br />
case wcJoin:<br />
cStartOverwcJoin ( ) ;<br />
break ;<br />
etc..<br />
São soluções pouco eficientes e flexíveis. Eis uma mais inteligente e elegante: defina-se um tipo, ActionType:<br />
typedef int ( * ActionType ) ( void * Parameter ) ;<br />
Na prática, o ActionType é um procedimento, com um parâmetro <strong>do</strong> tipo void*, que devolve um int.<br />
De seguida, define-se uma Tabela de acções atómicas, ActionsTable, com tantas linhas e colunas quantas as que<br />
a Tabela de Esta<strong>do</strong>s em causa tem; cada posição "linha/coluna" dessa ActionsTable será ocupada por um<br />
procedimento - precisamente aquele que codifica a acção atómica estabelecida na correspondente quadrícula<br />
"linha/coluna" daquela Tabela de Esta<strong>do</strong>s:<br />
ActionType ActionsTable [ 5 ] [ 3 ] = {<br />
cStartOverwcStart , cStartOverwcJoin , cStartOverwcPlay ,<br />
cJoinOverwcStart , cJoinOverwcJoin , cJoinOverwcPlay ,
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 37 <strong>of</strong> <strong>39</strong><br />
cPlayOverwcStart , cPlayOverwcJoin , cPlayOverwcPlay ,<br />
TTLOverwcStart , TTLOverwcJoin , TTLOverwcPlay ,<br />
AbortOverwcStart , AbortOverwcJoin , AbortOverwcPlay<br />
} ;<br />
Atribuem-se de seguida, a cada Esta<strong>do</strong> - wcStart, wcJoin e wcPlay - e Classe-de-Evento - cStart, cJoin, cPlay,<br />
TTL e Abort - considera<strong>do</strong>s, um valor inteiro entre 0 e, respectivamente, o número-de-colunas-menos-1 e o<br />
número-de-linhas-menos-1, inclusivé:<br />
# define wcStart 0 /* Identificação das colunas */<br />
# define wcJoin 1<br />
# define wcPlay 2<br />
# define cStart 0 /* Identificação das linhas */<br />
# define cJoin 1<br />
# define cPlay 2<br />
# define TTL 3<br />
# define Abort 4<br />
No caso em apreço (recepção de cStart), a Classe-de-Evento é 0; quanto ao Esta<strong>do</strong>, convirá registar a sua<br />
evolução num descritor associa<strong>do</strong> ao socket Off em causa - inicia<strong>do</strong> aquan<strong>do</strong> <strong>do</strong> connect/accept que activou esse<br />
socket.<br />
Conhecen<strong>do</strong>-se o State, então a Acção Atómica a invocar será ActionsTable [ Event ] [ State ] – e não mais é<br />
necessária a sequência if /else ou switch / case acima…<br />
B.9: Outras Inicializações:<br />
Antes de invocar o Kernel, quiçá se tenha que:<br />
- declarar o Handler <strong>do</strong> ctrl-C, com, por ex.: SetSignalCatcher ( SIGINT , OnSIGINT ) ;<br />
- declarar que se deve ignorar um eventual SIGPIPE, com: SetSignalCatcher ( SIGPIPE , SIG_IGN ) ;<br />
- declarar o Handler <strong>do</strong> relógio, com SetSignalCatcher ( SIGALRM , OnALRM ) ;<br />
(O leitor pode aceder a web.ist.utl.pt/~ist10898/public/<strong>Projecto</strong>s/CSources/index.htm, e proceder ao <strong>do</strong>wnload<br />
<strong>do</strong>s ficheiros Signals.c - que codifica SetSignalCatcher – e TCPAndUDPServer.c – que codifica um exemplo de<br />
OnSIGINT).<br />
Ademais, será de bom tom inicializar/limpar as estruturas de da<strong>do</strong>s pertinentes a cada socket…<br />
B.10: Relógios:<br />
Eventualmente, é preciso recorrer a múltiplos relógios – e todavia o computa<strong>do</strong>r encontra-se limita<strong>do</strong> a um<br />
único relógio físico. Como contornar esssa limitação?<br />
Uma solução é construir uma lista de descritores de relógios lança<strong>do</strong>s (mas ainda não-expira<strong>do</strong>s), ordena<strong>do</strong>s<br />
cronologicamente (pelo instante em que irão expirar) e acedida por quatro procedimentos:<br />
InitTimerModule, para iniciar aquela lista e registar (com SetSignalCatcher) o Handler <strong>do</strong> signal<br />
SIGALRM: ele deve ser trata<strong>do</strong> por OnTimeout;<br />
LaunchTimer, para se poder incluir nessa lista um novo descritor, indican<strong>do</strong>:<br />
- o instante em que deve expirar,<br />
- o Procedimento que deve ser invoca<strong>do</strong> se ele expirar,<br />
- e o eventual Argumento de que esse Procedimento necessitará.<br />
Eis um exemplo: TimerId = LaunchTimer ( cPeriod , OnTTL , … ) ;<br />
CancelTimer, para remover um descritor daquela lista<br />
GetRemainingTime, para indicar quanto tempo falta para o primeiro relógio expirar.<br />
Resta, apenas, utilizar uma de duas chamadas ao Sistema: select e alarm.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> 38 <strong>of</strong> <strong>39</strong><br />
- select deve ser invoca<strong>do</strong> quan<strong>do</strong> podem ser múltiplos os eventos exteriores a que o processo deve reagir, como<br />
seja a chegada de da<strong>do</strong>s (UDP ou TCP), pedi<strong>do</strong>s de conexão, o expirar de relógios e o premir <strong>do</strong> tecla<strong>do</strong>... Se não<br />
ocorrer nenhum <strong>do</strong>s eventos expectáveis durante o Perío<strong>do</strong> de tempo referi<strong>do</strong> no select, ele irá devolver 0 - e<br />
GetHandler irá devolver OnTimeout...<br />
- alarm pode ser invoca<strong>do</strong> quan<strong>do</strong>, de facto, um único evento é expectável. Eis algumas possibilidades:<br />
- a chegada dum Segmento (UDP ou TCP) a um Cliente que não espera por mais nada: antes de recvfrom ou<br />
read, executa LaunchTimer e alarm, e depois executa CancelTimer. Se não chegar nenhum segmento<br />
até ao Perío<strong>do</strong> de tempo em causa, o relógio irá expirar, e é executa<strong>do</strong> OnTimeout…<br />
- o expirar dum perío<strong>do</strong> máximo de pausa obrigatória: antes de pause, executa LaunchTimer e alarm. Ao<br />
fim de algum Perío<strong>do</strong> de tempo, o relógio irá expirar, e é executa<strong>do</strong> OnTimeout…<br />
OnTimeout deve varrer a lista de relógios e, para cada um que deveria ter expira<strong>do</strong> antes <strong>do</strong> momento actual,<br />
remove-o da lista e executa o Procedimento associa<strong>do</strong> (regista<strong>do</strong> aquan<strong>do</strong> <strong>do</strong> LaunchTimer que o criou)<br />
(O leitor pode aceder a web.ist.utl.pt/~ist10898/public/<strong>Projecto</strong>s/CSources/index.htm, e proceder ao <strong>do</strong>wnload<br />
de ficheiro Timers.c conforme ao que fica dito).<br />
B.11: Interrupções<br />
Por "atómica" que se reinvidique uma dada Acção, o que é facto é que ela demorará sempre um tempo maior ou<br />
menor a chegar ao fim: ela não é nunca instantânea! Por via disso, pode suceder que, enquanto ela se executa,<br />
venha a acontecer algum evento! Por exemplo: pode o relógio TTTL expirar quan<strong>do</strong> se está processan<strong>do</strong> a chegada<br />
de uma cPlay-Pdu (e aquele ainda não foi cancela<strong>do</strong>). Vice-versa, pode chegar uma cPlay-Pdu (que se atrasou)<br />
quan<strong>do</strong> se está já processan<strong>do</strong> o precedente expirar <strong>do</strong> relógio - mas, de facto, isto não é problemático: bastará<br />
invocar select só após o termo da ActionTable[] correspondente ao tratar <strong>do</strong> TimeOut.<br />
Com o fito de não quebrar o curso das Acções atómicas, uma solução é:<br />
- construir uma lista-<strong>do</strong>s-eventos-pendentes-de-análise;<br />
- sempre que ocorrer um evento no curso de execução de uma Acção atómica, suspendê-la por instantes, para<br />
invocar um Handler que registe esse evento naquela lista, e retomar de imediato a execução daquela Acção;<br />
- no fim, e antes de voltar a aguardar o próximo evento, em select, investigar primeiro se aquela lista está vazia;<br />
e, se o não estiver, proceder como se o evento que ela regista tivesse aconteci<strong>do</strong> precisamente agora.<br />
O Kernel ficaria então:<br />
gKeepGoing = TRUE ;<br />
<strong>do</strong> { /* Ciclo eterno, até gKeepGoing==FALSE */<br />
ActiveSocketCount = WaitNextEvent ( ) ; /* Aguarda o proximo evento */<br />
Action = GetHandler ( ActiveSocketCount , & Off ) ; /* Determina a Acção a executar */<br />
if ( Action ) RetVal = Action ( & Off ) ; /* Executa a Acção Atomica */<br />
while ( NotEmptyList ( PendingEventsList )) {<br />
PendingEvent = GetFirstOf (PendingEventsList ) ;<br />
PendingAction = GetPendingAction (PendingEvent ) ;<br />
RetVal = PendingAction (& Off ) ; /* Executa a Acção Atomica */<br />
} while ( gKeepGoing ) ;<br />
} while ( gKeepGoing ) ;<br />
B.12: Servin<strong>do</strong> Acções Longas a múltiplos Clientes…<br />
Pode a execução de uma Acção Atómica ser por demais demorada. No Servi<strong>do</strong>r, será algo problemático: como<br />
que um Cliente lhe cativa toda a atenção, enquanto os demais ficam "a ver navios". Pelo que, salvo indicação em<br />
contrário, um Servi<strong>do</strong>r deve servir os múltiplos Clientes em paralelo, isto é: gastan<strong>do</strong> um slot de tempo com um<br />
deles, depois um slot (com a mesma duração) com outro, etc., e depois voltar a gastar um slot com o primeiro, um<br />
slot com o segun<strong>do</strong> – e assim sucessivamente… Um continuum de ciclos em que por cada um gasta um slot com<br />
um Cliente - ao invés de acabar o trabalho requisita<strong>do</strong> pelo primeiro antes de passar ao segun<strong>do</strong>.
<strong>Pr<strong>of</strong></strong> V <strong>Vargas</strong>, <strong>IST</strong> <strong>Jogo</strong> <strong>do</strong> <strong>Galo</strong> (<strong>Resolução</strong>) <strong>Page</strong> <strong>39</strong> <strong>of</strong> <strong>39</strong><br />
Em ordem a lográ-lo, uma praxis comum é desencadear (com fork), por cada Cliente, um processo que lhe seja<br />
dedica<strong>do</strong> – relegan<strong>do</strong> para o Sistema Operativo a repartição <strong>do</strong> tempo <strong>do</strong> CPU pelas várias Acções concorrentes.<br />
Mas existe uma alternativa – que é a de fazer confluir to<strong>do</strong>s os Clientes sobre um mesmo Processo – e fazê-lo<br />
repartir o seu tempo – de uma maneira igualitária - pelos vários Clientes. Isso significa que a execução de uma<br />
Acção Atómica – se perdurar tempo demasia<strong>do</strong>! – deve ser repartida por vários slots de tempo.<br />
Seja, por exemplo, o caso da transmissão de um ficheiro deveras grande. Em vez de o transmitir to<strong>do</strong> para um<br />
Cliente, e somente depois ir servir os demais, poder-se-á fazer o seguinte:<br />
- enviar uma parte dele;<br />
- invocar um procedimento, seja SetPendingAction ( Off , Action ), que registe que no socket Off há<br />
uma Action temporariamente suspensa à espera de vez para continuar a executar-se;<br />
- regressar a WaitNextEvent, todavia configuran<strong>do</strong> o Timeout <strong>do</strong> select de mo<strong>do</strong> a não exceder 1.<br />
Então, WaitNextEvent ficará bloquea<strong>do</strong> não mais que 1.<br />
Quan<strong>do</strong> WaitNextEvent se desbloquear – e, se for o caso de a razão para tal ter si<strong>do</strong> um evento significativo, se<br />
ter executar o pertinente Handler -, dever-se-á varrer to<strong>do</strong>s os sockets activos: se houver algum em que esteja<br />
regista<strong>do</strong> um Acção suspensa à espera de vez, dever-se-á conceder-lhe mais algum tempo de execução…<br />
- no final, regressar a WaitNextEvent – todavia configuran<strong>do</strong> novamente o Timeout <strong>do</strong> select de mo<strong>do</strong> a não<br />
exceder 1 (se ainda houver alguma Action temporariamente suspensa à espera de vez).<br />
(O leitor pode aceder a web.ist.utl.pt/~ist10898/public/<strong>Projecto</strong>s/CSources/index.htm, e proceder ao <strong>do</strong>wnload<br />
<strong>do</strong> ficheiro Kernel.c - que codifica alguns <strong>do</strong>s procedimentos acima).<br />
B.13: Comentários a terminar<br />
Nível Transparência: A Arquitectura de Comunicações inclui um "Nível Transparência". Em termos de<br />
programação, isso conduz à inserção de código que saiba lidar com o PDU-length - em particular, que invoque a<br />
pertinente ActionTable somente quan<strong>do</strong> toda a PDU tiver si<strong>do</strong> já recolhida para um buffer;<br />
PDUs inválidas: A Arquitectura Modular <strong>do</strong> Elemento de Utiliza<strong>do</strong>r inclui um "Filtro de PDUs"; ele deverá<br />
conferir a validade de uma PDU provinda de um socket, rejeitan<strong>do</strong>-a se for caso disso. Em termos de programação,<br />
bastará invocar esse Filtro imediatamente antes de invocar a ActionTable.