c# WCF初学笔记(4)

1在服务器端,实现ChatService服务类中的方法:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using static ChatServer.MessageModule;

namespace ChatServer
{
    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的类名“ChatService”。
    /// 
    /// 设置服务器为单例,不允许创建多个ChatService对象
    /// 支持多线程操作:多个客户端可以同时访问同一台服务器
    /// 
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single
        ,ConcurrencyMode =ConcurrencyMode.Multiple)]
    public class ChatService : IChatService
    {
        /// 
        /// 保存所有在线人信息
        /// 
        List<MessageModule> messageModuleList = new List<MessageModule>();
        private static object obj = new object();//用于线程互斥对象判断
        /// 
        /// 上线
        /// 
        public void Register(MessageModule loginModule)
        {
            lock (obj) {//线程锁:多线程访问,保证同一时刻只能有一个线程操作,保证数据的安全性
            	//判断是否已处于登录状态
                if (!messageModuleList.Any(a=>a.SenderId==loginModule.SenderId))
                {
                    //设置为上线模式
                    loginModule.MessageMode = (int)MsgMode.Online;
                    //保存通道:用于服务器端与客户端进行数据的交互
                    loginModule.CallBack = OperationContext.Current.GetCallbackChannel<ICallBack>();
                    messageModuleList.Add(loginModule);
					//回调获取在线所有人信息
                    loginModule.CallBack.ToFriendList(messageModuleList);
                    //通知好友我上线了
                    BoardCast(loginModule);
                } 
            } 
        }

        /// 
        /// 下线(卸载)
        /// 
        /// 
        public void OffLine(MessageModule loginModule)
        {
            loginModule.MessageMode = (int)MsgMode.OffLine;//设置下线状态
            //获取当前登录客户端的通道对象
            ICallBack callBack= OperationContext.Current.GetCallbackChannel<ICallBack>();
            lock (this) {
                if (messageModuleList.Any(m=>m.CallBack== callBack)) {
                    MessageModule  module=messageModuleList.Where(m => m.CallBack == callBack).Single<MessageModule>();
                    messageModuleList.Remove(module);//从在线列表中移除
                    BoardCast(loginModule);//通知大家
                }
            }
        }
        /// 
        /// 广播:当某个用户登录或下线时,将通知其他好友
        /// 
        /// 
        private void BoardCast(MessageModule loginModule) {
            foreach (MessageModule module in messageModuleList) {
                if (module.SenderId!=loginModule.SenderId) {//非当前登录用户
                	//会调用底层接口实现类的实例方法
                    module.CallBack.MessageNotify(loginModule);//给其他好友发送通知
                }
            }
        }
         
        /// 
        /// 点对点
        /// 
        /// 
        public void PointToPoint(MessageModule module)
        {
           module.MessageMode=(int) MsgMode.PointToPoint;//点对点模式
           lock (obj) {
                if (messageModuleList.Any(m=>m.SenderId==module.ReceiverId)) {
                //获取收信人对象
                MessageModule receivedModule= messageModuleList.Where(m => m.SenderId == module.ReceiverId).Single<MessageModule>();
                    receivedModule.CallBack.MessageNotify(module);
                }
            }
        } 
    }
}

注意:这里调用回调接口中的方法(传参),实际就是客户端调用服务器端方法,然后通过通道把数据回传给客户端。
代码片段分析:

 //获取调用当前操作的客户端对象的通道
 OperationContext.Current.GetCallbackChannel<ICallBack>();

OperationContext.Current //获取当前线程的执行上下文对象
//泛型方法
T callBackChannel= OperationContext.Current.GetCallbackChannel(); // //获取调用当前操作的客户端对象的通道
附上微软官网链接: OperationContext执行上下文对象
[ServiceBehavior]特性:
可以在服务级别应用规则和行为,使用其属性控制行为的并发性、实例化、限流、事务、会话管理和线程等等。
这篇博客有详细介绍:ServiceBehavior特性

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,ConcurrencyMode =ConcurrencyMode.Multiple)]

2.客户端登录功能的完善:

由于登录人,在其他页面可能也会使用到,作为全局数据,保存在静态类中.
添加静态类Config,用来存储项目中常用的数据

using ChatClient.ChatService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ChatClient
{
   public static class Config
    {
    	//登录人
        public static MessageModule loginModule;
    }
}

LoginForm:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using DevExpress.XtraEditors;
using ChatClient.ChatService;

