Skip Navigation Links



Translate this page now :



»Programação
»Programação.NET
»Banco de Dados
»Webdesign
»Office
» Certificações Microsoft 4
»Treinamentos4
»Programação 4
»Webdesign«
»Office & User Tips«
»Grupos de Usuários
»Células Acadêmicas«

Xadrez
Você já está cadastrado e participa do grupo de usuários de sua cidade ? Se não, comente o porque.
 
 
Faça um pequeno teste com 10 questões de VB
.:.
Teste seus conhecimentos em Visual Basic, SQL Server e ASP 3.0 com nossas provas on-line
.:.
Aprimore seus conhecimentos em programação com nosso treinamento on-line de lógica de programação
.:.
Veja nosso calendário de treinamentos
Gostou da Página?
Então

para um amigo!

Pesquisa personalizada
Pesquisar Dicas:

 






Criando uma aplicação de Chat com WCF


Objetivo

Vamos fazer um passo-a-passo da criação de uma aplicação de chat utilizando WCF para que, nesta aplicação possamos explorar duas funcionalidades do WCF :

- O binding wsDualHttpBinding, que permite a realização de chamadas de callBack do serviço para o client.

- A criação de um serviço singleton

Como aplicação de chat em si o projeto será bem simples, tendo como objetivo principal demonstrar as tecnologias acima.


Arquitetura

Uma aplicação de chat precisa de um ponto central, um servidor, onde todos os clients possam se logar e compartilhar as mensagens de chat. Por causa disso nosso serviço precisará ser singleTon.

Os usuários (clients) precisarão manter-se logados no servidor, portanto o serviço precisará requerer o uso de sessão.

O servidor de chat, no caso nosso serviço, precisará alertar os clients quando uma nova mensagem for inserida. Para isso o serviço terá quer fazer o que é chamado de call back, o serviço precisará fazer chamadas para o client, chamadas independentes das que o client fará para o serviço.



Como funciona o CallBack

Para que o CallBack funcione, o client precisará expor um serviço que possa ser chamado a partir do servidor. Trata-se de uma chamada independente e não de uma resposta para a chamada do client.

Assim sendo, será necessário ter uma interface para esse serviço do client (chamaremos de IChatCallback) e precisará haver uma classe no client implementando essa interface.

Felizmente o trabalho de colocar o serviço no ar é quase automático, uma vez tendo a interface e a classe o .NET faz o resto para nós.



Passo-a-Passo

1) Crie um projeto class library chamado libServicoChat

2) Use o Add Reference para adicionar uma referência para os assemblies System.ServiceModel e System.Runtime.Serialization

System.ServiceModel

3) Crie uma interface chamada IChat com o código abaixo :

    public interface IChat

    {

        void EnviarMensagem(string mensagem);

 

        void Entrar(string usuario);

    }

4) No Arquivo IChat.cs adicione a seguinte instrução using :

using System.ServiceModel;

5) Aplique o atributo isOneWay nos métodos da interface IChat, ficando como abaixo :

    public interface IChat

    {

        [OperationContract(IsOneWay=true)]

        void EnviarMensagem(string mensagem);

 

        [OperationContract(IsOneWay = true)]

        void Entrar(string usuario);

    }

O processo de callBack envolve trabalho multi-thread inclusive no client, o que pode gerar problemas de sincronia e deadlock. A forma mais simples de contornar os problemas do trabalho multi-thread é com a aplicação do atributo isOneWay, que também impede que os métodos chamados possuam parâmetro de retorno.

Para que o callBack funcione com métodos que tenham retorno, sem o isOneWay, torna-se necessário tratar mais cuidadosamente o trabalho multi-thread para que não ocorra um deadlock. Veremos isso posteriormente.

6) Crie uma classe Mensagem conforme o código abaixo :

    [DataContract]

    public class Mensagem

    {

        [DataMember]

        public string EnviadaPor { get; set; }

        [DataMember]

        public string Texto { get; set; }

 

        public string SessionId { get; set; }

    }

Todas as mensagens do chat precisarão ser guardadas o serviço, então para cada mensagem criaremos uma instância da classe mensagem.

Repare que o SessionId não está marcado com DataMember, pois não será enviado para o client, isso ficará guardado apenas no servidor.

Importante : Precisará adicionar um using para System.Runtime.Serialization

