Programação assíncrona

Soquetes TCP assíncronos como uma alternativa ao WCF

James McCaffrey

Baixar o código de exemplo

Em um ambiente de tecnologias Microsoft, usar o Windows Communication Foundation (WCF) é uma abordagem comum para criar um sistema de cliente/servidor. É claro que há diversas alternativas ao WCF, cada uma com suas vantagens e desvantagens, incluindo os Serviços Web HTTP, API Web, DCOM, tecnologias Web do AJAX, programação de pipe nomeado e programação de soquete TCP bruto. Mas, se você levar em consideração fatores como esforço de desenvolvimento, capacidade de gerenciamento, escalabilidade, desempenho e segurança, em muitos casos, a abordagem mais eficiente é usar o WCF.

No entanto, o WCF pode ser extremamente complicado e pode ser excessivo em algumas situações de programação. Antes do lançamento do Microsoft .NET Framework 4.5, a programação de soquete assíncrona era, na minha opinião, muito difícil na maioria dos casos para justificar seu uso. Mas a facilidade de usar os novos recursos de linguagem await e async do C# muda o saldo, de modo que usar a programação de soquete para sistemas assíncronos de cliente/servidor agora é uma opção mais atraente do que costumava ser. Este artigo explica como usar esses novos recursos assíncronos do .NET Framework 4.5 para criar sistemas de software assíncronos cliente/servidor com alto desempenho e de nível baixo.

A melhor maneira de ver para onde estou indo é dar uma olhada no sistema cliente/servidor mostrado na Figura 1. No topo da imagem um shell de comando está executando um serviço assíncrono baseado no soquete TCP que aceita solicitações para calcular a média ou o mínimo de um conjunto de valores numéricos. Na parte do meio da imagem, há um aplicativo Windows Forms (WinForm) que recebeu uma solicitação para calcular a média de (3, 1, 8). Observe que o cliente é assíncrono - depois que a solicitação é enviada, enquanto aguarda o serviço responder, o usuário pode clicar no botão Say Hello três vezes, e o aplicativo passa a responder.

Demo TCP-Based Service with Two Clients
Figura 1 Demonstração de serviços baseado em TCP com dois clientes

A parte inferior da Figura 1 mostra um cliente do aplicativo Web em ação. O cliente enviou uma solicitação assíncrona para encontrar o valor mínimo de (5, 2, 7, 4). Embora não seja aparente na captura de tela, enquanto o aplicativo Web está aguardando a resposta do serviço, o aplicativo pode responder à entrada do usuário.

Nas seções que se seguem, mostrarei como codificar o serviço, o cliente do WinForm e o cliente do aplicativo Web. Ao longo do caminho, discutirei os prós e contras de usar soquetes. Este artigo pressupõe que você tenha, no mínimo, habilidades de nível intermediário em programação C#, mas não que você tenha vasta compreensão ou experiência significativa com programação assíncrona. O download de código que acompanha este artigo tem o código-fonte completo para os três programas mostrados na Figura 1. Removi a maior parte da verificação de erros comuns para deixar as ideias principais tão claras quanto possível.

Criando o serviço

A estrutura geral do serviço de demonstração, com algumas edições secundárias para economizar espaço, é apresentada na Figura 2. Para criar o serviço, iniciei o Visual Studio 2012, que tem o .NET Framework 4.5 exigido, e criei um novo aplicativo de console C# denominado DemoService. Como os serviços baseados em soquete tendem a ter funcionalidade limitada e específica, é melhor usar um nome mais descritivo em um cenário real.

Figura 2 A estrutura do programa de serviço da demonstração

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading.Tasks;
namespace DemoService
{
  class ServiceProgram
  {
    static void Main(string[] args)
    {
      try
      {
        int port = 50000;
        AsyncService service = new AsyncService(port);
        service.Run();
        Console.ReadLine();
      }
      catch (Exception ex)
      {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
      }
    }
  }
  public class AsyncService
  {
    private IPAddress ipAddress;
    private int port;
    public AsyncService(int port) { . . }
    public async void Run() { . . }
    private async Task Process(TcpClient tcpClient) { . . }
    private static string Response(string request)
    private static double Average(double[] vals) { . . }
    private static double Minimum(double[] vals) { . . }
  }
}

