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

作者:mdxy-dxy 时间:2023-01-19 17:04:59 

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

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

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

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

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

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

标签:WCF聊天程序
0
投稿

猜你喜欢

  • 详解基于spring多数据源动态调用及其事务处理

    2023-06-23 14:37:25
  • C#针对xml文件转化Dictionary的方法

    2021-09-12 12:33:48
  • Android开发登陆案例

    2022-03-19 10:13:01
  • ComposeDesktop开发桌面端多功能APK工具

    2022-02-11 16:57:04
  • Java线程池并发执行多个任务方式

    2023-08-14 16:26:03
  • Mybatis工具类JdbcTypeInterceptor运行时自动添加jdbcType属性

    2023-08-24 03:49:59
  • Spring Bean生命周期之BeanDefinition的合并过程详解

    2023-11-29 02:50:35
  • 如何使用try-with-resource机制关闭连接

    2022-04-25 01:36:54
  • SpringBoot整合XxlJob分布式任务调度平台

    2022-07-09 09:47:37
  • log4j配置失效日志中打印Debug信息问题

    2022-10-12 10:13:12
  • Android使用CountDownTimer模拟短信验证倒计时

    2022-10-24 18:00:28
  • C#之CLR内存字符串常量池(string)

    2023-04-23 00:09:12
  • java自定义切面增强方式(关于自定义注解aop)

    2022-05-30 15:16:58
  • 使用Java编写一个简单的Web的监控系统

    2023-02-18 04:00:19
  • C#导出网站功能实例代码讲解

    2023-09-15 03:15:36
  • C#将Sql数据保存到Excel文件中的方法

    2023-11-11 08:34:26
  • java并发之原子操作类和非阻塞算法

    2022-04-13 04:39:01
  • 深入了解Hadoop如何实现序列化

    2023-10-13 10:33:43
  • Java实现在Word指定位置插入分页符

    2021-06-29 03:24:44
  • java web实现简单留言板功能

    2021-10-29 17:22:31
  • asp之家 软件编程 m.aspxhome.com