7) Crie uma interface chamada iChatCallBack com a seguinte definição :

    [ServiceContract]

    public interface IChatCallBack

    {

        [OperationContract(IsOneWay=true)]

        void ReceberMensagem(Mensagem mensagem);

    }

O serviço irá implementar a interface iChat, mas o client precisará implementar a interface iChatCallBack e o objeto que implementar essa interface será exposto no client, na forma de um serviço, para que o call back ocorra.

Importante : Precisará fazer um using para System.ServiceModel.

8) Na interface IChat aplique os atributos CallBackContract e SessionMode, como no exemplo abaixo :

    [ServiceContract(CallbackContract=typeof(IChatCallBack),SessionMode= SessionMode.Required)]

    public interface IChat

Desta forma o serviço fica interligado com a interface que será utilizada para o CallBack.

Importante : precisará adicionar um using para System.ServiceModel


9) Crie a classe ServicoChat implementando a interface IChat, conforme o código abaixo :

    public class ServicoChat : IChat

    {

 

        public void EnviarMensagem(string mensagem)

        {

            throw new NotImplementedException();

        }

 

        public void Entrar(string usuario)

        {

            throw new NotImplementedException();

        }

    }

10) Aplique o atributo InstanceContextMode conforme o código abaixo:

    [ServiceBehavior(InstanceContextMode= InstanceContextMode.Single)]

    public class ServicoChat : IChat

Estamos determinando que nosso serviço usará uma única instância dessa classe. Assim sendo todas as chamadas ao servidor utilizarão a mesma instância do serviço.

Importante : Precisará adicionar um using para System.ServiceModel

11) Crie as variáveis Chat e Channels, conforme o código abaixo :

        List<Mensagem> Chat = new List<Mensagem>();

        Dictionary<string, IChatCallBack> channels = new Dictionary<string, IChatCallBack>();

Chat : Guardará todas as mensagens trocadas na sala de chat.

Channels : Guardará os canais de comunicação de Callback (channels) de cada usuário, permitindo a notificação sobre novas mensagens

12) Codifique o método "Notificar" conforme abaixo :

        private void Notificar(Mensagem mensagem)

        {

            foreach (var x in channels)

            {

                x.Value.ReceberMensagem(mensagem);

            }

        }

Dada uma mensagem, este método irá avisar todos os channels que a mensagem chegou. O serviço está neste ponto fazendo uma chamada para o client.

13) Crie o método GuardarMensagem e adicione uma chamada assíncrona para o método Notificar, conforme abaixo :

        private void GuardarMensagem(Mensagem mensagem)

        {

            Chat.Add(mensagem);

 

            Action<Mensagem> notificar = Notificar;

            notificar.BeginInvoke(mensagem, ar => notificar.EndInvoke(ar), null);

        }

A chamada precisa ser assíncrona para não prender o client durante todo o procedimento de notificação.

Repare no uso de um truque com Action<>, permitindo a chamada assincrona de forma bem simples.

14) Codifique o método "Entrar" conforme abaixo :

        public void Entrar(string usuario)

        {

            channels.Add(usuario,

                OperationContext.Current.GetCallbackChannel<IChatCallBack>());

 

            GuardarMensagem(new Mensagem() { EnviadaPor = "CHAT", Texto =

                usuario + " entrou no chat" });

        }

O método GetCallBackChannel nos permite obter o canal de CallBack através do qual o serviço fará a comunicação de retorno com o client.

O método "GuardarMensagem" foi cuidadosamente separado para ser utilizado em mais de um local e encapsular as notificações para os usuários.

15) Codifique o método "ObterUsuario" conforme abaixo :

        private string Obterusuario(string SessionId)

        {

            string usuario =

                (from x in channels

                       where ((IServiceChannel)x.Value).SessionId == SessionId

                       select x.Key).FirstOrDefault();

 

            return usuario;

        }

O nome do usuário é informado apenas no método Entrar e guardado junto com o canal de comunicação. A partir dai precisaremos sempre identificar quem é o usuário a partir do ID de sua sessão.

Observe que apesar do canal de comunicação ser uma implementação da nossa interface personalizada (IChatCallback), ele ainda é um canal de comunicação e pode ser usado como tal, pois implementa a interface IServiceChannel.