Depois que o código de modelo foi carregado no editor, modifiquei o uso das instruções no início do código-fonte para incluir System.Net e System.Net.Sockets. Na janela Gerenciador de Soluções, renomeei o arquivo Program.cs para ServiceProgram.cs e o Visual Studio renomeou a classe Program para mim automaticamente. Iniciar o serviço é simples:

int port = 50000;
AsyncService service = new AsyncService(port);
service.Run();

Cada serviço baseado em soquete personalizado em um servidor deve usar uma porta exclusiva. Os números de porta entre 49152 e 65535 geralmente são usados para serviços personalizados. Evitar as colisões de número de porta pode ser complicado. É possível reservar números de porta em um servidor usando a entrada ReservedPorts do Registro do sistema. O serviço usa um design OOP (programação orientada a objeto) e é instanciado por um construtor que aceita o número da porta. Como os números de porta do serviço são fixos, o número da porta pode ser inserido em código em vez de ser passado como um parâmetro. O método Run contém um loop while que vai aceitar e processar as solicitações do cliente até que o shell do console receba um pressionamento da tecla <enter>.

A classe AsyncService tem dois membros privados, ipAddress e port. Essencialmente, esses dois valores definem um soquete. O construtor aceita um número de porta e determina, de modo programático, o endereço IP do servidor. O método público Run aceita todas as solicitações, em seguida, calcula e envia respostas. O método Run chama o método auxiliar Process que, por sua vez, chama o auxiliar Response. O método Response chama os auxiliares Average e Minimum.

Há várias maneiras de organizar um servidor baseado em soquete. A estrutura usada na demonstração tenta encontrar o equilíbrio entre a modularidade e a simplicidade e, na prática, funcionou bem para mim.

Os métodos Constructor e Run do serviço

Os dois métodos públicos do serviço de demonstração baseado em soquete são apresentados na Figura 3. Depois de armazenar o nome da porta, o construtor usa o método GetHostName para determinar o nome do servidor e, em seguida, busca uma estrutura que contenha informações sobre o servidor. A coleção AddressList mantém diferentes endereços de máquina, inclusive endereços IPv4 e IPv6. O valor de enumeração InterNetwork significa um endereço IPv4.

Figura 3 Os métodos Constructor e Run do serviço