namespace ChatClient
{
    public partial class LoginForm : DevExpress.XtraEditors.XtraForm
    {
        public LoginForm()
        {
            InitializeComponent();
        }
        private void btnLogin_Click(object sender, EventArgs e)
        {
            //调用服务器端
            //ChatService.ChatServiceClient chatService = new ChatService.ChatServiceClient();
            //chatService.DoWork();
            Config.loginModule = new MessageModule() {
                SenderId = Guid.NewGuid().ToString(),//生成唯一标识
                SenderName=this.txtNickName.Text.Trim()
            };
            FriendListForm friendForm = new FriendListForm();
            friendForm.Show();
            this.Hide();
        }
    }
}

这边注意一下,我们会发现在引用了using ChatClient.ChatService这个namespace后可以使用服务器上的对象,其实这是由于在客户端引用了ChatServer服务。

3.在客户端添加回调接口的实现类ChatServiceCallBack:

using ChatClient.ChatService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChatClient
{

    /// 
    /// 处理服务器端聊天消息(回调)
    /// 
    public class ChatServiceCallBack: IChatServiceCallback//IChatServiceCallback就是服务器端的ICallBack接口
    {
        /// 
        /// 定义消息通知委托事件
        /// 
        public event Action<MessageModule> MessageNotifyCallback;
        //public event Func sum;

        /// 
        /// 定义获取好友列表委托事件
        /// 
        public event Action<MessageModule[]> ToFriendListCallback;

        /// 
        /// 消息通知(通用方法)
        /// 
        /// 
        public void MessageNotify(MessageModule module)
        {
            if (MessageNotifyCallback!=null) {
                MessageNotifyCallback(module);//调用消息通知委托事件
            }
            
        }
        /// 
        /// 获取好友列表
        /// 
        /// 
        public void ToFriendList(MessageModule[] list)
        {
            if (ToFriendListCallback!=null) {
                ToFriendListCallback(list);//调用获取好友列表的委托事件
            }
        }
    }
}

服务器端回调接口为:

/// 
    /// 回调接口:将服务器端的响应(处理)反馈给客户端
    /// 
    public interface ICallBack {//通知客户端

        /// 
        /// 消息通知
        /// 
        /// 
        [OperationContract(IsOneWay =true)]
        void MessageNotify(MessageModule module);

        [OperationContract(IsOneWay =true)]
        void ToFriendList(List<MessageModule> list);//通过通道传递给客户端
    }

但是在客户端更新引用后,找不到这个接口。会发现在客户端引用服务后名字被自动重新生成了:

 public interface IChatServiceCallback {
        
        [System.ServiceModel.OperationContractAttribute(IsOneWay=true, Action="http://tempuri.org/IChatService/MessageNotify")]
        void MessageNotify(ChatClient.ChatService.MessageModule module);
        
        [System.ServiceModel.OperationContractAttribute(IsOneWay=true, Action="http://tempuri.org/IChatService/ToFriendList")]
        void ToFriendList(ChatClient.ChatService.MessageModule[] list);
    }

4.完善好友列表FriendListForm的功能

当用户登录之后,将跳转到好友列表页面,如果有好友在线的话,添加指定的好友到页面。
如果好友下线,则从此好友页面上移除。
双击好友则弹出聊天对话框,进行对话聊天。

先创建点对点聊天ChatForm页面:
先使用splitContainer做上下富文本框的布局划分,上下分别放一个RichTextBox控件,上面的RichTextBox控件作为双方聊天显示的内容并设为只读,下面的作为用户输入的发送信息
两个按钮:发送和关闭
c# WCF初学笔记(4)_第1张图片
页面代码:

using ChatClient.ChatService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ChatClient
{
    public partial class ChatForm : Form
    {
        public ChatForm()
        {
            InitializeComponent();
        }

        MessageModule friendModule;
        public ChatForm(MessageModule module)
        {
            InitializeComponent();
            this.friendModule = module;
        }
        //定义发送信息的委托事件
        public event Action<MessageModule> sendMessage;
        //关闭聊天窗口的委托事件
        public event Action<string> ChatFormClose;

        /// 
        /// 发送消息
        /// 
        /// 
        /// 
        private void btnSend_Click(object sender, EventArgs e)
        {
            this.txtMessageList.AppendText($"{Config.loginModule.SenderName}:\r{this.txtSendMessage.Text}\r");
            this.txtMessageList.Select(this.txtMessageList.TextLength,0);
            this.txtMessageList.ScrollToCaret();//滚动条滚动

            //把信息发送到服务器
            if (sendMessage!=null) {
                //sendMessage(friendModule);
                MessageModule messageModule = new MessageModule();
                //设置发送人
                messageModule.SenderId = Config.loginModule.SenderId;
                messageModule.SenderName = Config.loginModule.SenderName;
                messageModule.ReceiverId = friendModule.SenderId;//接收人(好友编号)
                messageModule.MessageContent = this.txtSendMessage.Text;
                messageModule.MessageMode = 3;//点对点聊天
                sendMessage(messageModule);//调用委托事件--发送信息

                this.txtSendMessage.Text = "";//清空聊天窗口

            }
        }

        /// 
        /// 关闭窗体
        /// 
        /// 
        /// 
        private void btnClose_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        /// 
        /// 窗体关闭事件
        /// 
        /// 
        /// 
        private void ChatForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (ChatFormClose!=null) {
                ChatFormClose(this.Tag.ToString());
            }
        }
    }
}

