WCF双工通讯实现聊天传文件

本文源代码下载地址:
github下载地址:https://github.com/lishuangquan1987/WCF-Duplexing-Chat
注意:本代码的服务不依赖于IIS,而是寄托于程序本身,下载后不需要经过任何配置可以直接运行使用
最近在研究双工通讯,之前有写过用socket编写的局域网聊天软件(下载地址:Socket编写的局域网聊天软件)于是想到用WCF的双工通讯来写一个聊天软件。想实现的需求是:
1.服务端开启服务,客户端连接服务端与服务端相互聊天
2.客户端与服务端相互传输文件
3.客户端能够群发(广播)文字和传输文件
一、首先定义接口契约:(服务端和客户端的接口契约一模一样)

namespace WCFService
{
  [ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IChatToClient))]
  public interface IChatToServer
    {
      [OperationContract(IsOneWay=true)]
      void SendMessageToServer(string msg);
      [OperationContract(IsOneWay = true)]
      void SendImageToServer(MyImage image);
      [OperationContract(IsOneWay = true)]
      void Login(string userName);
    }
  public interface IChatToClient
  {
      [OperationContract(IsOneWay = true)]
      void SendMessageToClient(string msg);
      [OperationContract(IsOneWay = true)]
      void SendImageToClient(MyImage image);
  }
}

二、服务端实现IChatToServer接口:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ServiceModel;
using WCFService;
using System.Collections.ObjectModel;

namespace WCF_双工_Server
{
    /// <summary>
    /// 客户端调用的方法在服务端的实现
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
   public class ChatToServer:IChatToServer
    {
        public ChatToServer(System.Windows.Threading.Dispatcher mainDispatcher)
        {
            this.mainDispatcher = mainDispatcher;
        }
        private System.Windows.Threading.Dispatcher mainDispatcher;//得到主线程调度,防止跨线程操作
        private static ObservableCollection<User> lstUser = new ObservableCollection<User>();
        public static ObservableCollection<User> LstUser
        {
            get { return ChatToServer.lstUser; }
            set { ChatToServer.lstUser = value; }
        }
        
        /// <summary>
        /// 定义委托
        /// </summary>
        public delegate void Dele_Login(string key,string userName);
        public delegate void Dele_ReceiveMsg(string key,string msg);
        public delegate void Dele_ReceiveImage(string key,MyImage image);
        public delegate void Dele_ClientClosed(object sender, EventArgs e);
        /// <summary>
        /// 定义事件,当客户端调用时,触发此事件来更新UI
        /// </summary>
        public event Dele_Login LoginEvent;
        public event Dele_ReceiveMsg ReceiveMsgEvent;
        public event Dele_ReceiveImage ReceiveImageEvent;
        public event Dele_ClientClosed ClientClosedEvent;

        
        public void SendMessageToServer(string msg)
        {
            string key = OperationContext.Current.SessionId;
            if (ReceiveMsgEvent != null)
                ReceiveMsgEvent(key,msg);
        }

        public void SendImageToServer(MyImage image)
        {
            string key = OperationContext.Current.SessionId;
            if (ReceiveImageEvent != null)
                ReceiveImageEvent(key, image);
        }

        /// <summary>
        /// 当有客户端连接时,会调用此方法注册
        /// </summary>
        /// <param name="userName"></param>
        public void Login(string userName)
        {
            IChatToClient client = OperationContext.Current.GetCallbackChannel<IChatToClient>();            
            string key = OperationContext.Current.SessionId;
            IContextChannel chanel = OperationContext.Current.Channel;
            chanel.Closed += (sender, e) => { if (ClientClosedEvent != null) ClientClosedEvent(sender, e); };
            //检查是否有离线用户
            var users = LstUser.Where(x => x.Chanel.State != CommunicationState.Opened);
            if (users != null && users.Count() > 0)
                mainDispatcher.Invoke(new Action(() => users.ToList().ForEach(x => LstUser.Remove(x))));
            //将用户加入进去
            User user = new User() { Client = client, Key = key, Chanel = chanel, UserName = userName };
            var existUser = LstUser.Where(x => x.Key == user.Key);
            if (existUser != null && existUser.Count() > 0)
                return;
            mainDispatcher.Invoke(new Action(()=> LstUser.Add(user)));
            if (this.LoginEvent != null)
            {
                this.LoginEvent(key, userName);
            }
        }
    }
}