16) Implemente o método "EnviarMensagem" conforme o exemplo abaixo :

        [FaultContract(typeof(string))]

        public void EnviarMensagem(string mensagem)

        {

            Mensagem msg;

 

            string usuario = Obterusuario(OperationContext.Current.SessionId);

 

 

            if (usuario == string.Empty)

                throw new FaultException<String>(

                   "Usuário não identificado - precisa se logar no chat primeiro");

 

            msg = new Mensagem()

            {

                EnviadaPor = usuario,

                SessionId = OperationContext.Current.SessionId,

                Texto = mensagem

            };

 

            GuardarMensagem(msg);

        }

Excessões em serviços WCF devem sempre ser disparadas na forma de FaultException<>, assim serão encapsuladas e os detalhes da excessão (o tipo usado no generics) passados ao client.

Os métodos que disparam excessão precisam sempre ser marcados com um FaultContract, do contrário as excessões chegarão ao client de forma genérica, como instâncias de FaultException e não de FaultException<>.

O serviço mantém sessão e nós usamos o SessionId para identificar o usuário. Considere que isso está ligado ao fato do nosso serviço não exigir login.

O código completo da classe ServicoChat fica da seguinte forma :

    [ServiceBehavior(InstanceContextMode= InstanceContextMode.Single)]

    public class ServicoChat : IChat

    {

        List<Mensagem> Chat = new List<Mensagem>();

        Dictionary<string, IChatCallBack> channels =

            new Dictionary<string, IChatCallBack>();

 

 

        [FaultContract(typeof(string))]

        public void EnviarMensagem(string mensagem)

        {

            Mensagem msg;

 

            string usuario = Obterusuario(OperationContext.Current.SessionId);

 

 

            if (usuario == string.Empty)

                throw new FaultException<String>(

                   "Usuário não identificado - precisa se logar no chat primeiro");

 

            msg = new Mensagem()

            {

                EnviadaPor = usuario,

                SessionId = OperationContext.Current.SessionId,

                Texto = mensagem

            };

 

            GuardarMensagem(msg);

        }

 

        public void Entrar(string usuario)

        {

            channels.Add(usuario,

                OperationContext.Current.GetCallbackChannel<IChatCallBack>());

 

            GuardarMensagem(new Mensagem() { EnviadaPor = "CHAT", Texto =

                usuario + " entrou no chat" });

        }

 

        private void GuardarMensagem(Mensagem mensagem)

        {

            Chat.Add(mensagem);

 

            Action<Mensagem> notificar = Notificar;

            notificar.BeginInvoke(mensagem, ar => notificar.EndInvoke(ar), null);

        }

 

        private void Notificar(Mensagem mensagem)

        {

            foreach (var x in channels)

            {

                x.Value.ReceberMensagem(mensagem);

            }

        }

 

        private string Obterusuario(string SessionId)

        {

            string usuario =

                (from x in channels

                       where ((IServiceChannel)x.Value).SessionId == SessionId

                       select x.Key).FirstOrDefault();

 

            return usuario;

        }

    }



Implementando o Host

Precisamos que exista um host para hospedar o serviço, mante-lo no ar. Utilizaremos um servidor web para isso, portanto precisaremos de uma aplicação web.


17) Adicione na solução uma nova Empty Web Application chamada chatHost

Adicionar Projeto


18) No projeto chatHost adicione uma referência para o projeto libServicoChat

Referência
O host precisa de uma referência para o projeto do serviço, pois irá hospeda-lo.

19) Nesta aplicação, adicione um novo WCF Service chamado srvChat

Adicionar Serviço

Adicionamos um serviço, mas na verdade não o utilizaremos. Usaremos apenas o arquivo .svc apontando para o serviço já existente no projeto libServicoChat

20) Apague a interface ISrvChat que foi adicionada ao projeto

21) Apague o arquivo de code-behind chamado srvChat.svc.cs

Solution Explorer


22) Abra o arquivo srvChat.svc, apague o atributo codeBehind e altere o atributo Service para que a tag fique igual abaixo :

<%@ ServiceHost Language="C#" Debug="true" Service="libServicoChat.ServicoChat" %>

23) Defina o projeto chatHost como startup project.

24) Defina o arquivo srvChat.svc como start page.

25) Adicione no web.config, dentro de System.ServiceModel, as seguintes tags :

        <protocolMapping>

          <remove scheme ="http"/>

          <add scheme ="http" binding ="wsDualHttpBinding" />

        </protocolMapping>


