• 传说中的WCF(11):会话(Session)


    在标题中我加了一个大家都很熟悉的单词——Session,熟吧?玩过Web开发的朋友肯定在梦中都会见到她。

    在Web中为什么要会话呢?毕竟每个用户在一个Web应用中可能不止进行一次操作,比如,某二手飞机交易网站,用户A登陆后,可能他会修改他的个人 信息,他也有可能看好了一架二手飞机,打算入手,就把商品放到他的“购物车”中,这些过程中,都会产生许多与用户A相关的数据,这些数据只是对A有效,而 当用户B登陆后,对于B,又会有他自己的数据,总的一句话就是,每个客户端在服务器上都有其的独立数据存储区,互不相干,就好像A和服务器在单独谈话一样,所以叫会话。

    在WCF中,会话的含义与Web中的会话概念是差不多的,就是客户端与服务器端在“私聊”,这便是存在会话的调用;那么,没有会话的调用呢,就是“群聊”;通信过程中,数据都以明文显示,不进行加密保护,这叫“裸*聊”。

    好了,现在大家对于会话,肯定有点理解了。但是,会话是看不见摸不着的,怎么通过实例来检验它呢?

    下面,我们写一个例子,看看在不支持会话的绑定上连续调用两个有关联的代码,会发生什么情况。

    复制代码
    namespace Server
    {
        [ServiceContract]
        public interface IService
        {
            [OperationContract(IsOneWay = true)]
            void SetValue(int n);
            [OperationContract]
            int GetValue();
        }
    }
    复制代码
    复制代码
    namespace Server
    {
        [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
        public class MyService : IService
        {
    
            public MyService()
            {
                Console.WriteLine("-------------------------------");
                Console.WriteLine("{0} - 服务被实例化。", DateTime.Now.ToLongTimeString());
            }
            // 在析构函数中也输出信息
            ~MyService()
            {
                Console.WriteLine("{0} - 服务实例被释放。", DateTime.Now.ToLongTimeString());
                Console.WriteLine("--------------------------------");
            }
    
            /// <summary>
            /// 私有字段
            /// </summary>
            private int mValue = int.MinValue;
    
            public void SetValue(int n)
            {
                this.mValue = n;
                Console.WriteLine("会话ID:{0}", OperationContext.Current.SessionId);
            }
    
            public int GetValue()
            {
                Console.WriteLine("会话ID:{0}", OperationContext.Current.SessionId);
                return this.mValue;
            }
        }
    }
    复制代码

    在服务类中,我们分别在构造函数和析构函数中输出一些内容,以便于在运行时看到服务什么时候被实例化,什么时候被释放。同时,在调用每个操作协定时,我们也输出当前会话的ID,如果空就说明当前没有启用会话。

    接着,实现服务主机,我们使用不支持的Bindding。

    复制代码
    namespace Server
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.Title = "WCF服务器端";
                using (ServiceHost host = new ServiceHost(typeof(MyService), new Uri("http://127.0.0.1:1211/sv")))
                {
                    // 绑定
                    BasicHttpBinding binding = new BasicHttpBinding();
                    binding.Security.Mode = BasicHttpSecurityMode.None;//不需要安全模式
                    host.AddServiceEndpoint(typeof(IService), binding, "/ep");
                    // 服务元数据
                    ServiceMetadataBehavior mb = new ServiceMetadataBehavior();
                    mb.HttpGetEnabled = true;
                    mb.HttpGetUrl = new Uri("http://127.0.0.1:8008/meta");
                    host.Description.Behaviors.Add(mb);
                    host.Opened += (sender, arg) =>
                    {
                        Console.WriteLine("服务已启动。");
                    };
                    try
                    {
                        host.Open();//打开服务
                        Console.Read();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }
        }
    }
    复制代码

    然后,实现客户端,为了使演示操作更方便,客户端使用wpf项目,界面大致如下图所示。

    服务有两个操作,从前面服务类的定义我们知道,我在服务类内部定义了一个私有字段,而公开的两个操作协定,分别是设置这个值和获取这个值。

    复制代码
    namespace Client
    {
        /// <summary>
        /// Interaction logic for MainWindow.xaml
        /// </summary>
        public partial class MainWindow : Window
        {
            ServiceReference1.ServiceClient client = null;
    
            public MainWindow()
            {
                InitializeComponent();
    
                client = new ServiceReference1.ServiceClient();
                // 关闭通道
                this.Closing += (frmsender, frmargs) => client.Close();
            }
    
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                int v;
                if (!int.TryParse(this.textBox1.Text, out v))
                {
                    return;
                }
                client.SetValue(v);
            }
    
            private void button2_Click(object sender, RoutedEventArgs e)
            {
                int v = client.GetValue();
                this.textBox2.Text = v.ToString();
            }
        }
    }
    复制代码

    我们现在要测试一下,看看先后调用这两个方法,能不能得到我们预期的值。即如果我调用SetValue(100),那么,在调用GetValue时应该返回100,事实是不是这样呢?

    八格牙路,我没有看到预期的值。

    我输入了100,可是取出来的是Min int,再看一看服务器端输出了哪些信息。

    很明显,每调用一次操作,服务类就被实例化一次,意思就是:调用SetValue时是实例A,而调用GetValue时可能是实例B了,所以,私有字段的值没有被保存。

    那么,如何证明两次调用的操作不属于同一个服务实例呢?还记得GetHashCode吗?对的,只要在内存中不是同一个实例的,其哈希值肯定不同。是不是这样呢,我们把上面的服务代码改一下。

    复制代码
            public void SetValue(int n)
            {
                this.mValue = n;
                Console.WriteLine("------------------------");
                Console.WriteLine("当前实例的哈希值:{0}", this.GetHashCode().ToString("x"));
                Console.WriteLine("会话ID:{0}", OperationContext.Current.SessionId);
            }
    
            public int GetValue()
            {
                Console.WriteLine("------------------------");
                Console.WriteLine("当前实例的哈希值:{0}", this.GetHashCode().ToString("x"));
                Console.WriteLine("会话ID:{0}", OperationContext.Current.SessionId);
                return this.mValue;
            }
    复制代码

    那么,这次又会发生什么事呢,看结果。

    这个结果证实了我之前的推断,先后调用的两个方法不是同一个实例的。

    那么,如果启用了会话,结果又会如何呢?

        [ServiceContract(SessionMode = SessionMode.Required)]
        public interface IService
        {
            。。。。。
         }

    在定义服务协定的时候,设置SessionMode可以让服务要求客户端启用会话。

    接下来,要使用支持会话的绑定。

    复制代码
                    // 绑定
                    //BasicHttpBinding binding = new BasicHttpBinding();
                    //binding.Security.Mode = BasicHttpSecurityMode.None;//不需要安全模式
                    //host.AddServiceEndpoint(typeof(IService), binding, "/ep");
                    NetTcpBinding binding = new NetTcpBinding();
                    binding.Security.Mode = SecurityMode.None;
                    host.AddServiceEndpoint(typeof(IService), binding, "net.tcp://127.0.0.1:2377/ep");
    复制代码

    把客户端的服务引用更新一下。然后看看这回会不会达到我们的目的。

    怎么样,高兴吧?终于看到我们要的效果了,我输入了100,取出来的还是100,这一回从服务器端的输出看,服务类只被实例化了一次,而且看看两个哈希值是相同的,这证明了确实是同一个实例,同时,我们也看到了两次调用的会话ID是一样的,这也说明了,客户端两次调用都基于同一个会话进行的,这么一来,输进去的100就能顺利取出来了。

    你不妨多开几个客户端来试试。

    看到那个不同的会话ID,哈希值和实例化时间了吧?这表明了:服务器独立维护着与每个客户端的会话。

    下面还要对我们的解决方案进行修改。

    服务器端,把服务协定改为:

    复制代码
        [ServiceContract(SessionMode = SessionMode.Required)]
        public interface IService
        {
            [OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)]
            void SetValue(int n);
    
            [OperationContract]
            int GetValue();
    
            [OperationContract(IsInitiating = false, IsTerminating = true)]
            void EndSession();
        }
    复制代码

    在服务类中增加对EndSession方法的实现。

            public void EndSession()
            {
                Console.WriteLine("会话结束。");
            }

    看到变化了吗?

    我们在使用OperationContractAttribute定义操作协定时,设置了两个属性:

    a、IsInitiating:如果为真,则当调用该操作时就启用会话。

    b、IsTerminating:如果为真,则说明当该用该操作时终结会话。

    所以,上面的例子是,当调用SetValue时开始会话,当调用EndSession方法后会话结束,在选择作为结束会话的方法时,最好使用返回值为void或者单向通讯(One Way)的方法,这样,不用等待客户结束才结束会话,因为单向通讯,不需要向客户端回复消息,因为它被调用后就可以马上终止会话了。

    复制代码
            ServiceReference1.ServiceClient client = null;
    
            public MainWindow()
            {
                InitializeComponent();
    
                client = new ServiceReference1.ServiceClient();
                // 关闭通道
                //this.Closing += (frmsender, frmargs) => client.Close();
            }
    复制代码

    然后,在调用完GetValue后马上就EndSession,看看这回又会发生什么。

            private void button2_Click(object sender, RoutedEventArgs e)
            {
                int v = client.GetValue();
                this.textBox2.Text = v.ToString();
                client.EndSession();
            }

    这样,一旦三个方法调用完之后,会话结束,服务实例也解放了。

    再说明一下,IsInitiating = true的操作开始新会话,IsTerminating = true的操作终结会话。

  • 相关阅读:
    百度mp3地址解密码
    VB 在EXE后附加信息
    截屏函数
    Base64和StrToByte
    The Android ION memory allocator, DMABUF is mentioned as well
    DDC EDID 介绍
    Memory management for graphic processors TTM的由来
    科普 写display driver的必看 How video card works [2D的四种主要操作]
    GEM vs TTM
    DMABUF 背景介绍文章 Sharing buffers between devices
  • 原文地址:https://www.cnblogs.com/ywsoftware/p/3663876.html
Copyright © 2020-2023  润新知