Você está na página 1de 9

15/02/2015

WCFComunicaoP2P

DesenvolvimentoWCF

WCFComunicaoP2P
QuandoouvimosdizersobreprogramasP2P,semprenosvemamenteaquelessoftwaresquecompartilham
diversoscontedos(ilegalouno,noocasoaserdiscutidoaqui),equegeralmenteestoemformatode
msicas,vdeos,livros,etc.Aideiacadapontopublicaroquedesejadisponibilizar,paraqueoutrospontos
queestodentrodamesmarede,possambaixaroquelhesinteressam.
porIsraelAce

Like

QuandoouvimosdizersobreprogramasP2P,semprenosvemamenteaquelessoftwaresque
compartilhamdiversoscontedos(ilegalouno,noocasoaserdiscutidoaqui),equegeralmente
estoemformatodemsicas,vdeos,livros,etc.Aideiacadapontopublicaroquedeseja
disponibilizar,paraqueoutrospontosqueestodentrodamesmarede,possambaixaroquelhes
interessam.
Maspublicarcontedonoanicafinalidadedestetipodecomunicao.Podemosusufruirdeste
tipoderedeparacolaborao,quealgocadavezmaisfrequentenasempresas,ealmdisso,a
possibilidadededistribuirprocessamentoentreosvriospontos,paraquetodosconsigamcooperarna
execuodeumatarefamaior.
CadaumdospontosquefazempartedeumaredeP2P,soconhecidoscomon(node)oupeer,ea
redecomomesh(meshnetwork).MasumaredeP2Ppodeserdefinidadeduasformas,ondea
primeiraconsideradapura,ouseja,noexistirumservidorcentralizadorenvolvido,ecadaponto
atuarcomoclienteeservidor.Jaoutraforma,conhecidacomohbrida,teremosumservidor
fazendopartedarede,masafinalidadedeleapenasderastrearoudegerenciaracomunicao
entreospontos,masnoresponsvelporarmazenarqualquertipoinformao.
ParaimplementarmosumaredeP2P,aMicrosoftdisponibilizoudentrodo.NET
FrameworkumnamespacechamadoSystem.Net.PeerToPeer,quecontmtodosostiposnecessrios
paraaconstruodeumarededestetipo.MascomooWCFopilardecomunicaodentrodo.NET
Framework,aMicrosoftresolveupermitiraexposiodeserviosatravsdomodeloP2P,podendo
reutilizaroconhecimentoquetemosemserviostradicionaisparaexportarefas/dadosatravsdeste
modelo.Issoevitarconhecermosdetalhesdemaisbaixonvel,quealgoqueteramosquefazerse
fossemosutilizarasclassescontidasnonamespaceSystem.Net.PeerToPeer.
ApesardeutilizaroWCFcomomeiodecriaodeaplicaesqueestaroconectadasviaP2P,temos
algumasmudanasconsiderveisemrelaoaocontratoeaoconsumodestesservios,justamente
pelanaturezadele.Paraexplicarmelhor,vamosexplorarocontrato,queoprimeiropassoaser
realizadoparaaconstruodoservio.Abaixotemosainterfacequepossuiummtodo
chamadoEnviarMensagemquepossuiduasstringscomoparmetro,querepresentamoremetenteea
mensagem,respectivamente.Oprincipaldetalheasernotaraqui,apropriedadeCallbackContractdo
atributoServiceContractAttribute.Geralmenteutilizamosumainterfacedecallbackseparada,mas
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

1/9

15/02/2015

WCFComunicaoP2P