好友列表页面功能FriendListForm的完善:

using ChatClient.ChatService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ChatClient
{
    public partial class FriendListForm : Form
    {
        //定义服务器访问对象
        ChatServiceClient chatServiceClient;
        //用来保存好友发送来的信息
        List<MessageModule> messageList = new List<MessageModule>();
        //保存已打开的聊天窗口
        Dictionary<string, ChatForm> dict = new Dictionary<string, ChatForm>();
        public FriendListForm()
        {
            InitializeComponent();
        }

        private void FriendListForm_Load(object sender, EventArgs e)
        {
            this.ShowIcon = false;
            this.Text += "-" + Config.loginModule.SenderName;
            //实例化回调处理类
            ChatServiceCallBack chatServiceCallBack = new ChatServiceCallBack();
            chatServiceCallBack.MessageNotifyCallback += ChatServiceCallBack_MessageNotifyCallback;//注册消息通知委托事件
            chatServiceCallBack.ToFriendListCallback += ChatServiceCallBack_ToFriendListCallback;//注册获取好友列表的委托事件
            //添加服务引用生成的ChatServiceClient对象(元数据模型对象),用于调用服务端的功能
            //实例化服务器端服务对象(服务端需要回传数据到客户端)
            chatServiceClient =
            new ChatServiceClient(new System.ServiceModel.InstanceContext(chatServiceCallBack));
            //登录
            chatServiceClient.Register(Config.loginModule);
        }

        /// 
        /// 获取好友列表的回调方法:只有用户上线时才会调用
        /// 
        /// 
        private void ChatServiceCallBack_ToFriendListCallback(MessageModule[] obj)
        {
            foreach (MessageModule module in obj)//obj:传入的是所有在线人
            {
                
                if (module.SenderId != Config.loginModule.SenderId)//非当前登录用户
                {
                    ListViewItem item = new ListViewItem();
                    item.Text = module.SenderName;
                    item.Tag = module;
                    this.lvFriends.Items.Add(item);
                }
            }
        }

        /// 
        /// 信息通知的回调方法:从服务器端回调到客户端
        /// 消息模式:1.上线、 2.下线、3.点对点、4.点对面(广播、群聊)
        /// 
        /// 
        private void ChatServiceCallBack_MessageNotifyCallback(MessageModule obj)
        {
            if (obj.MessageMode == 1)
            {//上线:将当前登录者添加到在线的好友的页面上
                ListViewItem item = new ListViewItem();
                item.Text = obj.SenderName;
                item.Tag = obj;
                this.lvFriends.Items.Add(item);
            }
            else if (obj.MessageMode == 2)
            {//下线:将下线的用户从其他好友的页面上移除
                foreach (ListViewItem item in this.lvFriends.Items) {
                    MessageModule messageModule = item.Tag as MessageModule;
                    if (messageModule != null) {
                        if (messageModule.SenderId == obj.SenderId)//校验是否是当前下线用户
                        {
                            this.lvFriends.Items.Remove(item);//从好友列表中移除
                            break;
                        }
                    }
                }
            } else if (obj.MessageMode==3) {//点对点聊天
                setMessage(obj);
            }
        }

        /// 
        /// 聊天
        /// 
        /// 
        private void setMessage(MessageModule module) {
            messageList.Add(module);//添加到消息列表
            if (dict.Keys.Contains(module.SenderId)) {//判断窗口是否已经打开
                ChatForm chatForm = dict[module.SenderId];
                chatForm.txtMessageList.AppendText($"{module.SenderName}:\r{module.MessageContent}\r");
                module.IsRead = 1;//设置为已读
            }
        }

        /// 
        /// 关闭窗体
        /// 
        /// 
        /// 
        private void FriendListForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            //调用服务器端下线
            chatServiceClient.OffLine(Config.loginModule);
            Application.Exit();
        }

       
        /// 
        /// 鼠标双击事件:点击某个好友时触发
        /// 
        /// 
        /// 
        private void lvFriends_MouseDoubleClick(object sender, MouseEventArgs e)
        {
           ListViewHitTestInfo info= this.lvFriends.HitTest(new Point(e.X,e.Y));
           if (info!=null) {
               MessageModule module=info.Item.Tag as MessageModule;//获取选中的好友
                ChatForm cf = new ChatForm(module);
                cf.Tag = module.SenderId;
                cf.sendMessage += Cf_sendMessage;//注册发送信息的委托事件
                cf.ChatFormClose += Cf_ChatFormClose;//注册关闭聊天窗口的委托事件
                cf.Show();
                dict.Add(module.SenderId,cf);//保存已打开的窗口
				//点击好友打开对话框后,追加未读取的好友发送来的信息
                List<MessageModule> friendModuleList= messageList.Where(m => m.SenderId == module.SenderId&&m.IsRead==0).ToList();
                foreach (MessageModule friendModule in friendModuleList) {
                 //WinForm上添加的控件,默认都是private,这里将第一个富文本框权限改为public,可以在其他类中直接使用
                 cf.txtMessageList.AppendText($"{friendModule.SenderName}:\r{friendModule.MessageContent}\r");
                 friendModule.IsRead = 1; // 已读     
                }
            }
        }
        /// ry>
        /// 
        private void Cf_sendMessage(MessageModule obj)
        {
            chatServiceClient.PointToPoint(obj);//发送消息到服务器
        }
    }
}

