学习了一段时间的WCF之后,应该开始写一些实用的工程来锻炼应用能力了。本文介绍了如何使用WCF编写最简单的公共聊天服务,当然,这只是开始,在未来,该工程将得到扩展。
在上一篇关于WCF的文章中介绍了编写Duplex通讯方式的服务,在该聊天服务中我将利用到Duplex。
首先还是新建一个空的解决方案,叫做WCFChat,按照惯常的手法添加2个C#类库项目,一个用于定义服务契约,一个用于服务契约的实现。
在服务契约项目WCFChatContract中定义了2个接口,第一个接口是要通过宿主开放的操作,第二个接口定义了客户端回调的方法集合。
然后在WCFChatLibrary中实现服务接口。
在服务接口的实现中我使用了一个泛型Dictionary来记录所有的客户端回调。在后面的客户端的代码中可以看到,这样其实是记录了所有和服务器通讯的客户端。
WCFChatHost是一个Windows工程,作为WCF服务宿主,下面仅给出配置文件。
相对于上一篇文章中的配置,这里进行了简化,去掉了NamedPipe,并且开放了Http。Http的Binding使用wsDualHttpBinding,以满足Duplex的需要。
最后的工作便是写一个客户端来测试我们的服务是否正确。新建Windows工程WCFChatClient。启动Host,为Client添加服务引用,改名为ChatService。
让主窗口直接实现客户端回调接口,即
用MessageBox提示登录成功,用ListBox来显示消息。然后为2个按钮添加事件处理代码,调用服务。
OK,代码的编写到此为止,我们已经实现了一个公共聊天服务,现在来测试一下。启动2个客户端,测试登录。
以kwan为用户名登录服务,如下图所示,登录成功。

客户端显示来自服务的广播,说kwan已经成功注册

打个招呼(没有其他人,和服务器说话吧)。

下面在第二个客户端上用joshua登录

然后开始公共聊天。

三个看看。

