domingo, 9 de fevereiro de 2014

ZeroMQ: Request Assíncrono/Reply Síncrono

Preparação


Dando continuidade ao post anterior que ilustra o padrão request/reply mais simples com o ZeroMQ, vou demonstrar uma aplicação desse padrão com um cliente (request) assíncrono e o servidor (reply) síncrono.

O primeiro passo é criar um projeto para o cliente -- eu criei um projeto do tipo console, versão 4.5 do .NET e dei o nome de zeromq.testes.request. Agora, inclua o ZeroMQ em seu projeto com o seguinte comando pelo NuGet:

PM> install-package clrzmq -pre

Obs.: No post anterior, eu usei uma versão legada dessa biblioteca. Agora, vamos sempre usar essa última versão.

Pronto, agora faça o mesmo procedimento para termos o projeto para o reply. Dei o nome do projeto do servidor de zeromq.testes.reply.

Vamos ao código


Para o cliente, use o seguinte trecho de código:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using ZeroMQ;

namespace zeromq.testes.request
{
 class Program
 {
  static void Main(string[] args)
  {
   using (ZmqContext context = ZmqContext.Create())
   using (ZmqSocket request = context.CreateSocket(SocketType.DEALER))
   {
    request.Connect("tcp://127.0.0.1:5555");

    request.ReceiveReady += (sender, eventArgs) =>
    {
     ZmqMessage reply_msg = eventArgs.Socket.ReceiveMessage();
     string reply_body = Encoding.Unicode.GetString(reply_msg[1].Buffer);
     Console.WriteLine("{0} - mensagem recebida: {1}", DateTime.Now, reply_body);
    };

    Poller poller = new Poller(new List { request });

    while (true)
    {

     string request_body = Guid.NewGuid().ToString();

     ZmqMessage request_msg = new ZmqMessage(new List 
     {
      Encoding.Unicode.GetBytes(string.Empty),
      Encoding.Unicode.GetBytes(request_body),
     });

     request.SendMessage(request_msg);

     poller.Poll();

     Console.WriteLine("{0} - mensagem enviada: {1}", DateTime.Now, request_body);

     Thread.Sleep(2000);
    }
   }
   
  }
 }
}

Para o servidor, use o seguinte trecho de código:

using System;
using System.Collections.Generic;
using System.Text;
using ZeroMQ;

namespace zeromq.testes.reply
{
 class Program
 {
  static void Main(string[] args)
  {
   using (var context = ZmqContext.Create())
   using (var reply = context.CreateSocket(SocketType.REP))
   {
    reply.Bind("tcp://*:5555");

    while (true)
    {
     ZmqMessage request_msg = reply.ReceiveMessage();

     Console.WriteLine("--------------------------------------");
     Console.WriteLine(DateTime.Now);
     Console.WriteLine("requisição recebida.", DateTime.Now);

     string reply_body = Encoding.Unicode.GetString(request_msg[0].Buffer)
      .Replace("-", string.Empty)
      .ToUpper();
     
     ZmqMessage reply_msg = new ZmqMessage(new List<byte[]> 
     {
      Encoding.Unicode.GetBytes(reply_body)
     });
     
     reply.SendMessage(reply_msg);
     
     Console.WriteLine("resposta enviada.");
    }
   }
  }
 }
}

Agora, execute os dois projetos juntos. Você deve ver as requisições e respostas sendo exibidas nas telas dos dois componentes.

Detalhes Sórdidos ou Truques


No post anterior, eu disse que o ZeroMQ era fácil. Eu menti! Bom, se você conhecer os truques dele, ele é fácil. Se não conhecer, vai achar que ele simplesmente não funciona. Eu descobri esses truques na dor, pois não estão claros nos exemplos da documentação oficial. Entretanto, eles são mencionados na parte textual da documentação -- acabei comprando o livro ZeroMQ: Messaging for Many Applications de Pieter Hintjens, um dos caras que construiu essa biblioteca (merece todo o crédito: http://en.wikipedia.org/wiki/Pieter_Hintjenshttp://hintjens.com/https://twitter.com/hintjens).

Bom, vamos aos truques.

Linha 18 do cliente: Este trecho não é um truque, é bem óbvio o que faz. Aqui, você está delegando um método anônimo para quando uma mensagem estiver pronta para ser recebida como todo bom modelo assíncrono que não trabalha com retornos de métodos.

Linhas de 25 e 40 do cliente: Para os sockets assíncronos, você precisa fazer o polling para que os eventos ReceiveReady e SendReady sejam disparados. Primeiro, você instancia um Poller e inclui todos os sockets de modelo assíncrono para fazer a multiplexação desses canais. Depois, você invoca o método Poll() dessa instância para aguardar uma resposta e invocar o ReceiveReady. (Este trecho foi atualizado para não usar threads. Aprendi que nunca se deve compartilhar um socket entre threads.)

Linha 32 do cliente: Uma mensagem pode ser composta de vários frames. Um socket do tipo Reply (REP), sempre espera uma mensagem com o primeiro frame vazio; se este não estiver vazio, ele simplesmente descarta a mensagem. Por isso, que nessa linha incluímos um frame vazio. Quando você usa o socket síncrono REQ, este frame vazio já é incluído automaticamente. No caso do DEALER, temos que fazê-lo manualmente.

Pronto, se souber sobre esse detalhes antes de brincar de request/reply com os sockets DEALER/REP, sua vida terá mais qualidade. Te garanto!

Nenhum comentário:

Postar um comentário