一个简单的点对点聊天应用程序,使用 WCF netPeerTcpBinding






3.62/5 (27投票s)
2007年1月25日
4分钟阅读

269102

11363
一个演示 WCF 中 netPeerTcpBinding 用法的示例
引言
众所周知,微软已经推出了.NET 3.0,它拥有四个非常强大的基础。
- WCF (Windows Communication Foundation)
- WPF (Windows Presentation Foundation)
- WF (Windows Workflow Foundation)
- Windows Cardspace
关于WCF、WPF、WF和Cardspace已经有很多资料了。我将不深入探讨这些技术。在探索WCF的过程中,我遇到了一些新引入的有趣的东西。在我之前的聊天应用程序中使用Remoting时,我使用了在客户端和服务器之间分发的接口。有一个在客户端实现的抽象类。当服务器想发送消息给客户端时,服务器应该有客户端列表以及所有客户端的公共部分。接口实现是实现这一点的最佳方式。在.NET Remoting中有序列化、通道、协议等等。
在WCF中,事情变得非常简单。最重要也是最重要的一点是,您可以根据应用程序的需求拥有任意数量的终结点。如果您希望您的应用程序被.NET客户端、Java客户端使用,那么您可以选择TCP绑定和基本HTTP绑定。您需要做的是在服务器的配置文件中添加这些终结点并启动服务器。WCF负责为您提供与不同客户端通信的性能优势。WCF的基本通信协议是SOAP。但是,当您在WCF服务和WCF客户端之间建立通信时,基础结构会使用SOAP上的二进制来为您提供最佳性能。我认为您可以拥有一个WCF服务,所有东西都在一个屋檐下。目前我还在探索WCF,特别是绑定的更多内容。有一些功能是必需的,但我不知道如何从netPeerTcpBinding
和PeerResolvers.
中获得它们。
关于应用程序
我编写的简单的WCF聊天应用程序使用了netTcpPeerBinding
。使用此绑定,可以非常轻松地创建一个简单的内网聊天应用程序,该应用程序可以在内网上使用。您不必编写太多代码,甚至不需要编写
服务器端的任何特殊接口、类。一切都被封装在
- System.ServiceModel;
- System.ServiceModel.Channels;
- System.ServiceModel.PeerResolvers;
聊天服务器
一个非常简单的聊天服务器,只有四行代码。如前所述,您不需要编写任何特殊代码。因此,服务器应用程序看起来是这样的。
<CODE>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.PeerResolvers;
namespace ChatServer
{
public partial class ChatServer : Form
{
private CustomPeerResolverService cprs;
private ServiceHost host;
public ChatServer()
{
InitializeComponent();
btnStop.Enabled = false;
}
private void btnStart_Click(object sender, EventArgs e)
{
try
{
cprs = new CustomPeerResolverService();
cprs.RefreshInterval = TimeSpan.FromSeconds(5);
host = new ServiceHost(cprs);
cprs.ControlShape = true;
cprs.Open();
host.Open(TimeSpan.FromDays(1000000));
lblMessage.Text = "Server started successfully.";
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
btnStart.Enabled = false;
btnStop.Enabled = true;
}
}
private void btnStop_Click(object sender, EventArgs e)
{
try
{
cprs.Close();
host.Close();
lblMessage.Text = "Server stopped successfully.";
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
finally
{
btnStart.Enabled = true;
btnStop.Enabled = false;
}
}
}
}
代码是自解释的。您创建一个CustomPeerResolverService
对象,并将该对象作为输入参数传递给ServiceHost
类。打开peer resolver服务和host,您就完成了。没有新的类,也没有终结点。令人惊讶,对吧?但请稍等,我必须向您展示config文件。config文件在指定所需细节方面起着最重要的作用。
<CODE>
?xml version="1.0" encoding="utf-8" ?
configuration
system.serviceModel
services
service name="System.ServiceModel.PeerResolvers.
CustomPeerResolverService"
host
baseAddresses
add baseAddress="net.tcp://10.34.34.241/ChatServer"/
baseAddresses
host
endpoint address="net.tcp://10.34.34.241/ChatServer"
binding="netTcpBinding"
bindingConfiguration="TcpConfig"
contract="System.ServiceModel.PeerResolvers.
IPeerResolverContract"
endpoint
service
services
bindings
netTcpBinding
binding name="TcpConfig"
security mode="None"/security
binding
netTcpBinding
bindings
system.serviceModel
configuration
我已经删除了'<
'和'>
',因为我无法直接显示config文件。如果您知道如何显示,请告诉我。Config文件非常简单。在这种情况下,我们使用的是.NET预定义的.*服务System.ServiceModel.PeerResolvers.CustomPeerResolverService
。按所示方式为宿主解析器服务提供基地址。关于终结点,重要的是它使用了.*基础结构中.*已.*可用的System.ServiceModel.PeerResolvers.IPeerResolverContract
.*合同。如前所述,我们).*使用TCP.*终结点.*进行.*通信。因此,我们.*指定.*终结点.*与TCP.*绑定,.*并.*使用.*安全.*模式.*设置为.*none。.*就这样,.*您.*就.*完成了。.*只需.*启动.*服务器,.*您.*就.*准备.*好.*进行.*聊天.*客户端.*了。
聊天客户端
与聊天服务器.*相比,.*客户端.*变得.*稍微.*复杂.*一些。.*它.*独自.*完成.*所有.*事情。.*如.*前.*所述,.*我们需要.*一些.*服务器.*用于.*与.*客户端.*通信.*的.*公共.*部分,.*这.*可以.*通过.*接口.*来实现。
.*相同的.*概念.*在这里.*也.*适用,.*并且.*我们的.*客户端.*代码.*如下所示。
<CODE>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace ChatClient
{
// This is the service contract at client side which uses
// the same contract as call back contract.
// Using CallbackContract, server sends message to clients
[ServiceContract(CallbackContract = typeof(IChatService))]
public interface IChatService
{
// All operation contracts are one way so that client
// can fire the message and forget
// When server responds, client catches it acts accordingly
[OperationContract(IsOneWay = true)]
void Join(string memberName);
[OperationContract(IsOneWay = true)]
void Leave(string memberName);
[OperationContract(IsOneWay = true)]
void SendMessage(string memberName, string message);
}
// An interface to create a channel for communication
public interface IChatChannel : IChatService, IClientChannel
{
}
public partial class ChatClient : Form, IChatService
{
// Different delegates that are used internally to raise
// events when client joins,
// leaves or sends a message
private delegate void UserJoined(string name);
private delegate void UserSendMessage(string name, string message);
private delegate void UserLeft(string name);
// Events are made static because we want to create only once
// when client joins
private static event UserJoined NewJoin;
private static event UserSendMessage MessageSent;
private static event UserLeft RemoveUser;
private string userName;
private IChatChannel channel;
// As we need to establish duplex communication we use
// DuplexChanelFactory
private DuplexChannelFactory factory;
public ChatClient()
{
InitializeComponent();
this.AcceptButton = btnLogin;
}
public ChatClient(string userName)
{
this.userName = userName;
}
private void btnLogin_Click(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(txtUserName.Text.Trim()))
{
try
{
// Register an event
NewJoin += new UserJoined(ChatClient_NewJoin);
MessageSent += new UserSendMessage
(ChatClient_MessageSent);
RemoveUser += new UserLeft(ChatClient_RemoveUser);
channel = null;
this.userName = txtUserName.Text.Trim();
// Create InstanceContext to handle call back interface
// Pass the object of the CallbackContract implementor
InstanceContext context = new InstanceContext(
new ChatClient(txtUserName.Text.Trim()));
// We create a participant with the given end point
// The communication is managed with CHAT MESH and
// each client creates a duplex
// end point with the mesh. Mesh is nothing but the
// named collection of nodes.
factory =
new DuplexChannelFactory(context, "ChatEndPoint");
channel = factory.CreateChannel();
channel.Open();
channel.Join(this.userName);
grpMessageWindow.Enabled = true;
grpUserList.Enabled = true;
grpUserCredentials.Enabled = false;
this.AcceptButton = btnSend;
rtbMessages.AppendText
("****WEL-COME to Chat Application*****\r\n");
txtSendMessage.Select();
txtSendMessage.Focus();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
void ChatClient_RemoveUser(string name)
{
try
{
rtbMessages.AppendText("\r\n");
rtbMessages.AppendText(name + " left at " +
DateTime.Now.ToString());
lstUsers.Items.Remove(name);
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.ToString());
}
}
void ChatClient_MessageSent(string name, string message)
{
if (!lstUsers.Items.Contains(name))
{
lstUsers.Items.Add(name);
}
rtbMessages.AppendText("\r\n");
rtbMessages.AppendText(name + " says: " + message);
}
void ChatClient_NewJoin(string name)
{
rtbMessages.AppendText("\r\n");
rtbMessages.AppendText(name + " joined at:
[" + DateTime.Now.ToString() + "]");
lstUsers.Items.Add(name);
}
#region IChatService Members
public void Join(string memberName)
{
if (NewJoin != null)
{
NewJoin(memberName);
}
}
public new void Leave(string memberName)
{
if (RemoveUser != null)
{
RemoveUser(memberName);
}
}
public void SendMessage(string memberName, string message)
{
if (MessageSent != null)
{
MessageSent(memberName, message);
}
}
#endregion
private void btnSend_Click(object sender, EventArgs e)
{
channel.SendMessage(this.userName, txtSendMessage.Text.Trim());
txtSendMessage.Clear();
txtSendMessage.Select();
txtSendMessage.Focus();
}
private void ChatClient_FormClosing(object sender,
FormClosingEventArgs e)
{
try
{
if (channel != null)
{
channel.Leave(this.userName);
channel.Close();
}
if (factory != null)
{
factory.Close();
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
.*同样,.*客户端.*也.*非常.*简单。.*重要的是.*config.*文件。.*我们将.*看到.*详细.*信息。
<CODE>
?xml version="1.0" encoding="utf-8" ?
configuration
system.serviceModel
client
endpoint name="ChatEndPoint" address="net.p2p://chatMesh/ChatServer"
binding="netPeerTcpBinding"
bindingConfiguration="PeerTcpConfig"
contract="ChatClient.IChatService"endpoint
client
bindings
netPeerTcpBinding
binding name="PeerTcpConfig" port="0"
security mode="None"security
resolver mode="Custom"
custom address="net.tcp://10.34.34.241/ChatServer"
binding="netTcpBinding"
bindingConfiguration="TcpConfig"custom
resolver
binding
netPeerTcpBinding
netTcpBinding
binding name="TcpConfig"
security mode="None"security
binding
netTcpBinding
bindings
system.serviceModel
configuration
在这里,*config*文件.*中有.*名为*“ChatEndpoint
”*的.*终结点,.*它.*指向.*一个.*聊天.*网格.*并.*使用*netPeerTcpBinding
。*当我们.*配置.*绑定.*时,.*我们.*使用.*自定义.*解析器.*模式.*并.*提供.*实际.*服务器.*正在.*运行.*的.*自定义.*TCP.*地址。
将.*端口.*号.*设置为.*零.*将.*自动.*检测.*空闲.*端口.*进行.*通信。.*我们将.*安全.*模式.*设置为.*none。.*因此,.*使用.*此.*配置,.*我们可以.*启动.*客户端.*并在.*内网.*中.*发送.*消息。
限制
我.*没有.*找到.*一种.*方法.*将.*在线.*用户.*列表.*发送.*回.*给.*新.*加入.*的用户。.*我.*会.*很.*感激.*这个.*解决方案。.*如果您.*发现.*任何.*更有.*趣.*的东西,.*也.*请.*告知.*我。.*我.*也.*正在.*探索.*WCF.*阶段,.*这.*可能.*不是.*完美的.*聊天.*应用程序。
.*任何.*建议.*和.*更正.*都.*受欢迎。
参考
.*我.*参考.*了.*微软.*技术.*示例.*来.*熟悉.*WCF.*和.*不同.*绑定的.*工作.*方式。
结论
.*我们可以.*得出.*结论,.*WCF.*为.*我们.*提供.*了.*一种.*非常.*简单.*但.*非常.*强大.*的.*方式,.*可以在.*不同.*的.*系统.*之间.*建立.*通信,.*同时.*在.*相似.*的.*操作系统.*上.*保持.*性能.*优势。