comoasmensagensenviadaserecebidasteroomesmoformato,ocontratodecallbackdeversero
mesmoqueocontratoquedescreveoservio.Issoreferidocomocontratosimtrico.
[ServiceContract(CallbackContract=typeof(IContrato))]
publicinterfaceIContrato
{
[OperationContract(IsOneWay=true)]
voidEnviarMensagem(stringde,stringmensagem)
}
QuandodesenvolvemosumservioWCF"tradicional",oprximopassoacriaodaaplicaoque
servircomohospedeiradaclassequerepresentaroservio,equedeverutilizara
classeServiceHost(ouumadesuasderivadas)paragerenciaraexecuodoservio.Mascomo
falamosacima,umadasalternativasderedesP2Pnopossuirumservidor,ouseja,acriaode
umaaplicaoparahospedaroservio,nestecaso,noexistir.
Comessagrandediferena,tudooqueprecisaremosfazeraconfiguraodoladodocliente.E
comosabemos,todoservioWCFdevepossuirtrscaractersticasessenciais,conhecidas
comoAddress,BindingeContract.AquiobindingseroNetPeerTcpBinding,quefoicriadojustamente
paraabstrairtodaacomunicaofeitasobreoprotocoloP2P,equeinternamente,recorreraostipos
fornecidospelonamespaceSystem.Net.PeerToPeer,conformefalamosacima.
Seguindoemfrente,devemosdefiniroendereodoservio,quenaverdadeseronomedarede
(mesh)quetodososclientesfaroparte.MantendoamesmapadronizaoimpostapeloWCF,aquio
endereodeverserprefixadocomnet.p2p,anasequnciaonomedoservio.Umpoucomais
abaixo,temosaconfiguraodobinding,definindoaportaemqueasmensagensseroprocessadas.
Definindoaporta0(zero),farcomqueoWCFescolhaumaportaquenoestejasendoutilizadano
momento.
Porfim,enomenosimportante,temosoresolver.Comooprprionomediz,oresolvero
responsvelpormantereresolverosIDsdaquelesqueestoconectadosnarede.Quandoocanal
aberto,oWCFutilizaoresolverpararesolveroIDdaquelesqueestoconectadosrede,eassimcriar
umaredecompontosinterconectados.Oatributomodedefinequalmodeloderesolverserutilizado,
evocpodeescolherumaemtrsopes,queestodefinidasatravsdo
enumeradorPeerResolverMode,queestolistadosabaixo:
Pnrp:UtilizaopadroPNRP(PeerNameResolutionProtocol),queumprotocolo
desenvolvidopelaMicrosoftquehabilitaapublicaoeresoluodinmicadenomes.
Custom:Comestaopo,vocpodecriarumservioparaaresoluodenomeseIDs
daquelesqueestoenvolvidosnarede.Umexemplomaisdetalhadosermostradoabaixo.
Auto:QuandoconfiguradocomaopoAutoehouverumresolvercustomizadodefinido,ele
serutilizadocasocontrrio,tentarfazerusodoprotocoloPnrp.
AbaixotemostodaaconfiguraodoarquivoApp.configdaaplicao.Notequenesteprimeiro
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

2/9

15/02/2015

WCFComunicaoP2P

momento,estamosutilizandooresolverdefinidocomoPnrp:
<?xmlversion="1.0"encoding="utf8"?>
<configuration>
<system.serviceModel>
<client>
<endpointaddress="net.p2p://servicos/chat"
binding="netPeerTcpBinding"
bindingConfiguration="bindingConfig"
contract="AplicacaoDeChat.IContrato"
name="ChatEndpoint"/>
</client>
<bindings>
<netPeerTcpBinding>
<bindingname="bindingConfig"
port="0">
<securitymode="None"/>
<resolvermode="Pnrp"/>
</binding>
</netPeerTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
UtilizaroPNRPpareceseramelhoralternativa,masasvezesvocestemumambienteemqueele
nosuportado,ouporalgummotivo,vocquercontrolarcomoesseprocedimentorealizado.Para
issoqueaopoCustomfoicriada.IssonospermitirconstruirumservioWCF,elcustomizartoda
aregraderesoluodenomes/IDs.
Paraessacustomizaoserfeita,oprimeiropassocriarumservioquefaressatarefa.Felizmente
oWCFjtrazumaclassepronta,quecorrespondeaoservioderesoluo.Essaclassechamada
deCustomPeerResolverService,eimplementaumainterfacequedefineoseu
contrato:IPeerResolverContract.Apesardehavervriosmembros,essainterfacefornecealguns
principaismtodos,quesoautoexplicativos:Register,ResolveeUnregister.Casoessa
implementaonoteatenda,tudooquevocprecisafazercriarasuaprpriaimplementao,
utilizandoestamesmainterface.
ComoeleserumservioWCFpuro,precisamoscriarumaaplicaoquesirvacomohospedeirapara
ele,recorrendoaoServiceHost.Notequelogoabaixotemosoarquivodeconfigurao
correspondente,queestexpondooservioatravsdoprotocoloTCP:
CustomPeerResolverServicecprs=newCustomPeerResolverService()
using(ServiceHosthost=newServiceHost(cprs,newUri[]{}))
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