看来我们成功了。
该范例仅仅展示了WCF的基本应用,若要将该服务扩展成为实用服务,则需要在服务端和客户端编写更多的代码来实现复杂的逻辑。
在上一篇关于WCF的文章中介绍了编写Duplex通讯方式的服务,在该聊天服务中我将利用到Duplex。
首先还是新建一个空的解决方案,叫做WCFChat,按照惯常的手法添加2个C#类库项目,一个用于定义服务契约,一个用于服务契约的实现。
在服务契约项目WCFChatContract中定义了2个接口,第一个接口是要通过宿主开放的操作,第二个接口定义了客户端回调的方法集合。
using System.ServiceModel;
namespace Kwan.WCFChat.WCFChatContract
{
#region Server Interface
/// <summary>
/// 用于聊天服务的服务契约,使用Duplex模式
/// </summary>
[ServiceContract(CallbackContract=typeof(IChatClientCallback))]
public interface IChatServer
{
/// <summary>
/// 用户登录
/// </summary>
/// <param name="userName">用户名</param>
[OperationContract(IsOneWay=true)]
void LogonUser(String userName);
/// <summary>
/// 广播用户消息
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="message">消息</param>
[OperationContract(IsOneWay = true)]
void PublishChat(String userName, String message);
}
#endregion
#region Client Callback Interface
/// <summary>
/// 客户端回调接口
/// </summary>
public interface IChatClientCallback
{
/// <summary>
/// 用户登录的客户端回调接口
/// </summary>
/// <param name="userName">用户名</param>
[OperationContract(IsOneWay = true)]
void OnUserLogon(String userName);
/// <summary>
/// 广播用户消息的客户端回调接口
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="message">消息</param>
[OperationContract(IsOneWay = true)]
void OnChatPublish(String userName, String message);
}
#endregion
}
namespace Kwan.WCFChat.WCFChatContract
{
#region Server Interface
/// <summary>
/// 用于聊天服务的服务契约,使用Duplex模式
/// </summary>
[ServiceContract(CallbackContract=typeof(IChatClientCallback))]
public interface IChatServer
{
/// <summary>
/// 用户登录
/// </summary>
/// <param name="userName">用户名</param>
[OperationContract(IsOneWay=true)]
void LogonUser(String userName);
/// <summary>
/// 广播用户消息
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="message">消息</param>
[OperationContract(IsOneWay = true)]
void PublishChat(String userName, String message);
}
#endregion
#region Client Callback Interface
/// <summary>
/// 客户端回调接口
/// </summary>
public interface IChatClientCallback
{
/// <summary>
/// 用户登录的客户端回调接口
/// </summary>
/// <param name="userName">用户名</param>
[OperationContract(IsOneWay = true)]
void OnUserLogon(String userName);
/// <summary>
/// 广播用户消息的客户端回调接口
/// </summary>
/// <param name="userName">用户名</param>
/// <param name="message">消息</param>
[OperationContract(IsOneWay = true)]
void OnChatPublish(String userName, String message);
}
#endregion
}
然后在WCFChatLibrary中实现服务接口。
using Kwan.WCFChat.WCFChatContract;
namespace Kwan.WCFChat.WCFChatLibrary
{
/// <summary>
/// 服务接口实现
/// </summary>
/// 对每个会话使用一个服务对象实例进行处理
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class ChatServer : IChatServer
{
private const String CONFIG_SERVER_NAME = "WCF Chat Server";
private const String FORMAT_USER_REGISTERED = "{0} has registered.";
private const String ERROR_USER_ALREADY_EXISTS = "User already registered.";
/// <summary>
/// 记录所有的客户端回调
/// </summary>
static Dictionary<String, IChatClientCallback> _callbacks =
new Dictionary<String, IChatClientCallback>();
/// <summary>
/// 当前调用的客户端回调
/// </summary>
private IChatClientCallback _currentCallback;
#region IChatServer Members
public void LogonUser(String userName)
{
// 获取当前调用的客户端回调
_currentCallback =
OperationContext.Current.GetCallbackChannel<IChatClientCallback>();
if (!_callbacks.ContainsKey(userName))
{
_callbacks.Add(userName, _currentCallback);
_currentCallback.OnUserLogon(userName);
// 广播新用户登录的消息
foreach (String userKey in _callbacks.Keys)
{
// 从泛型Dictionary中查找
_currentCallback = _callbacks[userKey];
_currentCallback.OnChatPublish(CONFIG_SERVER_NAME,
String.Format(FORMAT_USER_REGISTERED, userName));
}
}
else
{
throw new Exception(ERROR_USER_ALREADY_EXISTS);
}
}
public void PublishChat(String userName, String message)
{
// 广播某用户发送的消息
foreach (String userKey in _callbacks.Keys)
{
_currentCallback = _callbacks[userKey];
_currentCallback.OnChatPublish(userName, message);
}
}
#endregion
}
}
namespace Kwan.WCFChat.WCFChatLibrary
{
/// <summary>
/// 服务接口实现
/// </summary>
/// 对每个会话使用一个服务对象实例进行处理
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class ChatServer : IChatServer
{
private const String CONFIG_SERVER_NAME = "WCF Chat Server";
private const String FORMAT_USER_REGISTERED = "{0} has registered.";
private const String ERROR_USER_ALREADY_EXISTS = "User already registered.";
/// <summary>
/// 记录所有的客户端回调
/// </summary>
static Dictionary<String, IChatClientCallback> _callbacks =
new Dictionary<String, IChatClientCallback>();
/// <summary>
/// 当前调用的客户端回调
/// </summary>
private IChatClientCallback _currentCallback;
#region IChatServer Members
public void LogonUser(String userName)
{
// 获取当前调用的客户端回调
_currentCallback =
OperationContext.Current.GetCallbackChannel<IChatClientCallback>();
if (!_callbacks.ContainsKey(userName))
{
_callbacks.Add(userName, _currentCallback);
_currentCallback.OnUserLogon(userName);
// 广播新用户登录的消息
foreach (String userKey in _callbacks.Keys)
{
// 从泛型Dictionary中查找
_currentCallback = _callbacks[userKey];
_currentCallback.OnChatPublish(CONFIG_SERVER_NAME,
String.Format(FORMAT_USER_REGISTERED, userName));
}
}
else
{
throw new Exception(ERROR_USER_ALREADY_EXISTS);
}
}
public void PublishChat(String userName, String message)
{
// 广播某用户发送的消息
foreach (String userKey in _callbacks.Keys)
{
_currentCallback = _callbacks[userKey];
_currentCallback.OnChatPublish(userName, message);
}
}
#endregion
}
}
在服务接口的实现中我使用了一个泛型Dictionary来记录所有的客户端回调。在后面的客户端的代码中可以看到,这样其实是记录了所有和服务器通讯的客户端。
WCFChatHost是一个Windows工程,作为WCF服务宿主,下面仅给出配置文件。
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service name="Kwan.WCFChat.WCFChatLibrary.ChatServer"
behaviorConfiguration="ChatBehaviorConfig">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8000/WCFChat"/>
<add baseAddress="http://localhost:8080/WCFChat"/>
</baseAddresses>
</host>
<endpoint address="tcpmex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
<endpoint address=""
binding="wsDualHttpBinding"
contract="Kwan.WCFChat.WCFChatContract.IChatServer"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ChatBehaviorConfig">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
<configuration>
<system.serviceModel>
<services>
<service name="Kwan.WCFChat.WCFChatLibrary.ChatServer"
behaviorConfiguration="ChatBehaviorConfig">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8000/WCFChat"/>
<add baseAddress="http://localhost:8080/WCFChat"/>
</baseAddresses>
</host>
<endpoint address="tcpmex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
<endpoint address=""
binding="wsDualHttpBinding"
contract="Kwan.WCFChat.WCFChatContract.IChatServer"/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="ChatBehaviorConfig">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
相对于上一篇文章中的配置,这里进行了简化,去掉了NamedPipe,并且开放了Http。Http的Binding使用wsDualHttpBinding,以满足Duplex的需要。
最后的工作便是写一个客户端来测试我们的服务是否正确。新建Windows工程WCFChatClient。启动Host,为Client添加服务引用,改名为ChatService。
让主窗口直接实现客户端回调接口,即
public partial class MainForm : Form, ChatService.IChatServerCallback
{
#region IChatServerCallback Members
public void OnUserLogon(string userName)
{
MessageBox.Show(userName + " log on the server successfully");
}
public void OnChatPublish(string userName, string message)
{
listBox1.Items.Add(String.Format("{0} say:{1}",userName,message));
}
#endregion
}
{
#region IChatServerCallback Members
public void OnUserLogon(string userName)
{
MessageBox.Show(userName + " log on the server successfully");
}
public void OnChatPublish(string userName, string message)
{
listBox1.Items.Add(String.Format("{0} say:{1}",userName,message));
}
#endregion
}
用MessageBox提示登录成功,用ListBox来显示消息。然后为2个按钮添加事件处理代码,调用服务。
public partial class MainForm : Form, ChatService.IChatServerCallback
{
ChatService.ChatServerClient _client;
private void MainForm_Load(object sender, EventArgs e)
{
InstanceContext ic = new InstanceContext(this);
_client = new WCFChatClient.ChatService.ChatServerClient(ic);
}
private void button1_Click(object sender, EventArgs e)
{
_userName = textBox2.Text;
_client.LogonUser(_userName);
}
private void button2_Click(object sender, EventArgs e)
{
_client.PublishChat(_userName, textBox1.Text);
}
}
{
ChatService.ChatServerClient _client;
private void MainForm_Load(object sender, EventArgs e)
{
InstanceContext ic = new InstanceContext(this);
_client = new WCFChatClient.ChatService.ChatServerClient(ic);
}
private void button1_Click(object sender, EventArgs e)
{
_userName = textBox2.Text;
_client.LogonUser(_userName);
}
private void button2_Click(object sender, EventArgs e)
{
_client.PublishChat(_userName, textBox1.Text);
}
}
OK,代码的编写到此为止,我们已经实现了一个公共聊天服务,现在来测试一下。启动2个客户端,测试登录。
以kwan为用户名登录服务,如下图所示,登录成功。

客户端显示来自服务的广播,说kwan已经成功注册

打个招呼(没有其他人,和服务器说话吧)。

下面在第二个客户端上用joshua登录

然后开始公共聊天。

三个看看。

看来我们成功了。
该范例仅仅展示了WCF的基本应用,若要将该服务扩展成为实用服务,则需要在服务端和客户端编写更多的代码来实现复杂的逻辑。