O WCF 4.0 possui o que são chamandos de endpoints padrões, definidos para o nosso serviço sem nenhuma configuração adicional. Porém o endpoint padrão usa a configuração basicHttpBinding, que não aceita sessões. As tags acima alteram o endpoint padrão para trabalhar com wsHttpBinding.

A alternativa seria configurar manualmente o endpoint para utilizar o wsHttpBinding. Nesta 2a opção, teríamos que adicionar as seguintes tags no web.config :

      <services>

        <service name="libServicoChat.ServicoChat">

          <endpoint address="" contract="libServicoChat.IChat" binding ="wsDualHttpBinding" />

        </service>

      </services>


26) Rode a aplicação.


Neste ponto você estará vendo a página de descrição do nosso serviço recém criado.


Criando a aplicação Client

27) Adicione um novo projeto na solução com o nome de winChatClient

Adicionar Projeto

28) Monte o Form1.cs conforme o desenho da tela abaixo :

formulário chat

29) Adicione uma service reference apontando para o endereço http do arquivo .SVC (se tiver dúvidas, rode a aplicação novamente e copie o endereço do browser).

Add Service Reference

30) Crie uma classe chamada RetornoChat com o código abaixo :

    public class RetornoChat : srvChat.IChatCallback

    {

        public delegate void ChegandoMensagem(srvChat.Mensagem mensagem);

        public event ChegandoMensagem ChegouMensagem;

 

        public void ReceberMensagem(srvChat.Mensagem mensagem)

        {

            ChegouMensagem(mensagem);

        }

    }

Essa classe será exposta pelo WCF para receber as chamadas de call back do serviço para o client. Além de implementar a interface IChatCallback, essa classe gera um evento para avisar o restante da aplicação que uma mensagem do servidor chegou.

31) No formulário, crie um método chamado RecebendoMensagemUI conforme o código abaixo :

        private void RecebendoMensagemUI(srvChat.Mensagem msg)

        {

            if (msg.EnviadaPor != string.Empty)

                lstChat.Items.Add(msg.EnviadaPor + " diz : " + msg.Texto);

            else

                lstChat.Items.Add(" ********** " + msg.Texto + " ***************");

        }

Esse método será interligado com o evento que criamos na classe RetornoChat e portanto será disparado sempre que o client receber uma mensagem do servidor.

Neste trecho assumimos ainda, por padrão, que se a propriedade "EnviadaPor" não estiver preenchida, então trata-se de uma mensagem de controle do chat e não mensagem de usuário.

32) No formulário, programe o form_load com o seguinte código :

        srvChat.ChatClient chat;

        private void Form1_Load(object sender, EventArgs e)

        {

            RetornoChat retorno = new RetornoChat();

            retorno.ChegouMensagem += RecebendoMensagemUI;

 

            chat = new srvChat.ChatClient(new InstanceContext(retorno));

        }

Por ser feito um call back, precisamos criar a instância client que irá receber o callback e passa-la como parâmetro para o proxy do serviço. O proxy se encarregará de colocar um serviço no ar no lado client.

Importante : Precisará adicionar um using para System.ServiceModel

33) No formulário, programe o click do botão "entrar" com o seguinte código :

        string sUsuario;

        private void cmdEntrar_Click(object sender, EventArgs e)

        {

            sUsuario = txtUsuario.Text;

            panelChat.Visible = true;

            chat.Entrar(sUsuario);

        }

34) Ainda no formulário, programe o click do botão "enviar" da seguinte forma :

        private void cmdEnviar_Click(object sender, EventArgs e)

        {

            chat.EnviarMensagem(txtMensagem.Text);

            txtMensagem.Text = string.Empty;

            txtMensagem.Focus();

        }

35) Rode a aplicação.

36) Experimente enviar uma mensagem.

Você verá sua mensagem enviada na listbox - o serviço recebeu a mensagem e fez o callback de volta para sua aplicação avisando da mensagem recebida.

37) Clique com o botão direito no projeto winChatClient e selecione "Open folder in windows explorer"

38) Dentro da pasta bin, rode o executável e use o botão entrar.
Neste momento você está com 2 instâncias da aplicação de chat rodando.
39) Envie uma mensagem.

A mensagem que você enviar será recebida por ambas as aplicações de chat em execução.

