服务器端实现原理
Session在服务器端具体是怎么实现的呢?我们使用session的时候一般都是这么使用的:
request.getSession()或者request.getSession(true)。
这个时候,服务器就检查是不是已经存在对应的Session对象,见HttpRequestBase类doGetSession(boolean create)方法:
if ((session != null) && !session.isValid()) session = null;
if (session != null) return (session.getSession());
// Return the requested session if it exists and is valid
Manager manager = null;
if (context != null) manager = context.getManager();
if (manager == null) return (null); // Sessions are not supported
if (requestedSessionId != null) {
try {
session = manager.findSession(requestedSessionId);
} catch (IOException e) {
session = null;
}
if ((session != null) && !session.isValid()) session = null;
if (session != null) {
return (session.getSession());
}
}
requestSessionId从哪里来呢?这个肯定是通过Session实现机制的cookie或URL重写来设置的。见HttpProcessor类中的parseHeaders(SocketInputStream input):
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equals(Globals.SESSION_COOKIE_NAME)) {
// Override anything requested in the URL
if (!request.isRequestedSessionIdFromCookie()) {
// Accept only the first session id cookie
request.setRequestedSessionId(cookies[i].getValue());
request.setRequestedSessionCookie(true);
request.setRequestedSessionURL(false);
}
}
}
或者HttpOrocessor类中的parseRequest(SocketInputStream input, OutputStream output)
// Parse any requested session ID out of the request URI
int semicolon = uri.indexOf(match); // match 是";jsessionid="字符串
if (semicolon >= 0) {
String rest = uri.substring(semicolon + match.length());
int semicolon2 = rest.indexOf(';');
if (semicolon2 >= 0) {
request.setRequestedSessionId(rest.substring(0, semicolon2));
rest = rest.substring(semicolon2);
} else {
request.setRequestedSessionId(rest);
rest = "";
}
request.setRequestedSessionURL(true);
uri = uri.substring(0, semicolon) + rest;
if (debug >= 1)
log(" Requested URL session id is " + ((HttpServletRequest) request.getRequest()).getRequestedSessionId());
} else {
request.setRequestedSessionId(null);
request.setRequestedSessionURL(false);
}
里面的manager.findSession(requestSessionId)用于查找此会话ID对应的session对象。Tomcat实现
是通过一个HashMap实现,见ManagerBase.java的findSession(String id):
if (id == null) return (null);
synchronized (sessions) {
Session session = (Session) sessions.get(id);
return (session);
}
Session本身也是实现为一个HashMap,因为Session设计为存放key-value键值对,Tomcat里面Session实现类是StandardSession,里面一个attributes属性:
/**
* The collection of user data attributes associated with this Session.
*/
private HashMap attributes = new HashMap();
所 有会话信息的存取都是通过这个属性来实现的。Session会话信息不会一直在服务器端保存,超过一定的时间期限就会被删除,这个时间期限可以在 web.xml中进行设置,不设置的话会有一个默认值,在tomcat目录下conf/web.xml中找到 <session-config> 元素,Tomcat的默认值是60分钟。那么服务器端是怎么判断会话过期的呢?原理服务器会启动一个线程,一 直查询所有的Session对象,检查不活动的时间是否超过设定值,如果超过就将其删除。见StandardManager类,它实现了Runnable 接口,里面的run方法如下:
/**
* The background thread that checks for session timeouts and shutdown.
*/
public void run() {
// Loop until the termination semaphore is set
while (!threadDone) {
threadSleep();
proces***pires();
}
}
/**
* Invalidate all sessions that have expired.
*/
private void proces***pires() {
long timeNow = System.currentTimeMillis();
Session sessions[] = findSessions();
for (int i = 0; i < sessions.length; i++) {
StandardSession session = (StandardSession) sessions[i];
if (!session.isValid())
continue;
int maxInactiveInterval = session.getMaxInactiveInterval();
if (maxInactiveInterval < 0)
continue;
int timeIdle = // Truncate, do not round up
(int) ((timeNow - session.getLastUsedTime()) / 1000L);
if (timeIdle >= maxInactiveInterval) {
try {
expiredSessions++;
session.expire();
} catch (Throwable t) {
log(sm.getString("standardManager.expireException"), t);
}
}
}
}
Session 信息在create,expire等事情的时候都会触发相应的Listener事件,从而可以对session信息进行监控,这些Listener只需要 继承HttpSessionListener,并配置在web.xml文件中。如下是一个监控在线会话数的Listerner:
public class MySessionListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent event) {
HttpSession session = event.getSession();
ServletContext application = session.getServletContext();
// 在application范围由一个HashSet集保存所有的session
HashSet sessions = (HashSet) application.getAttribute("sessions");
if (sessions == null) {
sessions = new HashSet();
application.setAttribute("sessions", sessions);
}
// 新创建的session均添加到HashSet集中
sessions.add(session);
// 可以在别处从application范围中取出sessions集合
// 然后使用sessions.size()获取当前活动的session数,即为“在线人数”
}
public void sessionDestroyed(HttpSessionEvent event) {
HttpSession session = event.getSession();
ServletContext application = session.getServletContext();
HashSet sessions = (HashSet) application.getAttribute("sessions");
// 销毁的session均从HashSet集中移除
sessions.remove(session);
}
}