3/9

15/02/2015

WCFComunicaoP2P

{
cprs.Open()
host.Open()
Console.WriteLine("[ServiodeResoluoAtivo]")
Console.ReadLine()
}
<?xmlversion="1.0"encoding="utf8"?>
<configuration>
<system.serviceModel>
<services>
<servicename="System.ServiceModel.PeerResolvers.CustomPeerResolverService">
<endpointaddress="net.tcp://localhost:8282/prs"
binding="netTcpBinding"
bindingConfiguration="bindingConfig"
contract="System.ServiceModel.PeerResolvers.IPeerResolverContract"/>
</service>
</services>
<bindings>
<netTcpBinding>
<bindingname="bindingConfig">
<securitymode="None"/>
</binding>
</netTcpBinding>
</bindings>
</system.serviceModel>
</configuration>
Quandoesteservioestiverativo,podemosdefiniroresolvercustomizadonasaplicaes,ecomoj
sabemos,devemosdefiniroatributomodecomoCustom,elogonointeriordoelementoresolver,
especificarmosasconfiguraesdeacessoaoservioderesoluo,taiscomooendereo,obindinge
suarespectivaconfigurao.Ocdigoabaixoilustraamesmaconfiguraoquevimosacima,mas
agorautilizandoumresolvercustomizadoaoinvsdoPNRP:
<bindings>
<netPeerTcpBinding>
<bindingname="bindingConfig"
port="0">
<securitymode="None"/>
<resolvermode="Custom">
<customaddress="net.tcp://localhost:8282/prs"
binding="netTcpBinding"
bindingConfiguration="peerBindingConfig"/>
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

4/9

15/02/2015

WCFComunicaoP2P

