HTTP协议本身是无状态的,客户端只需要简单的向服务器请求下载某些文件,无论是客户端还是服务器都没有必要纪录彼此过去的行为,每一次请求之间都是独立的,好比一个顾客和一个自动售货机的关系一样。
但是很多时候,我们需求区分用户发来的请求是否来自于同一个浏览器,例如用户只用登陆一次,就可以了,后来的请求都带着这个登陆信息,就不用重复登陆了。
所以客户端和服务器端的交互,需要携带一部分的状态信息,Cookie和Session的解决方案应运而生。
就好比我们到超市买东西,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 == null) return (null);
synchronized (sessions) {
Session session = (Session) sessions.get(id);
return (session);
}
}
public void add(Session session) {
synchronized (sessions) {
sessions.put(session.getId(), session);
if( sessions.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 ==null) return;
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);
}
}