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

学习了一段时间的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的基本应用,若要将该服务扩展成为实用服务,则需要在服务端和客户端编写更多的代码来实现复杂的逻辑。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值