初探Tomcat源码 (7) —— Session_Manager

        HTTP协议本身是无状态的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机的关系一样。
但是很多时候,我们需求区分用户发来的请求是否来自于同一个浏览器,例如用户只用登陆一次,就可以了,后来的请求都带着这个登陆信息,就不用重复登陆了。
所以客户端和服务器端的交互,需要携带一部分的状态信息,CookieSession的解决方案应运而生。
 
        就好比我们到超市买东西,cookie就像我们的会员卡,存放在客户那,每次消费完都记录一下,下次买的时候就知道有多少积分了。而session就像是购物车,存放在服务端,我们每次进超市都会生成一个购物车实例,我们和服务器的交互就相当于来到不同的货架买东西,session都给我们记录着,一直到我们走出超市的时候才回收,否则我们就不记得自己买过什么了。
        综上所述,cookie机制采用的是在客户端保持状态的方案,而session机制采用的是在服务器端保持状态的方案。
Cookie对应会员卡,是存储在客户端的一个小文本文件。客户每次访问服务器都要附带着的。
而Session通常存放在服务器的内存中,用来存储客户的临时状态信息,超时回收。一般是,客户第一次访问,服务器就会创建一个Session并把ID返回给客户,客户每次访问的时候都需要携带着这个ID号,用来唯一标识这个客户,直到离开或者超时。  


Session

Session对象存储在服务器端,记录一个浏览器客户的信息,属性以HashMap的形式存放着。

protected HashMap attributes = new HashMap();

 

一般的,在客户第一次访问的时候创建Session实例,然后返回一个ID,客户每次访问都携带着这个ID来标识身份,客户端和服务器端可以通过这个Session实例来进行属性状态的交互。

 

Tomcat5.0中Catalina通过管理器组件Manager来完成session的管理。一个Manager通常跟一个Context相关联,它负责创建、更新以及销毁session对象,并给任何请求组件返回一个合法的session,此处通常为Facade

默认情况下管理器将session对象存储在内存中,但是Tomcat也允许将session对象存储在文件或者数据库中(通过JDBC)。

 

Manager

管理器相当于一个Session,管理Session对象的创建、更新以及销毁,该组件由org.apache.catalina.Manager接口表示。

ManagerBase类提供了Manager的常用函数基本实现。他有两个直接子类:StandardManager和PersistentManagerBase类。

在运行的时候,StandardManager将session对象存放在内存中,维护一个session池,循环使用和回收,而当服务器start/stop时,还需要从文件SESSIONS.ser和内存间互相进行Session实例的持久化和反序列化。  而PersistentManagerBase负责session的持久化存储,备份在文件或者数据库中,需要的时候再拿出来。为了支持持久化,Session和其属性需要实现序列化。

 

ManagerBase

这个类是所有Manager类的抽象类,提供了很多公共的方法。

ManagerBase维护了一个激活的Session的Map

protected HashMap sessions = new HashMap();

 

通过createSession()来创建获取Session,新建一个Session对象,并把自身this传入。     

另外,每当创建Session,就会随机生成一个SessionID,关联到Session实例中,在Session.setId()中就会把当前Manager实例this关联到Session中。

public Session createSession(StringsessionId) {

        // Recycle or create a Session instance

       Session session = createEmptySession();

       session.setNew(true);

       session.setValid(true);

       session.setCreationTime(System.currentTimeMillis());

       session.setMaxInactiveInterval(this.maxInactiveInterval);

        if (sessionId == null) {

           sessionId = generateSessionId();

       }

       session.setId(sessionId);

       sessionCounter++;

        return (session);

}

protected synchronized String generateSessionId() {

       Random random = getRandom();

        byte bytes[] = new byte[SESSION_ID_BYTES];

       getRandom().nextBytes(bytes);

       bytes = getDigest().digest(bytes);

 

        // Render the result as a String ofhexadecimal digits

       StringBuffer result = new StringBuffer();

        for (int i = 0; i < bytes.length; i++) {

            byte b1 = (byte) ((bytes[i] & 0xf0) >> 4);

            byte b2 = (byte) (bytes[i] & 0x0f);

            if (b1 < 10)

               result.append((char) ('0' + b1));

            else

               result.append((char) ('A' + (b1 - 10)));

            if (b2 < 10)

               result.append((char) ('0' + b2));

            else

               result.append((char) ('A' + (b2 - 10)));

       }

        return (result.toString());

}

 

        在Session的get和put时候,需要注意防止冲突,在互联网这个大并发量的环境中,Session实例的数据很容易冲突。这里到后面的jdk版本,可以用ConcurrentHashMap替换HashMap,能提高并发的效率。

public Session findSession(String id) throws IOException {

        if (id == nullreturn (null);

        synchronized (sessions) {

           Session session = (Session) sessions.get(id);

            return (session);

       }

   }

    public void add(Session session) {

        synchronized (sessions) {

            sessions.put(session.getId(), session);

            ifsessions.size() > maxActive ) {

                maxActive=sessions.size();

           }

       }

   }


StandardManager

管理器组件,一般指的是StandardManager,因为其继承了ManagerBase,并且实现了Lifecycle接口,可以受到容器生命周期的控制

start()方法中,执行load()并触发开始事件监听。    

