分享WCF聊天程序--WCFChat实现代码

无意中在一个国外的站点下到了一个利用WCF实现聊天的程序,作者是:Nikola Paljetak。研究了一下,自己做了测试和部分修改,感觉还不错,分享给大家。
先来看下运行效果:
开启服务:

客户端程序:


程序分为客户端和服务器端:

------------服务器端:

IChatService.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using System.Collections;

namespace WCFChatService
{
  // SessionMode.Required 允许Session会话。双工协定时的回调协定类型为IChatCallback接口
  [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatCallback))]
  public interface IChatService
  {
    [OperationContract(IsOneWay = false, IsInitiating = true, IsTerminating = false)]//----->IsOneWay = false等待服务器完成对方法处理;IsInitiating = true启动Session会话,IsTerminating = false 设置服务器发送回复后不关闭会话
    string[] Join(string name);//用户加入

    [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
    void Say(string msg);//群聊信息

    [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
    void Whisper(string to, string msg);//私聊信息

    [OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = true)]
    void Leave();//用户加入
  }
  /// <summary>
  /// 双向通信的回调接口
  /// </summary>
  interface IChatCallback
  {
    [OperationContract(IsOneWay = true)]
    void Receive(string senderName, string message);

    [OperationContract(IsOneWay = true)]
    void ReceiveWhisper(string senderName, string message);

    [OperationContract(IsOneWay = true)]
    void UserEnter(string name);

    [OperationContract(IsOneWay = true)]
    void UserLeave(string name);
  }

  /// <summary>
  /// 设定消息的类型
  /// </summary>
  public enum MessageType { Receive, UserEnter, UserLeave, ReceiveWhisper };
  /// <summary>
  /// 定义一个本例的事件消息类. 创建包含有关事件的其他有用的信息的变量,只要派生自EventArgs即可。
  /// </summary>
  public class ChatEventArgs : EventArgs
  {
    public MessageType msgType;
    public string name;
    public string message;
  }
}