</resolver>
</binding>
</netPeerTcpBinding>
<netTcpBinding>
<bindingname="peerBindingConfig">
<securitymode="None"/>
</binding>
</netTcpBinding>
</bindings>
Depoisdoresolverdefinindo(noimportaqualmodeloseja),chegaomomentoquecriarmosa
aplicaoquefarusodestetipodiferenciadodeservio.Comosabemos,nohumserviorodando
quepodemosefetuarareferncia.Tudooqueprecisaremosfazeraquio"consumodireto",criando
manualmenteasclassesparaefetuaressacomunicao.
Paraoconsumo,necessrioacriaodeumcanaldecomunicao,eoresponsvelporissoa
classeChannelFactory<TChannel>.Masnestecaso,ocanaldeverserduplex(bidirecional),oque
nosobrigaautilizarumafactoryespecializadaparaestasituao,quea
classeDuplexChannelFactory<TChannel>.Oconstrutordestaclassedevereceberainstnciada
classeInstanceContext,quepossuiacessoclassedoladodoclientequeimplementa
ainterface(contrato)decallback.Almdela,aindanecessrioapontaronomedoendpointqueest
noarquivodeconfigurao,paraqueafactorypossacriaroscanaisutilizandotodasasconfiguraes
previamenteestabelecidas.
Comoestamosconstruindoumchat,estamosimplementandoocontratodecallbacknoprprio
formulrio.OmtodoEnviarMensagemocallback,queserdisparadoquandoumamensagem
chegar,enquantoomtodoEnviar_ClickotratadordoeventoClickdoboto,queserdisparado
quandoaaplicaodesejarenviarumamensagem.Abaixotemosoformulriojimplementado,com
algumaslinhasdecdigoomitidasporquestesdeespao:
publicpartialclassConversacao:Form,IContrato
{
privateIContratoChannelchannel
privatestringnomeDoUsuario
publicConversacao(stringnomeDoUsuario)
{
InitializeComponent()
this.nomeDoUsuario=nomeDoUsuario
this.channel=newDuplexChannelFactory<IContratoChannel>(
newInstanceContext(this),"ChatEndpoint").CreateChannel()
this.channel.Open()
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

5/9

15/02/2015

WCFComunicaoP2P

}
publicvoidEnviarMensagem(stringde,stringmensagem)
{
this.Mensagens.Text+=
string.Format("{0}:{2}{1}{2}{2}",de,mensagem,Environment.NewLine)
}
privatevoidEnviar_Click(objectsender,EventArgse)
{
this.channel.EnviarMensagem(this.nomeDoUsuario,this.Mensagem.Text)
}
}

DistribuindoasMensagens
Aparentementetudofuncionadaformacorreta,masimportantetercuidadocomomodoemqueas
mensagenssoenviadasparaarede.OP2Putilizabroadcast,ouseja,seumaaplicaomandaruma
mensagemparaarede,todosospontosqueestiveremconectados,receberoamesma.Naimagem
acimatemosasensaodequesomenteasduaspessoasestoconversando,masseumaoutra
instnciadestamesmaaplicaoentrarnoar,elatambmcomearareceberasmensagens.
OWCFpossuialgumasformasparacontrolaradistribuiodasmensagensentreospontosque
compemarede,ondeaprimeiraatravsdeumacontagemeasegundautilizandoumfiltro.No
primeirocaso,podemosmodificaronossocontrato,criandoaoinvsdestringsquerepresentamas
informaesdomensagem,umaclasseparaincorporartodososparmetrosnecessrios,ealm
disso,devemosincluirumnovocampoparacontabilizaronmerodeencaminhamentosquea
mensagemdeverfazer.
ComoaAPIqueestamosutilizandoabstraigrandepartedacomunicaoentreospontos,podemos
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

6/9

15/02/2015

WCFComunicaoP2P

apenasincluirnocontratodamensagemumcampodecoradocomoatributoPeerHopCountAttribute,
queserdecrementadoacadapontoqueelaforentregue,equandoatingironmero0,pormaisque
existamaindapontos,elesnoreceberoarespectivamensagem.Abaixotemosa
classeMensagemcomoscamposquecompemaestruturadonegcioeumcampochamadoHops,
queserutilizandoapenasquandoestiversendoexpostoatravsdobindingNetPeerTcpBinding.
[MessageContract]
publicclassMensagem
{
[PeerHopCount]
publicintHops
[MessageBodyMember]
publicstringDe
[MessageBodyMember]
publicstringPara
[MessageBodyMember]
publicstringTexto
}
importantedizerqueocontratoquecriamosacima(IContrato),tambmfoimudadoparareceber
estanovaclasseaoinvsdoparmetrosseparados.
Parafinalizar,podemostambmfazerousodefiltros,quepermitecriaralgumaespciederegrapara
entregarounoamensagem.NotequecoloqueinaclasseMensagemumcampochamadoParaque
nohavianaimplementaoinicial.Afinalidadedestecampoanalisarseopontoqueestrecebendo
amensagemmesmoousurioqualsedestina.Casoseja,entregaremosamensagemeiremos
pararapropagaoparaospontosseguintescasocontrrio,noentregaremoseiremosoptarpor
propagaramensagemadiante.
AcriaodeumfiltroconsisteemimplementaraclasseabstrataPeerMessagePropagationFilter,que
nosobrigaasobrescreveromtodoShouldMessagePropagate.Essemtodopossuiumparmetro
quecorrespondeamensagemqueestchegandoeumsegundoparmetrodo
tipoPeerMessageOrigination,quedeterminaseaorigemdelalocalouremota.Esseparmetrotil
paradeterminarseamensagempartiudaprpriaaplicao,equenonossocaso,deveremosentregar
paraelaprpriaparaconseguirexibirocontedonatela.
EssemtododeveretornarumadasopesdefinidasnoenumeradorPeerMessagePropagation,que
determinarseamensagemdeverserentregueapenaslocalmente,remotamente,ambosouno
devefazernada.Paraexemplificar,abaixotemosaclasseUserNameChatFilter,queanalisaro
contedodaclasseMessagequevemcomoparmetroedeterminarseeledeveounoserentregue
paraaaplicaolocal,olhandoparaaorigemdamensagemeparaoatributoParadocorpoda
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

