什么是Session
對Tomcat而言,Session是一塊在服務器開辟的內存空間,其存儲結構為ConcurrentHashMap;
Session的目的
Http協議是一種無狀態協議,即每次服務端接收到客戶端的請求時,都是一個全新的請求,服務器並不知道客戶端的歷史請求記錄;
Session的主要目的就是為了彌補Http的無狀態特性。簡單的說,就是服務器可以利用session存儲客戶端在同一個會話期間的一些操作記錄;
實現機制
先看兩個問題,如下:
1、服務器如何判斷客戶端發送過來的請求是屬於同一個會話?
答:用Session id區分,Session id相同的即認為是同一個會話,在Tomcat中Session id用JSESSIONID表示;
2、服務器、客戶端如何獲取Session id?Session id在其之間是如何傳輸的呢?
答:服務器第一次接收到請求時,開辟了一塊Session空間(創建了Session對象),同時生成一個Session id,並通過響應頭的Set-Cookie:“JSESSIONID=XXXXXXX”命令,向客戶端發送要求設置cookie的響應;
客戶端收到響應后,在本機客戶端設置了一個JSESSIONID=XXXXXXX的cookie信息,該cookie的過期時間為瀏覽器會話結束;
接下來客戶端每次向同一個網站發送請求時,請求頭都會帶上該cookie信息(包含Session id);
然后,服務器通過讀取請求頭中的Cookie信息,獲取名稱為JSESSIONID的值,得到此次請求的Session id;
ps:服務器只會在客戶端第一次請求響應的時候,在響應頭上添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,接下來在同一個會話的第二第三次響應頭里,是不會添加Set-Cookie:“JSESSIONID=XXXXXXX”信息的;
而客戶端是會在每次請求頭的cookie中帶上JSESSIONID信息;
舉個例子:
以chrome瀏覽器為例,訪問一個基於tomcat服務器的網站的時候,
瀏覽器第一次訪問服務器,服務器會在響應頭添加Set-Cookie:“JSESSIONID=XXXXXXX”信息,要求客戶端設置cookie,如下圖:
同時我們也可以在瀏覽器中找到其存儲的sessionid信息,如下圖
接下來,瀏覽器第二次、第三次...訪問服務器,觀察其請求頭的cookie信息,可以看到JSESSIONID信息存儲在cookie里,發送給服務器;且響應頭里沒有Set-Cookie信息,如下圖:
只要瀏覽器未關閉,在訪問同一個站點的時候,其請求頭Cookie中的JSESSIONID都是同一個值,被服務器認為是同一個會話。
再舉個簡單的例子加深印象,新建個Web工程,並寫一個Servlet,在doGet中添加如下代碼,主要做如下工作
首先,從session中獲取key為count的值,累加,存入session,並打印;
然后,每次從請求中獲取打印cookie信息,從響應中獲取打印Header的Set-Cookie信息:
/** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if(request.getSession().getAttribute("count") == null){ request.getSession().setAttribute("count", 0); response.getWriter().write(0+""); }else{ int a = Integer.parseInt(request.getSession().getAttribute("count").toString()); request.getSession().setAttribute("count", ++a); response.getWriter().write(a+""); } Cookie[] cookies = request.getCookies(); StringBuffer sb = new StringBuffer(); if(cookies!=null){ for(Cookie cookie : cookies){ sb.append(cookie.getName()+":"+cookie.getValue()+","); } sb.deleteCharAt(sb.length()-1); } System.out.println("[第"+(++index)+"次訪問]from client request, cookies:" + sb); System.out.println("[第"+(index)+"次訪問]from server response, header-Set-Cookie:" + response.getHeader("Set-Cookie"));; }
部署到tomcat后,連續訪問該servlet,觀察控制台輸出,如下,客戶端第一次訪問服務器的時候,在服務端的響應頭里添加了JSESSIONID信息,且接下來客戶端的每次訪問都會帶上該JSESSIONID:
其實這里有一個問題,session劫持
只要用戶知道JSESSIONID,該用戶就可以獲取到JSESSIONID對應的session內容,還是以上面這個例子為例,
我先用IE瀏覽器訪問該站點,比如連續訪問了5次,此時,session中的count值為:
查看該會話的Session id,為6A541281A79B24BC290ED3270CF15E32
接下來打開chrome控制台,將IE瀏覽器獲取過來的JSESSIONID信息(“6A541281A79B24BC290ED3270CF15E32”)寫入到cookie中,如下
接着刪除其中的一個,只留下JSESSIONID為“6A541281A79B24BC290ED3270CF15E32”的cookie;
刷新頁面,發現我們從session獲取的count值已經變成6了,說明此次chrome瀏覽器的請求劫持了IE瀏覽器會話中的session,
Tomcat中的session實現
Tomcat中一個會話對應一個session,其實現類是StandardSession,查看源碼,可以找到一個attributes成員屬性,即存儲session的數據結構,為ConcurrentHashMap,支持高並發的HashMap實現;
/** * The collection of user data attributes associated with this Session. */ protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
那么,tomcat中多個會話對應的session是由誰來維護的呢?ManagerBase類,查看其代碼,可以發現其有一個sessions成員屬性,存儲着各個會話的session信息:
/** * The set of currently active Sessions for this Manager, keyed by * session identifier. */ protected Map<String, Session> sessions = new ConcurrentHashMap<String, Session>();
接下來,看一下幾個重要的方法,
服務器查找Session對象的方法
客戶端每次的請求,tomcat都會在HashMap中查找對應的key為JSESSIONID的Session對象是否存在,可以查看Request的doGetSession方法源碼,如下源碼:

先看doGetSession方法中的如下代碼,這個一般是第一次訪問的情況,即創建session對象,session的創建是調用了ManagerBase的createSession方法來實現的; 另外,注意response.addSessionCookieInternal方法,該方法的功能就是上面提到的往響應頭寫入“Set-Cookie”信息;最后,還要調用session.access方法記錄下該session的最后訪問時間,因為session是可以設置過期時間的;
session = manager.createSession(sessionId); // Creating a new session cookie based on that session if ((session != null) && (getContext() != null) && getContext().getServletContext(). getEffectiveSessionTrackingModes().contains( SessionTrackingMode.COOKIE)) { Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie( context, session.getIdInternal(), isSecure()); response.addSessionCookieInternal(cookie); } if (session == null) { return null; } session.access(); return session;
再看doGetSession方法中的如下代碼,這個一般是第二次以后訪問的情況,通過ManagerBase的findSession方法查找session,其實就是利用map的key從ConcurrentHashMap中拿取對應的value,這里的key即requestedSessionId,也即JSESSIONID,同時還要調用session.access方法,記錄下該session的最后訪問時間;
if (requestedSessionId != null) { try { session = manager.findSession(requestedSessionId); } catch (IOException e) { session = null; } if ((session != null) && !session.isValid()) { session = null; } if (session != null) { session.access(); return (session); } }
在session對象中查找和設置key-value的方法
這個我們一般調用getAttribute/setAttribute方法:
getAttribute方法很簡單,就是根據key從map中獲取value;
setAttribute方法稍微復雜點,除了設置key-value外,如果添加了一些事件監聽(HttpSessionAttributeListener)的話,還要通知執行,如beforeSessionAttributeReplaced, afterSessionAttributeReplaced, beforeSessionAttributeAdded、 afterSessionAttributeAdded。。。
session存在的問題
- 安全性,session劫持,這個前面已經舉過例子了;
- 增加服務器壓力,因為session是直接存儲在服務器的內存中的;
- 如果存在多台服務器的話,還存在session同步問題,當然如果只有一台tomcat服務器的話,也就沒有session同步的事情了,然而現在一般的應用都會用到多台tomcat服務器,通過負載均衡,同一個會話有可能會被分配到不同的tomcat服務器,因此很可能出現session不一致問題;解決session同步問題,實際上主要是保證能夠抽離出一塊共享空間存放session信息,且這塊空間不同的tomcat服務器都可以訪問到;一般這塊共享的空間可以是數據庫,或者某台服務器的內存空間,甚至硬盤空間,或者客戶端的cookie也是可以的;