学习了一段时间的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为用户名登录服务,如下图所示,登录成功。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/garfilone/logon.jpg)
客户端显示来自服务的广播,说kwan已经成功注册
![](https://p-blog.csdn.net/images/p_blog_csdn_net/garfilone/logoned.jpg)
打个招呼(没有其他人,和服务器说话吧)。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/garfilone/say1.jpg)
下面在第二个客户端上用joshua登录
![](https://p-blog.csdn.net/images/p_blog_csdn_net/garfilone/logoned2.jpg)
然后开始公共聊天。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/garfilone/say3.jpg)
三个看看。
![](https://p-blog.csdn.net/images/p_blog_csdn_net/garfilone/say4.jpg)
看来我们成功了。
该范例仅仅展示了WCF的基本应用,若要将该服务扩展成为实用服务,则需要在服务端和客户端编写更多的代码来实现复杂的逻辑。