Um cliente de cada vez: envia-recebe resposta pedido recebe trata envia o cliente bloqueia-se até que: o servidor receba a mensagem, a trate e lhe responda outros clientes aguardam pela vez Clientes: enviam pedidos aos servidores, aguardam resposta (ou não) Servidor: aguarda pedidos dos clientes, trata cada pedido recebido, envia resposta (ou não) os clientes vão pondo pedidos, independentes do ritmo a que o servidor os trata o servidor trata um pedido de cada vez Pode ser programada por: um modelo de comunicação baseada em mensagens (quer o sistema seja centralizado ou distribuído) um modelo de comunicação baseada em memória partilhada (só se o sistema for centralizado mono- ou multiprocessador) Os pedidos dos clientes e as respostas dos servidores são enviados através da invocação das operações de troca de mensagens: envia(mensagem, ) recebe(mensagem, ) Cliente: /*pedir*/ /*msgpedido*/ envia(msg); Servidor: /*aguardar*/ recebe(msgp); /*pedidomsgp*/ tratar(); /*se há resposta */ /*msgresposta*/ recebe(msgr); envia(msg); /*respostamsgr*/
Fila de pedidos em memória partilhada: reservar memória e associá-la ao processo (shmget, shmat no Unix) implementar funções para inserir e remover pedido, numa fila nessa memória estas permitem implementar operações equivalentes às envia e recebe ~ envia P(exmut); insere-pedido(); V(exmut); V(pedidos); /*aguarda resposta*/ P(pedidos); P(exmut); remove-pedido(); V(exmut); tratar(); /*responde*/ ~ recebe Uma Fila de Pedidos em Memória Partilhada Semáforo contador: pedidos, inicial 0 Semáforo exclusão mútua: exmut inicial 1 Pseudo-código do ciclo principal: //efectua serviço: a não ser que o tratamento de cada pedido seja "imediato" por exemplo, envolva apenas a simples consulta de uma estrutura em memória o tratamento sequencial dos pedidos pode originar apreciáveis tempos de espera Exemplo: pedidos concorrentes de 10 clientes, cada um demora 1s a tratar o primeiro é atendido em 1s o último demora 10s!! 1. pedidos de tratamento "imediato" 2. pedidos que demoram um tempo apreciável, mas dentro de limites conhecidos (p.ex. quando envolvem acessos a disco) 3. pedidos cujo tempo de tratamento é indeterminado. Exemplos: operações que dependem do próprio pedido ou de entidades externas ao serviço serviços que envolvem outros Exemplo: um cliente faz um pedido a um servidor S1 S1, por sua vez, precisa de pedir a um outro servidor S2 que realize uma parte do tratamento pedido o servidor S2 pode demorar um tempo imprevisível a responder a S1 por exemplo, devido a sobrecarga de pedidos no sistema, num dado momento.
cliente 1 cliente 2 pedido recebe trata envia operação demorada p.ex. entidade externa Um servidor sequencial: quando tratam um pedido, todos os pedidos de outros clientes ficam pendentes (mesmo que tenham pedidos "imediatos") Solução: usar a concorrência na implementação do servidor o servidor é um programa concorrente: conjunto de cada processo trata um pedido em concorrência com os restantes pedidos clientes " O servidor como um conjunto de concorrentes, que cooperam, distribuindo entre si o tratamento dos pedidos feitos por múltiplos clientes. C 1 C 2 C 3 pedidos pendentes Serv 1 Serv 2 servidores # Usando mensagens fila de mensagens (caixa de correio), gerida pelo SO Usando memória partilhada fila de pedidos em memória partilhada, gerida por todos com o auxílio de semáforos C 4 $ Como organizar os vários quando são criados como distribuem os pedidos quando terminam Várias abordagens criação dinâmica dos, quando necessário criação de um conjunto de logo no início etc % & Uma organização possível: um processo "vigilante" está atento à chegada de novos pedidos para cada pedido cria um "trabalhador", que atende o pedido depois de tratar o pedido, o processo "trabalhador" termina pedidos concorrentes são tratados por trabalhadores concorrentes
Pseudo-código do ciclo principal: if (fork() == 0) //efectua serviço: exit(0) Vantagens é fácil distribuir os pedidos pelos o servidor adapta-se aos pedidos concorrentes existentes Desvantagens a criação de um processo tem um custo pode estar constantemente a criar e terminar pode levar a que tenha muitos em execução & Outra possível organização do servidor: No início são criados N trabalhadores A distribuição dos pedidos pode ser: por um processo vigilante que distribui os pedidos via o próprio mecanismo de comunicação (p.ex. uma fila de mensagens de onde todos lêem) Cada trabalhador executa um ciclo de tratamento de pedidos, sem terminar Pseudo-código do ciclo principal: for (i=0; i<n-1; i++) if ( fork()==0 ) break; //efectua serviço: ' & Vantagens os já estão criados são reaproveitados Desvantagens a distribuição de trabalho pode ser complicada qual o número de trabalhadores ideal? poucos menos pedidos tratados muitos desperdício de recursos Outras possibilidades, exemplo: começar com poucos trabalhadores conforme o número de pedidos que afluem ao servidor esteja num crescendo ou num diminuendo, assim: vão-se criando novos trabalhadores ou vão-se deixando terminar alguns.
Estes servidores têm de se manter coerentes (partilham informação) pode ser difícil se há alterações nos dados do serviço Partilham memória? ficheiros/bd? sincronizam-se? Podemos por em causa, para certos casos, o uso de independentes Processos em que todos os recursos se mantém partilhados: memória, I/O, etc OU várias execuções dentro do mesmo processo: vários PC, Stack Isto é: Processos leves ou fluxos de execução (em inglês: threads)