stop()方法中,执行unload(),并且把Manager下的Session实例都置失效expire(),并触发事件。       start和stop两个方法通过属性started来避免重复开始或结束。

unload(),持久化可用的Session实例到sessions.ser文件中。这个文件保存在CATALINA_HOME下。

load()则相反,从该文件中读取Session,反序列化成Session实例。

protected Stringpathname ="SESSIONS.ser";

 

创建Session实例,在ManagerBase.createSession()上添加了一个计数器,如果查过Session最大数量,就拒绝并抛出异常,记录下已拒绝的次数。

public Session createSession() {

        if ((maxActiveSessions >= 0) && (sessions.size() >=maxActiveSessions)) {

            rejectedSessions++;

            thrownew IllegalStateException

                (sm.getString("standardManager.createSession.ise"));

        }

        return (super.createSession());

}

 

有效性检查 

        有效性检查(主要是超时),原来专门启动一个扫描线程来检查Session的有效性,如果失效超时了,就终结回收。后来所有的组件都共享容器中的后台线程,那么只需要把校验逻辑放到backgroundProcess(),就可以把逻辑挂到容器的后台线程执行了。

检查逻辑:遍历session实例,执行session.isValid(),让每个Session自己检查有效性,最后更新处理时长。   如果超时了,checkInterval默认60s ,session实例会调用Manager来解除关联

publicvoid backgroundProcess() {

        processExpires();

}

publicvoid processExpires() {

        long timeNow = System.currentTimeMillis();

        Session sessions[] = findSessions();

        for (int i = 0; i < sessions.length; i++) {

            StandardSession session =(StandardSession) sessions[i];

            if (!session.isValid()) {

                expiredSessions++;

            }

        }

        long timeEnd = System.currentTimeMillis();

        processingTime += ( timeEnd - timeNow );

}

 

load 加载

启动的时候执行load(),会把Session从文件中加载出来:

A 清空内存Session实例;

B 读取文件,把每个Session加载到内存中;

C 删除Session文件,因为大多数情况下,下次stop时保存的Session几乎都不一样,所以没有必要还存着原来的,直接重新生成一个Session文件即可。

(为了方便阅读,省略log打印和异常捕捉的部分代码)

protectedvoid doLoad()throws ClassNotFoundException, IOException {

        sessions.clear();         //加载session前,先把内存清掉

        File file = file();

        if (file ==nullreturn;

        FileInputStream fis = null;

        ObjectInputStream ois = null;

        Loader loader = null;

        ClassLoader classLoader = null;

        try {

            fis = new FileInputStream(file.getAbsolutePath());

            BufferedInputStream bis =newBufferedInputStream(fis);

            if (container !=null)

                loader = container.getLoader();

            if (loader !=null)

                classLoader =loader.getClassLoader();

            if (classLoader !=null) {

                ois = newCustomObjectInputStream(bis, classLoader);

            } else {

                ois = newObjectInputStream(bis);

            }

        } catch (FileNotFoundException e) {

        } catch (IOException e) {

        }

        // 加载文件中的Session

        synchronized (sessions) {

            try {

                Integer count = (Integer)ois.readObject();

                int n = count.intValue();

for (int i = 0; i < n;i++) {

                    StandardSession session =getNewSession();

                   session.readObjectData(ois);

                    session.setManager(this);

                    sessions.put(session.getId(), session);

                    session.activate();

                    session.endAccess();

                }

            } catch (ClassNotFoundException e) {

} catch (IOException e) {

} finally {

                if (ois !=null)  ois.close();

                if (file !=null && file.exists() )

                    file.delete();        //加载后,删除session文件

            }

        }

}

 

unload 持久化

关闭的时候会调用unload()来把当前有效的Session持久化到文件中:

A 打开/创建Session文件

B 把内存的Session实例逐个写入文件

C 清除已写入文件的Session,通常是干掉所有内存中的Session实例,因为大多数情况下执行unload的时候,就是关闭的时候,把内存的Session实例数据转移到文件中。

(为了方便阅读,省略log打印和异常捕捉的部分代码)

protectedvoid doUnload()throws IOException {

        File file = file();

        if (file ==null)return;

        FileOutputStream fos = null;

        ObjectOutputStream oos = null;

        try {

            fos = new FileOutputStream(file.getAbsolutePath());

            oos = new ObjectOutputStream(new BufferedOutputStream(fos));

        } catch (IOException e) { }

 

        ArrayList list = new ArrayList();

        synchronized (sessions) {

            try {

                oos.writeObject(new Integer(sessions.size()));

                Iterator elements = sessions.values().iterator();

                while (elements.hasNext()) {

                    StandardSession session = (StandardSession) elements.next();

                    list.add(session);

                    ((StandardSession)session).passivate();

                    session.writeObjectData(oos);     //逐个写入文件

                }

            } catch (IOException e) {}

        }

        try {                 // Flush and close the output stream

            oos.flush();

            oos.close();

            oos = null;

        } catch (IOException e) {}

       

        Iterator expires = list.iterator();    //把已写入文件的Session置失效掉

        while (expires.hasNext()) {    

            StandardSession session =(StandardSession) expires.next();

            session.expire(false);

        }

}

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值