ChatService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WCFChatService
{
  // InstanceContextMode.PerSession 服务器为每个客户会话创建一个新的上下文对象。ConcurrencyMode.Multiple 异步的多线程实例
  [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
  public class ChatService : IChatService
  {
    private static Object syncObj = new Object();////定义一个静态对象用于线程部份代码块的锁定,用于lock操作
    IChatCallback callback = null;

    public delegate void ChatEventHandler(object sender, ChatEventArgs e);//定义用于把处理程序赋予给事件的委托。
    public static event ChatEventHandler ChatEvent;//定义事件
    static Dictionary<string, ChatEventHandler> chatters = new Dictionary<string, ChatEventHandler>();//创建一个静态Dictionary(表示键和值)集合(字典),用于记录在线成员,Dictionary<(Of <(TKey, TValue>)>) 泛型类

    private string name;
    private ChatEventHandler myEventHandler = null;

    public string[] Join(string name)
    {
      bool userAdded = false;
      myEventHandler = new ChatEventHandler(MyEventHandler);//将MyEventHandler方法作为参数传递给委托

      lock (syncObj)//线程的同步性,同步访问多个线程的任何变量,利用lock(独占锁),确保数据访问的唯一性。
      {
        if (!chatters.ContainsKey(name) && name != "" && name != null)
        {
          this.name = name;
          chatters.Add(name, MyEventHandler);
          userAdded = true;
        }
      }

      if (userAdded)
      {
        callback = OperationContext.Current.GetCallbackChannel<IChatCallback>();//获取当前操作客户端实例的通道给IChatCallback接口的实例callback,此通道是一个定义为IChatCallback类型的泛类型,通道的类型是事先服务契约协定好的双工机制。
        ChatEventArgs e = new ChatEventArgs();//实例化事件消息类ChatEventArgs
        e.msgType = MessageType.UserEnter;
        e.name = name;
        BroadcastMessage(e);
        ChatEvent += myEventHandler;
        string[] list = new string[chatters.Count]; //以下代码返回当前进入聊天室成员的称列表
        lock (syncObj)
        {
          chatters.Keys.CopyTo(list, 0);//将字典中记录的用户信息复制到数组中返回。
        }
        return list;
      }
      else
      {
        return null;
      }
    }

    public void Say(string msg)
    {
      ChatEventArgs e = new ChatEventArgs();
      e.msgType = MessageType.Receive;
      e.name = this.name;
      e.message = msg;
      BroadcastMessage(e);
    }

    public void Whisper(string to, string msg)
    {
      ChatEventArgs e = new ChatEventArgs();
      e.msgType = MessageType.ReceiveWhisper;
      e.name = this.name;
      e.message = msg;
      try
      {
        ChatEventHandler chatterTo;//创建一个临时委托实例
        lock (syncObj)
        {
          chatterTo = chatters[to]; //查找成员字典中,找到要接收者的委托调用
        }
        chatterTo.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);//异步方式调用接收者的委托调用
      }
      catch (KeyNotFoundException)
      {
      }
    }

    public void Leave()
    {
      if (this.name == null)
        return;

      lock (syncObj)
      {
        chatters.Remove(this.name);
      }
      ChatEvent -= myEventHandler;
      ChatEventArgs e = new ChatEventArgs();
      e.msgType = MessageType.UserLeave;
      e.name = this.name;
      this.name = null;
      BroadcastMessage(e);
    }

    //回调,根据客户端动作通知对应客户端执行对应的操作
    private void MyEventHandler(object sender, ChatEventArgs e)
    {
      try
      {
        switch (e.msgType)
        {
          case MessageType.Receive:
            callback.Receive(e.name, e.message);
            break;
          case MessageType.ReceiveWhisper:
            callback.ReceiveWhisper(e.name, e.message);
            break;
          case MessageType.UserEnter:
            callback.UserEnter(e.name);
            break;
          case MessageType.UserLeave:
            callback.UserLeave(e.name);
            break;
        }
      }
      catch
      {
        Leave();
      }
    }

    private void BroadcastMessage(ChatEventArgs e)
    {

      ChatEventHandler temp = ChatEvent;

      if (temp != null)
      {
        //循环将在线的用户广播信息
        foreach (ChatEventHandler handler in temp.GetInvocationList())
        {
          //异步方式调用多路广播委托的调用列表中的ChatEventHandler
          handler.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
        }
      }
    }
    //广播中线程调用完成的回调方法功能:清除异常多路广播委托的调用列表中异常对象(空对象)
    private void EndAsync(IAsyncResult ar)
    {
      ChatEventHandler d = null;

      try
      {
        //封装异步委托上的异步操作结果
        System.Runtime.Remoting.Messaging.AsyncResult asres = (System.Runtime.Remoting.Messaging.AsyncResult)ar;
        d = ((ChatEventHandler)asres.AsyncDelegate);
        d.EndInvoke(ar);
      }
      catch
      {
        ChatEvent -= d;
      }
    }
  }
}

------------客户端:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ServiceModel;

namespace WCFChatClient
{
  public partial class ChatForm : Form, IChatServiceCallback
  {
    /// <summary>
    /// 该函数将指定的消息发送到一个或多个窗口。此函数为指定的窗口调用窗口程序,直到窗口程序处理完消息再返回。 
    /// </summary>
    /// <param name="hWnd">其窗口程序将接收消息的窗口的句柄</param>
    /// <param name="msg">指定被发送的消息</param>
    /// <param name="wParam">指定附加的消息指定信息</param>
    /// <param name="lParam">指定附加的消息指定信息</param>
    [DllImport("user32.dll")]
    private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);
    //当一个窗口标准垂直滚动条产生一个滚动事件时发送此消息给那个窗口,也发送给拥有它的控件
    private const int WM_VSCROLL = 0x115;
    private const int SB_BOTTOM = 7;
    private int lastSelectedIndex = -1;

    private ChatServiceClient proxy;
    private string userName;

    private WaitForm wfDlg = new WaitForm();
    private delegate void HandleDelegate(string[] list);
    private delegate void HandleErrorDelegate();

    public ChatForm()
    {
      InitializeComponent();
      ShowInterChatMenuItem(true);
    }

    /// <summary>
    /// 连接服务器
    /// </summary>
    private void InterChatMenuItem_Click(object sender, EventArgs e)
    {
      lbOnlineUsers.Items.Clear();
      LoginForm loginDlg = new LoginForm();
      if (loginDlg.ShowDialog() == DialogResult.OK)
      {
        userName = loginDlg.txtUserName.Text;
        loginDlg.Close();
      }

      txtChatContent.Focus();
      Application.DoEvents();
      InstanceContext site = new InstanceContext(this);//为实现服务实例的对象进行初始化
      proxy = new ChatServiceClient(site);
      IAsyncResult iar = proxy.BeginJoin(userName, new AsyncCallback(OnEndJoin), null);
      wfDlg.ShowDialog();
    }

