【WCF实践】编写最基础的公共聊天服务

本文介绍如何使用WCF实现一个基本的聊天服务。通过Duplex通讯模式,文章详细阐述了服务契约的定义、服务实现及客户端回调接口的设计,并提供了一个简单的聊天服务示例。
学习了一段时间的WCF之后,应该开始写一些实用的工程来锻炼应用能力了。本文介绍了如何使用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
}

然后在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
    }
}


在服务接口的实现中我使用了一个泛型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>


相对于上一篇文章中的配置,这里进行了简化,去掉了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
}


用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);
    }
}


OK,代码的编写到此为止,我们已经实现了一个公共聊天服务,现在来测试一下。启动2个客户端,测试登录。
以kwan为用户名登录服务,如下图所示,登录成功。



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



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



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



然后开始公共聊天。



三个看看。



看来我们成功了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值