业务逻辑分析:用户的上线和下线,无非就是
客户端ChatClient调用服务器端ChatService的上线和下线功能,然后在服务器端通过管道回调客户端(发送通知给其他好友、获取当前登录人的其他好友列表)
回调到客户端后(调用的是ChatServiceCallBack的MessageNotify和ToFriendList方法),
在方法内再调用对应的委托事件,把参数回调到注册委托事件的对象的回调函数中,
然后在此对象中的回调函数中根据接收的参数再做处理

代码片段分析:

			//把信息发送到服务器
            if (sendMessage!=null) {
                //sendMessage(friendModule);
                MessageModule messageModule = new MessageModule();
                //设置发送人
                messageModule.SenderId = Config.loginModule.SenderId;
                messageModule.SenderName = Config.loginModule.SenderName;
                messageModule.ReceiverId = friendModule.SenderId;//接收人(好友编号)
                messageModule.MessageContent = this.txtSendMessage.Text;
                messageModule.MessageMode = 3;//点对点聊天
                sendMessage(messageModule);//调用委托事件--发送信息

                this.txtSendMessage.Text = "";//清空聊天窗口

            }
//获取当前选中好友发送来的未读信息,
//messageList中的每个元素封装的是发送的信息(发送人,收信人,内容等等)
//需要去辨别发送的信息列表中对象的SenderId是否等于当前选中好友的SenderId(他作为发送者,你作为接收者)
 List<MessageModule> friendModuleList= messageList.Where(m => m.SenderId == module.SenderId&&m.IsRead==0).ToList();
//实例化服务器端服务对象
 chatServiceClient = new ChatServiceClient(new System.ServiceModel.InstanceContext(chatServiceCallBack));

ChatServiceClient是在客户端添加服务引用之后自动生成的客户端服务代理对象,通过它可以访问服务器并调用指定的方法,生成的代理类为:服务名称 +Client
这里没有空参的构造器是由于IChatServer服务接口的服务契约要求有回调接口,
生成的代理类的构造器需要传递一个InstanceContext对象,
附上微软官网关于此类:InstanceContext类的构造器
这里我们使用第一个构造器,但是要求传入的对象需要实现服务,我们这里传入的是回调接口ICallBack的实现子类ChatServiceCallBack对象,注意:ICallBack没有指定的服务锲约,但是我们在IChatService服务接口的服务契约中,添加了回调契约属性类型并指定其类型为ICallback,那么对应接口以及接口子类也就实现了服务
**

这边为何不为ICallback接口独立添加[ServiceContract]服务契约?

**
单独设置服务,则回调将为单独的服务。
我们经常在服务接口上配置回调契约,设置双工通道时的回调接口类型,这样的话在客户端通过客户端服务代理对象调用服务时,需要传入回调对象,然后在服务器端可以通过通道回传数据给客户端。

OperationContext.Current.GetCallbackChannel<ICallBack>()

获取当前客户端调用服务的通道对象,然后可以在服务端通过此通道回传数据给客户端

[ServiceContract(CallbackContract =typeof(ICallBack))]
    public interface IChatService
    {
      ................................
    }