tela chat

Mais detalhes sobre o IsOneWay

Quando o client chama um método do serviço, fica aguardando uma mensagem de resposta, isso "prende" o client enquanto aguarda essa mensagem.

No caso de um callback, se o serviço disparar a mensagem de callback para o client antes de disparar a resposta final da chamada feita pelo client, encontrará o client "preso" esperando a resposta e haverá um deadlock : O client espera a resposta que o serviço só vai mandar depois que o client receber o callback.

dead lock

O IsOneWay nos métodos fica sendo a forma mais fácil de resolver isso : O client faz a chamada e se libera para receber o callBack, não fica preso. Porém a presença do IsOneWay impede que os métodos tenham valores de retorno.

Uma alternativa ao uso do IsOneWay é utilizar o atributo CallBackBehavior na classe de callBack (no nosso exemplo, retornoChat), definindo os valores de concurrencymode para multiple e usesynchronizationcontext para false. Isso fará com que o WCF utilize multiplas threads para atender as mensagens de callBack, evitando deixar o client preso.

CallbackBehavior

Optando por essa solução, teremos que nos lembrar que nossos métodos de retorno estarão rodando em threads separadas da interface e por isso estarão limitados na manipulação da interface. Para manipular a interface teríamos que utilizar this.Invoke do form, cujo objetivo é fazer com que uma ação rode na thread principal, a thread de interface.

Precisariamos de 2 métodos, RecebendoMensagem e RecebendoMensagemUI cujo código ficaria como abaixo :

        private void RecebendoMensagem(srvChat.Mensagem msg)

        {

            Action<srvChat.Mensagem> rm = RecebendoMensagemUI;

            this.Invoke(rm,msg);

        }

 

        private void RecebendoMensagemUI(srvChat.Mensagem msg)

        {

            if (msg.EnviadaPor != string.Empty)

                lstChat.Items.Add(msg.EnviadaPor + " diz : " + msg.Texto);

            else

                lstChat.Items.Add(" ********** " + msg.Texto + " ***************");

        }



O evento ChegouMensagem (da classe retornochat) seria tratado pelo método RecebendoMensagem.

Alternativamente é interessante saber que ao invés de usar o concurrencymode=multiple poderíamos nós mesmos assumir um controle maior sobre as threads : Teríamos que disparar o método "Enviar" em uma thread a parte para não travar a threa de interface. Porém esta última solução não tem muita utilidade, o concurrencymode=multiple resolve nosso problema.


 



Envie seus comentários sobre este artigo

Nome :

E-mail :

Comentários :


Avise-me quando houverem novos comentários nesta página

Veja abaixo os comentários já enviados :

Nome : sdfsdf E-Mail : sf@sdfsd.com
Credo!!!!
Nome : Marcos E-Mail : mj2rs@terra.com.br
Este artigo ficou excelente, ?tima descri??o e did?tica perfeita! Parab?ns! Muito bacana quando encontramos material de qualidade e pessoas interessadas em disseminar boas ideias.



Nome : Rui Peres E-Mail : r.peres.91@gmail.com
Bom dia amigos, Eu segui o tutorial, mas agora queria fazer com que tivesse varias salas. Como posso conseguir isso?
Nome : 1 E-Mail : 1
-1'
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : QLgiTxFARvgn E-Mail : ecp4vocyac@gmail.com
No, sorry for the confusion. I was reerrfing to the fact that I can video chat from iChat to another iChat or I can video chat from Xbox Vision Cam to another Vision cam.You see, I has playing UNO on Xbox Live with my nephew and we were video chatting when my wife sent me a video chat through iChat on my Mac that was next to me.I was not cross chatting between different devices.
Nome : -1' E-Mail : 1
1
Nome : 1 E-Mail : -1'
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1
Nome : 1 E-Mail : 1
1

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Conheça mais sobre o nosso site :

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::



Quer saber mais?
Faça um curso na Búfalo Informática, Treinamento e Consultoria e
Prepare-se para o Mercado!
Veja o que a Búfalo tem para você.

� Búfalo Informática, Treinamento e Consultoria - Rua Álvaro Alvim, 37 Sala 920 - Cinelândia - Rio de Janeiro / RJ
Tel.: (21)2262-1368 (21) 9240-5134 (21) 9240-7281 e-Mail:
contato@bufaloinfo.com.br