三、客户端实现IChatToClient的回调接口:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WCFService;

namespace WCF_双工_Client
{
   public class ChatToClient:IChatToClient
    {
       /// <summary>
       /// 定义委托
       /// </summary>
       /// <param name="msg"></param>
       public delegate void Dele_ReceiveMsg(string msg);
       public delegate void Dele_ReceiveImage(MyImage image);
        /// <summary>
        /// 定义事件
        /// </summary>
       public event Dele_ReceiveMsg ReceiveMsgEvent;
       public event Dele_ReceiveImage ReceiveImageEvent;
       #region~实现IChatToClient
       public void SendMessageToClient(string msg)
        {
            if (ReceiveMsgEvent != null)
                ReceiveMsgEvent(msg);
        }

        public void SendImageToClient(MyImage image)
        {
            if (ReceiveImageEvent != null)
                ReceiveImageEvent(image);
        }
       #endregion
    }
}

四、服务端开启服务,客户端连接服务。具体我就不晒代码了。
本代码中加入了一些界面细节和优化,让人感觉人性化些,不过在写代码的过程中遇到如下坑:
1.聊天无障碍,传输文件超过某一大小时便无效,经过查询,加入了入下代码:对于ReaderQuotas,除了要引用System.Xml外,还要using System.Runtime.Serialization;

//必须加上,否则传输大文件有问题...
                bingding.ReaderQuotas.MaxDepth = int.MaxValue;
                bingding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
                bingding.ReaderQuotas.MaxArrayLength = int.MaxValue;
                bingding.ReaderQuotas.MaxBytesPerRead = int.MaxValue;
                bingding.ReaderQuotas.MaxNameTableCharCount = int.MaxValue;               

2.在客户端登录服务器的时候,在服务端引发的有时候是线程上得方法,线程上得方法直接去更改ObservableCollection lstUser = new ObservableCollection()是不行的,因为它跟主界面的显示绑定在一起,所以在服务器实现类中要得到主线程调度:

public ChatToServer(System.Windows.Threading.Dispatcher mainDispatcher)
        {
            this.mainDispatcher = mainDispatcher;
        }

本代码还有不足之处是:
1.客户端无法与客户端通讯,思路已经想好,待实现。(已经实现,点击下载)
2.当客户端异常退出时,服务器无法知道,即使加入了如下判断chanel的状态也不行:待解决.

//检查是否有离线用户
            var users = LstUser.Where(x => x.Chanel.State != CommunicationState.Opened);
            if (users != null && users.Count() > 0)
                mainDispatcher.Invoke(new Action(() => users.ToList().ForEach(x => LstUser.Remove(x))));

完成后的图片演示:
打开服务端:
打开服务端
服务端开启服务成功:
服务端开启服务成功
打开客户端输入用户名登录:
打开客户端输入用户名登录
客户端在登录:
客户端在登录
登录成功后客户端和服务端图:
这里写图片描述
客户端准备发消息:
这里写图片描述
发送消息后:
这里写图片描述
服务器对所有好友广播消息:
这里写图片描述
服务器对所有好友发送文件,客户端提示:
这里写图片描述
客户端选择接收后:
这里写图片描述
再登录一个客户端Jack,并且Jack发了一个消息:
这里写图片描述
服务器广播消息:
这里写图片描述
服务器对Jack私聊:
这里写图片描述
Jack下线:
这里写图片描述

本文源代码下载地址:
github下载地址:https://github.com/lishuangquan1987/WCF-Duplexing-Chat

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值