生成的代理类:

[System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    public partial class ChatServiceClient : System.ServiceModel.DuplexClientBase<ChatClient.ChatService.IChatService>, ChatClient.ChatService.IChatService {
        
        public ChatServiceClient(System.ServiceModel.InstanceContext callbackInstance) : 
                base(callbackInstance) {
        }
        
        public ChatServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName) : 
                base(callbackInstance, endpointConfigurationName) {
        }
        
        public ChatServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, string remoteAddress) : 
                base(callbackInstance, endpointConfigurationName, remoteAddress) {
        }
        
        public ChatServiceClient(System.ServiceModel.InstanceContext callbackInstance, string endpointConfigurationName, System.ServiceModel.EndpointAddress remoteAddress) : 
                base(callbackInstance, endpointConfigurationName, remoteAddress) {
        }
        
        public ChatServiceClient(System.ServiceModel.InstanceContext callbackInstance, System.ServiceModel.Channels.Binding binding, System.ServiceModel.EndpointAddress remoteAddress) : 
                base(callbackInstance, binding, remoteAddress) {
        }

代码逻辑的分析:
我们在ChatServiceCallBack这个回调接口的实现类中添加了两个委托事件:

public event Action<MessageModule> MessageNotifyCallback;
public event Action<MessageModule[]> ToFriendListCallback;

这里简单介绍一下委托事件的用法:
使用回调,可以把一个函数返回报告给另一个函数,.NET中的委托支持回调。
我们这里使用的是.NET内置的泛型委托:Action<>和Func<>
许多情况下,我们只需要接受一组参数并返回一个值(void)的委托。
可以使用泛型委托。

Action<>泛型委托:

可以指向多至16参数,并返回void的方法
For Example:

 	//Action委托的目标
 	static void DisplayMessage(string msg, ConsoleColor txtColor, int printCount)
        {
            ConsoleColor previous = Console.ForegroundColor;
            Console.ForegroundColor = txtColor;
            for (int i=0;i<printCount;i++) {
                Console.WriteLine(msg);
            }
            //重置颜色
            Console.ForegroundColor = previous;
        }

 		static void main(string[] args) {
          Console.WriteLine("****** Fun with Acton and Func");
           Action<string, ConsoleColor, int> actionTarget = new Action<string, ConsoleColor, int>(DisplayMessage);	
           //调用委托-->调用委托指向的方法
			actionTarget("ACtion Message!",ConsoleColor.Yellow,5);
        } 

Func<>委托泛型:

可以指向多至16个参数,并具有自定义返回值的方法,
注意: Func<>的最后一个类型参数总是方法的返回值
For Example:

//Func委托的目标
static int Add(int x,int y){
	return x+y;
}
//在main方法里,调用委托
static void main(string[] args) {
	Func<int,int,int> funcTarget=new Func<int,int,int>(Add);
	//调用委托
	int result=funcTarget(10,20);//两个输入参数,一个返回值
	Console.WriteLine("result:"+result);
}

event 关键字:

为了简化自定义方法的构建,为委托调用列表增加和删除方法,C#提供event关键字。
在编译器处理event关键字的时候,它会自动提供注册和注销方法以及任何必要的委托类型成员变量。
所以可以在定义的委托对象前添加event关键字–>委托事件
监听传入的事件:
调用者仅需使用’+="和’-='操作符,来注册和注销事件,
写完操作符,按下Tab键,注册成功会自动生成回调函数
For Example:

//实例化回调处理类
            ChatServiceCallBack chatServiceCallBack = new ChatServiceCallBack();
            chatServiceCallBack.MessageNotifyCallback += ChatServiceCallBack_MessageNotifyCallback;//注册消息通知委托事件
            chatServiceCallBack.ToFriendListCallback += ChatServiceCallBack_ToFriendListCallback;//注册获取好友列表的委托事件

		//对应的回调方法:
 		/// 
        /// 获取好友列表的回调方法
        /// 
        /// 
        private void ChatServiceCallBack_ToFriendListCallback(MessageModule[] obj)
        {
            ...............................
        }
	
		 private void ChatServiceCallBack_MessageNotifyCallback(MessageModule obj)
        {
			.................................
		}

委托的好处在于,跨对象之间数据的回调传递
比如:Winform项目中跨关联页面数据的互相传输,可以在A页面里定义委托事件,然后在B页面注册此委托事件,在A页面里调用完此委托事件之后,会回到B页面执行对应的回调函数
注意:调用委托事件之前,务必判空

想要整个项目源码的朋友,请给我留下email地址!

你可能感兴趣的:(c#,wcf,后端)