WCF入门(十一)——会话

时间:2022-09-01 08:56:44

在WCF服务中,有时我们要保存会话状态。例如,对于一个客户端来说,需要保存它是否登录的状态,从而提供不同的服务。在WCF中,对于登录服务来说,我们可以通过如下方式实现:

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        void Login();

        [OperationContract]
        bool IsLoggedIn();
    }

    [ServiceBehavior]
    public class Service1 : IService1
    {
        bool isLoggedin = false;

        public void Login() { isLoggedin = true; }
        public bool IsLoggedIn() { return isLoggedin; }
    }

可见,WCF是通过服务对象来表示会话的:一个服务对象对应一个会话,在对象中通过成员函数即可保存会话状态。非常简单而直观。

但是,当我们验证这个功能的时候,发现并没有如我们预期那样在登录后返回true,仍然返回的是false。

WCF入门(十一)——会话

通过Debug我们可以发现:服务对象并非像我们所预期的那样每个会话创建一个,而是每次调用都创建了一个,从而导致调用LoggedIn的服务对象并没有调用Login,仍然维持在未登录状态

因此,要实现一个服务对象对应一个会话,还需要服务器端实现如下功能:

  1. 同一个客户端的所有调用都发送给同一个服务对象
  2. 不同的客户端的调用发送给不同的服务对象

WCF并不是无条件实现这个功能的,要实现这两个约束,要满足三个条件:

  1. 服务对象的的创建策略要设置为PerSession(这个是默认行为,上一章也介绍过,这里就不再多说了)
  2. 服务契约中开启会话
  3. 低层协议支持会话功能(持久化)

 

开启会话

服务契约中开启会话是在的SessionMode属性中实现的,它有三种行为方式:

  • Allowed        默认状态,尝试开启会话
  • Required        强制开启会话
  • NotAllowed    不开启会话

默认策略是Allowed,显式设置的方式如下:

    [ServiceContract(SessionMode=SessionMode.Allowed)]
    public interface IService1

其中Required和NotAllowed还比较好理解,但为什么会有Allowed这个策略?

这是因为会话要依靠传输层的持久连接来实现的,而持久连接特性并不是所有的协议都支持的。例如,默认的BasicHttpBinding协议就不支持,而支持持久连接的常见协议有NetTcpBinding和WsHttpBinding等。

而我们现在恰好就是用的不支持持久连接的BasicHttpBinding,按照SessionMode.Allowed策略下,开启会话失败。在前文《WCF服务对象模型》中已经介绍过,当Session没有设置是,PerSession等价于PerCall,每次服务调用都会创建一个新的服务对象,无法使用会话功能。

知道原因后,就知道如何修改了,我这里把协议改成了WsHttpBinding,这个时候再测试时,就能正确返回登录状态了。

WCF入门(十一)——会话

由此可见:由于低层协议限制,默认方式下,并不能保证会话功能一定生效。由此,对于要实现会话功能的业务,建议通过将SessionMode设置为Required强制要求开启会话,从而保证业务能按预期结果运行。

    [ServiceContract(SessionMode=SessionMode.Required)]
    public interface IService1

当SessionMode设置为Required时,如果底层协议不支持持久化,则会启动失败,能提示你正确配置底层协议,不会以一种非预期的状态运行。

WCF入门(十一)——会话

 

会话和InstanceContext

每个会话是和一个InstanceContext关联的,通过它可以获取会话更多信息,从而实现更多功能。例如,如果我们要感知会话什么时候关闭的(靠感知析构函数调用需要等到GC的时候,是不可取的),可以通过如下代码实现:

    public Service1()
    {
        var ic = OperationContext.Current.InstanceContext;
        ic.Closed += (s, e) => Debug.WriteLine(">>> session closed.");
    }

关于InstanceContext更多内容,以后会专门写一篇文章来解释,这里就不多说了。

PS:仅仅需要感知下线的话,用我在前文中服务对象声明周期中介绍的那个方法——在Disopse方法中感知会话下线更加方便。

 

会话和流

当您有大量的数据要传输时,WCF 中的流传输模式是整个在内存中缓冲和处理消息的默认行为的一个可行的替代方法。 在流与基于会话的绑定一起调用时可能会产生意外行为。可通过不支持会话的单一通道(数据报通道)执行所有流调用 。

关于会话更多信息,请参看MSDN文章:WCF中使用会话