7/9

15/02/2015

WCFComunicaoP2P

mensagem.
internalclassUserNameChatFilter:PeerMessagePropagationFilter
{
privatestringusername
publicUserNameChatFilter(stringusername)
{
this.username=username
}
publicoverridePeerMessagePropagationShouldMessagePropagate(
Messagemessage,PeerMessageOriginationorigination)
{
if(origination==PeerMessageOrigination.Local)
{
returnPeerMessagePropagation.LocalAndRemote
}
else
{
Mensagemtemp=
(Mensagem)TypedMessageConverter.Create(typeof(Mensagem),null).FromMessage(message)
stringto=temp.Para
returnto==username?PeerMessagePropagation.Local:PeerMessagePropagation.Remote
}
}
}
Depoisdaclassecriada,temosqueadicionlaexecuo,eparaissovamosrecorrerao
mtodoGetProperty<T>daclassegenricaDuplexChannelFactory<TChannel>.Essemtodogenrico
utilizadoparaextrairdachannelstack,objetosquesoespecficosaomodelodecomunicao.
Nestecaso,vamostentarrecuperaroobjetoPeerNode,quecorrespondeaopontoatual.Essaclasse
possuiumapropriedadechamadaMessagePropagationFilter,quepodereceberumainstnciade
algumaclassequeimplementeumfiltrocustomizado(PeerMessagePropagationFilter),assimcomo
fizemosacima.Ocdigoabaixoilustracomoacoplarofiltroexecuo:
this.channel.GetProperty<PeerNode>().MessagePropagationFilter=
newUserNameChatFilter(nomeDoUsuario)
Comisso,aoenviarumarequisioparaumusurioespecfico,ofiltroentraremao,avaliandose
devereceberamensagemoupropagarparaoprximopontoexistentenarede.Apesarde
conseguirmosatingiroobjetivo,talvezessemodelonosejaamelhorsadaemalgumassituaes,
inclusiveaqui.Comonamaioriadasvezesqueremosconversarcomalgumespecfico,omelhorseria
data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

8/9

15/02/2015

WCFComunicaoP2P

manterumcontatodireto,aoinvsdeinundararedeembuscadestapessoa.
Concluso:ApesardemudarumpoucoaformadecomosecriaserviosWCF,vimosnesteartigo
quetemosmaisumapossibilidadedecriaraplicaesconectadas,queagoratambmnospermite
usufruirdeumaredeP2Putilizandoummodelosecomunicaoligeiramentediferentedoqual
estamoshabituadosatrabalhar.
P2PComWCF.zip(24.53kb)

data:text/htmlcharset=utf8,%3Cspan%20id%3D%22ContentPlaceHolder1_lblNomeCanal%22%20class%3D%22titlepage%22%20style%3D%22margin

9/9