    private void OnEndJoin(IAsyncResult iar)
    {
      try
      {
        string[] list = proxy.EndJoin(iar);
        HandleEndJoin(list);

      }
      catch (Exception e)
      {
        HandleEndJoinError();
      }

    }
    /// <summary>
    /// 错误提示
    /// </summary>
    private void HandleEndJoinError()
    {
      if (wfDlg.InvokeRequired)
        wfDlg.Invoke(new HandleErrorDelegate(HandleEndJoinError));
      else
      {
        wfDlg.ShowError("无法连接聊天室!");
        ExitChatSession();
      }
    }
    /// <summary>
    /// 登录结束后的处理
    /// </summary>
    /// <param name="list"></param>
    private void HandleEndJoin(string[] list)
    {
      if (wfDlg.InvokeRequired)
        wfDlg.Invoke(new HandleDelegate(HandleEndJoin), new object[] { list });
      else
      {
        wfDlg.Visible = false;
        ShowInterChatMenuItem(false);
        foreach (string name in list)
        {
          lbOnlineUsers.Items.Add(name);
        }
        AppendText(" 用户: " + userName + "--------登录---------" + DateTime.Now.ToString()+ Environment.NewLine);
      }
    }
    /// <summary>
    /// 退出聊天室
    /// </summary>
    private void OutInterChatMenuItem_Click(object sender, EventArgs e)
    {
      ExitChatSession();
      Application.Exit();
    }
    /// <summary>
    /// 群聊
    /// </summary>
    private void btnChat_Click(object sender, EventArgs e)
    {
      SayAndClear("", txtChatContent.Text, false);
      txtChatContent.Focus();
    }
    /// <summary>
    /// 发送消息
    /// </summary>
    private void SayAndClear(string to, string msg, bool pvt)
    {
      if (msg != "")
      {
        try
        {
          CommunicationState cs = proxy.State;
          //pvt 公聊还是私聊
          if (!pvt)
          {
            proxy.Say(msg);
          }
          else
          {
            proxy.Whisper(to, msg);
          }

          txtChatContent.Text = "";
        }
        catch
        {
          AbortProxyAndUpdateUI();
          AppendText("失去连接: " + DateTime.Now.ToString() + Environment.NewLine);
          ExitChatSession();
        }
      }
    }
    private void txtChatContent_KeyPress(object sender, KeyPressEventArgs e)
    {
      if (e.KeyChar == 13)
      {
        e.Handled = true;
        btnChat.PerformClick();
      }
    }
    /// <summary>
    /// 只有选择一个用户时,私聊按钮才可用
    /// </summary>
    private void lbOnlineUsers_SelectedIndexChanged(object sender, EventArgs e)
    {
      AdjustWhisperButton();
    }
    /// <summary>
    /// 私聊
    /// </summary>
    private void btnWhisper_Click(object sender, EventArgs e)
    {
      if (txtChatDetails.Text == "")
      {
        return;
      }
      object to = lbOnlineUsers.SelectedItem;
      if (to != null)
      {
        string receiverName = (string)to;
        AppendText("私下对" + receiverName + "说: " + txtChatContent.Text);//+ Environment.NewLine
        SayAndClear(receiverName, txtChatContent.Text, true);
        txtChatContent.Focus();
      }
    }
    /// <summary>
    /// 连接聊天室
    /// </summary>
    private void ShowInterChatMenuItem(bool show)
    {
      InterChatMenuItem.Enabled = show;
      OutInterChatMenuItem.Enabled = this.btnChat.Enabled = !show;
    }
    private void AppendText(string text)
    {
      txtChatDetails.Text += text;
      SendMessage(txtChatDetails.Handle, WM_VSCROLL, SB_BOTTOM, new IntPtr(0));
    }
    /// <summary>
    /// 退出应用程序时,释放使用资源
    /// </summary>
    private void ExitChatSession()
    {
      try
      {
        proxy.Leave();
      }
      catch { }
      finally
      {
        AbortProxyAndUpdateUI();
      }
    }
    /// <summary>
    /// 释放使用资源
    /// </summary>
    private void AbortProxyAndUpdateUI()
    {
      if (proxy != null)
      {
        proxy.Abort();
        proxy.Close();
        proxy = null;
      }
      ShowInterChatMenuItem(true);
    }
    /// <summary>
    /// 接收消息
    /// </summary>
    public void Receive(string senderName, string message)
    {
      AppendText(senderName + "说: " + message + Environment.NewLine);
    }
    /// <summary>
    /// 接收私聊消息
    /// </summary>
    public void ReceiveWhisper(string senderName, string message)
    {
      AppendText(senderName + " 私下说: " + message + Environment.NewLine);
    }
    /// <summary>
    /// 新用户登录
    /// </summary>
    public void UserEnter(string name)
    {
      AppendText("用户 " + name + " --------登录---------" + DateTime.Now.ToString() + Environment.NewLine);
      lbOnlineUsers.Items.Add(name);
    }
    /// <summary>
    /// 用户离开
    /// </summary>
    public void UserLeave(string name)
    {
      AppendText("用户 " + name + " --------离开---------" + DateTime.Now.ToString() + Environment.NewLine);
      lbOnlineUsers.Items.Remove(name);
      AdjustWhisperButton();
    }
    /// <summary>
    /// 控制私聊按钮的可用性,只有选择了用户时按钮才可用
    /// </summary>
    private void AdjustWhisperButton()
    {
      if (lbOnlineUsers.SelectedIndex == lastSelectedIndex)
      {
        lbOnlineUsers.SelectedIndex = -1;
        lastSelectedIndex = -1;
        btnWhisper.Enabled = false;
      }
      else
      {
        btnWhisper.Enabled = true;
        lastSelectedIndex = lbOnlineUsers.SelectedIndex;
      }

      txtChatContent.Focus();
    }
    /// <summary>
    /// 窗体关闭时,释放使用资源
    /// </summary>
    private void ChatForm_FormClosed(object sender, FormClosedEventArgs e)
    {
      AbortProxyAndUpdateUI();
      Application.Exit();
    }
  }
}