public AsyncService(int port)
{
  this.port = port;
  string hostName = Dns.GetHostName();
  IPHostEntry ipHostInfo = Dns.GetHostEntry(hostName);
  this.ipAddress = null;
  for (int i = 0; i < ipHostInfo.AddressList.Length; ++i) {
    if (ipHostInfo.AddressList[i].AddressFamily ==
      AddressFamily.InterNetwork)
    {
      this.ipAddress = ipHostInfo.AddressList[i];
      break;
    }
  }
  if (this.ipAddress == null)
    throw new Exception("No IPv4 address for server");
}
public async void Run()
{
  TcpListener listener = new TcpListener(this.ipAddress, this.port);
  listener.Start();
  Console.Write("Array Min and Avg service is now running"
  Console.WriteLine(" on port " + this.port);
  Console.WriteLine("Hit <enter> to stop service\n");
  while (true) {
    try {
      TcpClient tcpClient = await listener.AcceptTcpClientAsync();
      Task t = Process(tcpClient);
      await t;
    }
    catch (Exception ex) {
      Console.WriteLine(ex.Message);
    }
  }
}

Essa abordagem restringe o servidor a escutar solicitações usando apenas o primeiro endereço IPv4 atribuído do servidor. Uma alternativa mais simples poderia permitir que o servidor aceitasse solicitações enviadas a qualquer um de seus endereços, bastando atribuir o campo do membro como this.ipAddress = IPAddress.Any.

Observe que a assinatura do método Run do serviço usa o modificador assíncrono, indicando que, no corpo do método, alguns métodos assíncronos serão chamados juntamente com a palavra-chave await. O método retorna void em vez do habitual Task, pois Run é chamado pelo método Main que, como um caso especial, não permite o modificador assíncrono. Uma alternativa é definir o método Run para retornar o tipo Task e, em seguida, chamar o método service.Run().Wait.

O método Run do serviço instancia um objeto TcpListener usando o número da porta e o endereço IP do servidor. O método Start do ouvinte começa monitorando a porta especificada, aguardando por uma solicitação de conexão.

Dentro do principal processamento do loop while, um objeto TcpClient, no qual você pode pensar como um soquete inteligente, é criado e espera uma conexão pelo método AcceptTcpClientAsync. Antes do .NET Framework 4.5, você tinha que usar o BeginAcceptTcpClient e, em seguida, escrever o código de coordenação assíncrona personalizado que, acredite, não é simples. O .NET Framework 4.5 adiciona muitos métodos novos que, por convenção, terminam com "Async". Esses métodos novos, combinados com as palavras-chaves async e await, tornam a programação assíncrona muito, muito mais fácil.

O método Run chama o método Process usando duas instruções. Uma alternativa é usar a sintaxe de atalho e chamar o método Process em uma única instrução: await Process(tcpClient).

Para resumir, o serviço usa os objetos TcpListener e TcpClient para ocultar a complexidade da programação de soquete bruto, além de usar o novo método AcceptTcpClientAsync em conjunto com as novas palavras-chaves async e await para ocultar a complexidade da programação assíncrona. O método Run configura e coordena atividades de conexão, além de chamar o método Process para processar solicitações e, em seguida, uma segunda instrução para await no Task de retorno.

Os métodos Process e Response do serviço

Os métodos Process e Response do objeto de serviço são apresentados na Figura 4. A assinatura do método Process usa o modificador assíncrono e retorna o tipo Task.

Figura 4 Os métodos Process e Response do serviço de demonstração

private async Task Process(TcpClient tcpClient)
{
  string clientEndPoint =
    tcpClient.Client.RemoteEndPoint.ToString();
  Console.WriteLine("Received connection request from "
    + clientEndPoint);
  try {
    NetworkStream networkStream = tcpClient.GetStream();
    StreamReader reader = new StreamReader(networkStream);
    StreamWriter writer = new StreamWriter(networkStream);
    writer.AutoFlush = true;
    while (true) {
      string request = await reader.ReadLineAsync();
      if (request != null) {
        Console.WriteLine("Received service request: " + request);
        string response = Response(request);
        Console.WriteLine("Computed response is: " + response + "\n");
        await writer.WriteLineAsync(response);
      }
      else
        break; // Client closed connection
    }
    tcpClient.Close();
  }
  catch (Exception ex) {
    Console.WriteLine(ex.Message);
    if (tcpClient.Connected)
      tcpClient.Close();
  }
}
private static string Response(string request)
{
  string[] pairs = request.Split('&');
  string methodName = pairs[0].Split('=')[1];
  string valueString = pairs[1].Split('=')[1];
  string[] values = valueString.Split(' ');
  double[] vals = new double[values.Length];
  for (int i = 0; i < values.Length; ++i)
    vals[i] = double.Parse(values[i]);
  string response = "";
  if (methodName == "average") response += Average(vals);
  else if (methodName == "minimum") response += Minimum(vals);
  else response += "BAD methodName: " + methodName;
  int delay = ((int)vals[0]) * 1000; // Dummy delay
  System.Threading.Thread.Sleep(delay);
  return response;
}

Uma das vantagens de usar soquetes de nível baixo em vez do Windows Communication Foundation (WCF) é que você pode inserir facilmente instruções WriteLine de diagnóstico no lugar escolhido. Na demonstração, substitui clientEndPoint pelo valor de endereço IP fictício 123.45.678.999 por motivos de segurança.

As três linhas principais no método Process são:

string request = await reader.ReadLineAsync();
...
string response = Response(request);
...
await writer.WriteLineAsync(response);

Você pode interpretar a primeira instrução como "leia uma linha da solicitação de forma assíncrona, permitindo que outras instruções sejam executadas se necessário". Assim que a cadeia de caracteres de solicitação é obtida, ela é passada para o auxiliador Response. Em seguida, a resposta é enviada de volta ao cliente solicitante de forma assíncrona.

O servidor está usando um ciclo de solicitação por leitura e resposta por escrito. É simples, mas há vários poréns dos quais você deve estar ciente. Se o servidor lê sem gravar, ele não poderá detectar uma situação entreaberta. Se o servidor grava sem ler (por exemplo, respondendo com uma grande quantidade dados), ele poderia criar um deadlock com o cliente. Um design de leitura/gravação é aceitável para serviços internos simples, mas não deve ser usado para serviços que são essenciais ou dirigidos ao público.

O método Response aceita a cadeia de caracteres de solicitação, analisa a solicitação e calcula a cadeia de caracteres de resposta. Um ponto forte e um ponto fraco simultâneos de um serviço baseado em soquete é que você deve trabalhar em algum tipo de protocolo personalizado. Nesse caso, é pressuposto que as solicitações tenham esta forma:

method=average&data=1.1 2.2 3.3&eor

Em outras palavras, o serviço espera o literal “method=” seguido pela cadeia de caracteres “average” ou “minimum”, depois um caractere de "e" comercial (“&”) seguido pelo literal “data=”. Os dados reais de entrada devem estar na forma delimitada por espaço. A solicitação é encerrada por um “&” seguido pelo literal “eor”, que significa fim da solicitação. A desvantagem dos serviços baseados em soquete comparada com o WCF é que serializar tipos complexos de parâmetro, às vezes, pode ser um pouco trabalhoso.

Nesta demonstração de exemplo, a resposta do serviço é simples, apenas uma representação da cadeia de caracteres da média ou do mínimo de uma matriz de valores numéricos. Em muitas situações personalizadas de cliente/servidor, você terá que projetar algum protocolo para a resposta do serviço. Por exemplo, em vez enviar uma resposta apenas como "4.00", talvez seja conveniente enviar a resposta como "média=4.00".

O método Process usa uma abordagem relativamente bruta para fechar uma conexão se um ocorrer um Exception. Uma alternativa é usar o C# usando instrução (que fechará automaticamente qualquer conexão) e remover a chamada explícita do método Close.

Os métodos auxiliares Average e Minimum são definidos como:

private static double Average(double[] vals)
{
  double sum = 0.0;
  for (int i = 0; i < vals.Length; ++i)
    sum += vals[i];
  return sum / vals.Length;
}
private static double Minimum(double[] vals)
{
  double min = vals[0]; ;
  for (int i = 0; i < vals.Length; ++i)
    if (vals[i] < min) min = vals[i];
  return min;
}

Na maioria das vezes, se você estiver usando uma estrutura de programa semelhante ao serviço de demonstração, seus métodos auxiliares, nesse ponto, se conectarão a algumas fontes de dados e buscarão alguns dados. Uma vantagem dos serviços de nível baixo é que você tem maior controle sobre sua abordagem de acesso a dados. Por exemplo, se estiver obtendo dados do SQL, você pode usar o clássico ADO.NET, o Entity Framework ou qualquer outro método de acesso a dados.

Uma desvantagem de uma abordagem de nível baixo é que você deve determinar explicitamente como tratar erros no sistema. Aqui, se o serviço de demonstração não puder analisar satisfatoriamente a cadeia de caracteres de solicitação, em vez de retornar uma resposta válida (como uma cadeia de caracteres), o serviço retornará uma mensagem de erro. Com base na minha experiência, há poucos princípios gerais nos quais confiar. Cada serviço exige tratamento de erros personalizado.

Observe que o método Response tem um atraso fictício:

int delay = ((int)vals[0]) * 1000;
System.Threading.Thread.Sleep(delay);

Esse atraso na resposta, arbitrariamente baseado no primeiro valor numérico da solicitação, foi inserido para mostrar o serviço desativado, de modo que os clientes do aplicativo Web e WinForm pudessem demonstrar a capacidade de resposta da interface do usuário enquanto aguarda uma resposta.

O cliente de demonstração do aplicativo WinForm

Para criar o cliente do WinForm mostrado na Figura 1, iniciei o Visual Studio 2012 e criei um novo aplicativo WinForm do C# denominado DemoFormClient. Observe que, por padrão, o Visual Studio modulariza um aplicativo WinForm em vários arquivos que separam o código da interface do usuário do código lógico. Para o download do código que acompanha este artigo, refatorei o código modularizado do Visual Studio em um único arquivo de código-fonte. Você pode compilar o aplicativo iniciando um shell de comando do Visual Studio (que sabe onde o compilador do C# está) e executando o comando: csc.exe /target:winexe DemoFormClient.cs.

Usando as ferramentas de design do Visual Studio, adicionei um controle ComboBox, um controle TextBox, dois controles Button e um controle ListBox, juntamente com quatro controles Label. Para o controle ComboBox, adicionei as cadeias de caracteres "average" e "minimum" à propriedade da coleção Items do controle. Alterei as propriedades Text de button1 e button2 para Send Async e Say Hello, respectivamente. Em seguida, no modo de exibição de design, cliquei duas vezes nos controles button1 e button2 para registrar seus manipuladores de eventos. Editei os manipuladores de cliques, conforme mostrado na Figura 5.

Figura 5 Manipuladores de cliques de botão do cliente de demonstração do WinForm

private async void button1_Click(object sender, EventArgs e)
{
  try {
    string server = "mymachine.network.microsoft.com";
    int port = 50000;
    string method = (string)comboBox1.SelectedItem;
    string data = textBox1.Text;
    Task<string> tsResponse = 
      SendRequest(server, port, method, data);
    listBox1.Items.Add("Sent request, waiting for response");
    await tsResponse;
    double dResponse = double.Parse(tsResponse.Result);
    listBox1.Items.Add("Received response: " +
     dResponse.ToString("F2"));
  }
  catch (Exception ex) {
    listBox1.Items.Add(ex.Message);
  }
}
private void button2_Click(object sender, EventArgs e)
{
  listBox1.Items.Add("Hello");
}

Observe que a assinatura do manipulador de cliques do controle button1 foi alterada para incluir o modificador async. O manipulador configura um nome de computador servidor inserido em código como uma cadeia de caracteres e número de porta. Ao usar serviços baseados em soquete de nível baixo, não há mecanismo de descoberta automática, de modo que os clientes devem ter acesso ao nome do servidor ou ao endereço IP e às informações de porta.

As principais linhas de código são:

Task<string> tsResponse = SendRequest(server, port, method, data);
// Perform some actions here if necessary
await tsResponse;
double dResponse = double.Parse(tsResponse.Result);

SendRequest é um método assíncrono definido por programa. A chamada pode ser interpretada imprecisamente como "envie uma solicitação assíncrona que retornará uma cadeia de caracteres e quando finalizada continue a execução na instrução 'await tsResponse', que ocorre posteriormente". Isso permite que o aplicativo execute outras ações enquanto espera pela resposta. Como a resposta é encapsulada em um Task, o resultado real da cadeia de caracteres deve ser extraído usando a propriedade Result. Esse resultado de cadeia de caracteres é convertido no tipo double, de modo que ele pode ser formatado legalmente para duas casas decimais.

Uma abordagem de chamada alternativa é:

string sResponse = await SendRequest(server, port, method, data);
double dResponse = double.Parse(sResponse);
listBox1.Items.Add("Received response: " + dResponse.ToString("F2"));

Aqui, a palavra-chave await é embutida na chamada assíncrona para SendRequest. Isso simplifica um pouco o código de chamada e também permite que a cadeia de caracteres de retorno seja buscada sem uma chamada a Task.Result. A opção de usar uma chamada await embutida ou usar uma chamada await de instrução separada variará de acordo com a situação, porém, de acordo com a regra prática, é melhor evitar o uso explícito da propriedade Result de um objeto Task.

Grande parte do trabalho assíncrono é executada no método Send­Request, que é listado na Figura 6. Como SendRequest é assíncrono, seria melhor ser denominado SendRequestAsync ou MySendRequestAsync.

Figura 6 Método SendRequest do cliente de demonstração do WinForm

private static async Task<string> SendRequest(string server,
  int port, string method, string data)
{
  try {
    IPAddress ipAddress = null;
    IPHostEntry ipHostInfo = Dns.GetHostEntry(server);
    for (int i = 0; i < ipHostInfo.AddressList.Length; ++i) {
      if (ipHostInfo.AddressList[i].AddressFamily ==
        AddressFamily.InterNetwork)
      {
        ipAddress = ipHostInfo.AddressList[i];
        break;
      }
    }
    if (ipAddress == null)
      throw new Exception("No IPv4 address for server");
    TcpClient client = new TcpClient();
    await client.ConnectAsync(ipAddress, port); // Connect
    NetworkStream networkStream = client.GetStream();
    StreamWriter writer = new StreamWriter(networkStream);
    StreamReader reader = new StreamReader(networkStream);
    writer.AutoFlush = true;
    string requestData = "method=" + method + "&" + "data=" +
      data + "&eor"; // 'End-of-request'
    await writer.WriteLineAsync(requestData);
    string response = await reader.ReadLineAsync();
    client.Close();
    return response;
  }
  catch (Exception ex) {
    return ex.Message;
  }
}

SendRequest aceita uma cadeia de caracteres que representa o nome do servidor e começa resolvendo esse nome para um endereço IP usando a mesma lógica de código que foi usada no construtor de classe de serviço. Uma alternativa mais simples é apenas passar o nome do servidor: await client.ConnectAsync(server, port).

Depois que o endereço IP do servidor é determinado, um objeto de soque inteligente TcpClient é instanciado e o método Connect­Async do objeto é usado para enviar uma solicitação de conexão ao servidor. Depois de configurar um objeto StreamWriter de rede para enviar dados ao servidor e um objeto StreamReader para receber dados dos servidor, uma cadeia de caracteres de solicitação é criada usando a formatação esperada pelo servidor. A solicitação é enviada e recebida de modo assíncrono e retornada pelo método como uma cadeia de caracteres.

O cliente de demonstração do aplicativo Web

Criei o cliente do aplicativo Web de demonstração mostrado na Figura 1 em duas etapas. Primeiro, usei o Visual Studio para criar um site para hospedar o aplicativo e, em seguida, codifiquei o aplicativo Web usando o Bloco de Notas. Iniciei o Visual Studio 2012 e criei um novo site vazio do C# denominado DemoClient em http://localhost/. Isso configurou todos os detalhes técnicos necessários do IIS para hospedar um aplicativo e criou o local físico associado ao site em C:\inetpub\wwwroot\DemoClient\. Esse processo também criou um arquivo de configuração básica, o Web.config, que contém informações para permitir que aplicativos no site acessem a funcionalidade async no .NET Framework 4.5:

<?xml version="1.0"?>
<configuration>
  <system.web>
    <compilation debug="false" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
</configuration>

Em seguida, inicie o Bloco de Notas com privilégios administrativos. Às vezes, ao criar aplicativos ASP.NET simples, prefiro usar o Bloco de Notas em vez do Visual Studio, assim posso manter todo o código de aplicativo em um único arquivo .aspx, em vez de gerar vários arquivos e código de exemplo indesejado. Salvei o arquivo vazio como DemoWeb­Client.aspx em C:\inetpub\wwwroot\DemoClient.

A estrutura geral do aplicativo Web é mostrada na Figura 7.

Figura 7 Estrutura do cliente de demonstração do aplicativo Web

<%@ Page Language="C#" Async="true" AutoEventWireup="true"%>
<%@ Import Namespace="System.Threading.Tasks" %>
<%@ Import Namespace="System.Net" %>
<%@ Import Namespace="System.Net.Sockets" %>
<%@ Import Namespace="System.IO" %>
<script runat="server" language="C#">
  private static async Task<string> SendRequest(string server,
  private async void Button1_Click(object sender, System.EventArgs e) { . . }
</script>
<head>
  <title>Demo</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  <p>Enter service method:
    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox></p>
  <p>Enter data:
    <asp:TextBox ID="TextBox2" runat="server"></asp:TextBox></p>
  <p><asp:Button Text="Send Request" id="Button1"
    runat="server" OnClick="Button1_Click"> </asp:Button> </p>
  <p>Response:
    <asp:TextBox ID="TextBox3" runat="server"></asp:TextBox></p>
  <p>Dummy responsive control:
    <asp:TextBox ID="TextBox4" runat="server"></asp:TextBox></p>
  </div>
  </form>
</body>
</html>

No topo da página adicionei instruções Import para introduzir os namespaces .NET relevantes no escopo e uma diretiva Page que inclui o atributo Async=true.

A região de script do C# contém dois métodos, SendRequest e Button1_Click. O corpo da página do aplicativo tem dois controles TextBox e um controle Button para entrada, um controle TextBox de saída para manter a resposta do serviço e um controle TextBox não usado, fictício, para demonstrar a capacidade de resposta da interface do usuário enquanto o aplicativo aguarda o serviço responder a uma solicitação.

O código para o método SendRequest do aplicativo Web é exatamente igual ao código no Send­Request do aplicativo WinForm. O código para o manipulador Button1_Click do aplicativo Web difere apenas ligeiramente do manipulador button1_Click do WinForm para acomodar a diferente interface do usuário:

try {
  string server = "mymachine.network.microsoft.com";
  int port = 50000;
  string method = TextBox1.Text;
  string data = TextBox2.Text;
  string sResponse = await SendRequest(server, port, method, data);
  double dResponse = double.Parse(sResponse);
  TextBox3.Text = dResponse.ToString("F2");
}
catch (Exception ex) {
  TextBox3.Text = ex.Message;
}

Embora o código para o aplicativo Web seja essencialmente igual ao código para o aplicativo WinForm, o mecanismo de chamada é muito diferente. Quando um usuário faz uma solicitação usando o WinForm, o WinForm emite a chamada diretamente ao serviço e o serviço responde diretamente ao WinForm. Quando um usuário faz uma solicitação do aplicativo Web, o aplicativo Web envia as informações de solicitação ao servidor Web que está hospedando o aplicativo, o servidor Web faz a chamada ao serviço, o serviço responde ao servidor Web, o servidor Web constrói uma página de resposta que inclui a resposta e a página de resposta é enviada de volta ao navegador do cliente.

Conclusão

Resumindo, quando você deve pensar em usar soquetes TCP assíncronos em vez do WCF? Aproximadamente dez anos atrás, antes da criação do WCF e seus Serviços Web ASP.NET da tecnologia antecessora, se você quisesse criar um sistema cliente/servidor, usar soquetes geralmente era a opção mais lógica. A introdução do WCF foi um grande avanço, mas devido ao enorme número de cenários que o WCF foi designado a tratar, usá-lo para sistemas simples de cliente/servidor pode ser um exagero em algumas situações. Embora a versão mais recente do WCF seja mais fácil de configurar do que as versões anteriores, ainda pode ser difícil trabalhar com ele.

Para situações em que o cliente e o servidor estão em redes diferentes, levando a segurança como a principal preocupação, sempre uso o WCF. Mas para muitos sistemas cliente/servidor em que o cliente e o servidor estão localizados em uma única rede corporativa segura, muitas vezes prefiro usar soquetes TCP.

Uma abordagem relativamente nova para implementar sistemas cliente/servidor é usar a estrutura de API Web ASP.NET para serviços baseados em HTTP combinados com a biblioteca SignalR do ASP.NET para métodos assíncronos. Em muitos casos, essa abordagem é mais simples de implementar do que usar o WCF, além de evitar muitos dos detalhes de nível baixo envolvidos em uma abordagem de soquete.

Dr. James McCaffrey trabalha para a Microsoft Research em Redmond, Washington. Ele trabalhou em vários produtos da Microsoft, incluindo Internet Explorer e Bing. Entre em contato com ele pelo email jammc@microsoft.com.

Agradecemos aos seguintes especialistas técnicos pelos conselhos e pela revisão deste artigo: Piali Choudhury (MS Research), Stephen Cleary (consultor), Adam Eversole (MS Research) Lynn Powers (MS Research) e Stephen Toub (Microsoft)