代码中我做了详细的讲解,相信园友们完全可以看懂。代码中的一些使用的方法还是值得大家参考学习的。这里涉及到了WCF的使用方法,需要注意的是:如果想利用工具生成代理类,需要加上下面的代码:

if (host.Description.Behaviors.Find<System.ServiceModel.Description.ServiceMetadataBehavior>() == null)
      {
        BindingElement metaElement = new TcpTransportBindingElement();
        CustomBinding metaBind = new CustomBinding(metaElement);
        host.Description.Behaviors.Add(new System.ServiceModel.Description.ServiceMetadataBehavior());
        host.AddServiceEndpoint(typeof(System.ServiceModel.Description.IMetadataExchange), metaBind, "MEX");
      }

否则在生成代理类的时候会报错如下的错误:

源码下载:
/Files/gaoweipeng/WCFChat.rar

(0)

相关推荐

  • WCF配置心得

    根据蒋金楠老师的博文所说的, WCF的终结点有三个要素组成,分别是地址(Address).绑定(Binding)和契约(Contract),简记可写成Endpoint = ABC. 地址:地址决定了服务的位置,解决了服务寻址的问题. 绑定:绑定实现了通信的所有细节,包括网络传输.消息编码,以及其他为实现某种功能对消息进行的相应处理.绑定的类型包括BasicHttpBinding.WsHttpBinding.NetTcpBinding等. 契约:契约是对服务操作的抽象,也是对消息交换模式以及消息结

  • IIS7 配置大全(ASP.NET 2.0, WCF, ASP.NET MVC,php)

    一.IIS7.0 配置 ASP.NET2.0 1.ASP.NET 2.0 部署 1)首先打开win7 的特性,路径我已标注 下面选中的是ASP.NET2.0, 如果要支持ASP.NET1.1,你的选中IIS6兼容 2.)设置安全选项 3)添加.Net经典应用程序池 4)将站点转换为Application 5)为站点添加 yourmachinename\IIS_IUSRS权限 6.)右键站点-Manage Application-Advanced Setting 设置当前站点为Classic .N

  • WinForm窗体调用WCF服务窗体卡死问题

    窗体启动会启动一个程序主线程,如果在From_Load()方法中调用服务,调用服务操作会阻塞主程序. 只需要将调用服务的操作放到其他线程中处理就可以解决这个问题. 比如: 复制代码 代码如下: Thread ServiceThread=null; public void TestForm_Load(object sender, EventArgs e) { CheckForIllegalCrossThreadCalls = false; ServiceThread = new Thread(ne

  • II7添加应用程序测试时 无法验证对路径(c:\test\WcfService)的访问

    在II7种部署WCF服务,在"应用程序"页中点击右侧的"添加应用程序"项,添加完内容后,点击"测试设置"时报出如下异常: 解决办法: 关闭该出错的测试连接,回到上一个界面.点击"连接为..."在弹出的对话框中选择"特定用户"选择设置,输入用户名 密码.可以输入管理员账号.确定即可. 再次点击"测试设置"

  • 区分WCF与WebService的异同、优势

    首先对WCF与WebService的定义进行概括,接着介绍了WCF的优势,最后就是对WCF与WebService两者的根本区别进行比较,具体内容如下 一.定义 1.WebService:严格来说是行业标准,不是技术,使用XML扩展标记语言来表示数据(这个是夸语言和平台的关键).微软的Web服务实现称为ASP.NET Web Service.它使用Soap简单对象访问协议来实现分布式环境里应用程序之间的数据交互.WSDL来实现服务接口相关的描述.此外Web services 可以注册到UDDI中心

  • C# yield在WCF中的错误用法(一)

    在定义API的时候,对于一些返回集合对象的方法,很多人喜欢将返回类型定义成IEnumerable<T>,这本没有什么问题.这里要说的是另一个问题:对于返回类型为IEnumerable<T>的方法来说,我们可以使用yield return的方式来输出返回集合的元素.但是如果我们不了解yield 关键字背后的实现机制,很有可能造成很大的问题. 这是一个WCF相关的问题,我想99%的人都有可能会犯这样的错误--即使你对yield了解得非常透彻.闲话少说,我们通过一个简单的实例来说明这个问

  • 让IIS8支持WCF的更简单方法

    以前在IIS8中使用WCF时,总是参考在IIS8服务器添加WCF服务支持的方法进行手工设置: 复制代码 代码如下: 1. 首先添加MIME类型:扩展名".svc",MIME类型 "application/octet-stream": 2. 然后在"Handler Mappings"中添加Managed Handler:Request path: *.svcType: System.ServiceModel.Activation.HttpHandl

  • 关于.NET/C#/WCF/WPF 打造IP网络智能视频监控系统的介绍

    OptimalVision网络视频监控系统 OptimalVision(OV)网络视频监控系统(Video Surveillance System),是一套基于.NET.C#.WCF.WPF等技术构建的IP网络视频监控系统.设计与实现该系统的初衷是希望在家用电脑中部署该系统,连接本地或局域网设备,通过浏览器或手机客户端浏览宝宝实时视频,也就是俗称的"宝宝在线"或"家庭看护". 但由于业余时间总是有限,完成系统中的服务.配置.采集.传输和桌面GUI部分后,继续完成后续

  • C# yield在WCF中的错误使用(二)

    昨天写了<yield在WCF中的错误使用--99%的开发人员都有可能犯的错误[上篇]>,引起了一些讨论.关于yield关键字这个语法糖背后的原理(C#编译器将它翻译成什么)其实挺简单,虽然有时候因为误用它会导致一些问题,但是它本无过错.接下来,我们通过这篇短文简单地谈谈我所理解的yield. 目录 一.先看一个简单的例子 二.了解本质,只需要看看yield最终编译成什么 三.回到WCF的例子 一.先看一个简单的例子 我们现在看一个简单的例子.我们在一个Console应用中编写了如下一段简单的程

  • 在WCF数据访问中使用缓存提高Winform字段中文显示速度的方法

    本文较为详细的讲述了在WCF数据访问中使用缓存提高Winform字段中文显示速度的方法,分享给大家供大家参考之用.具体方法如下: 在我们开发基于WCF访问方式的Winform程序的时候,一般情况下需要对界面显示的字段进行中文显示的解析.如果是硬编码进行中文显示,那么除了不方便调整及代码臃肿外,性能上没有什么问题,但是不建议这样处理:一般情况下,我们把中文对照信息放到业务类里面去统一解析,但是这样会导致每次WCF访问方式请求解析中文化的操作耗费一定的响应时间.如果使用缓存存储中文字段的对照表,那么

随机推荐