我爱电脑技术论坛's Archiver

黑侠 发表于 2008-5-1 02:04

关于JSP中基于Session的在线用户统计分析

JSP作为后起之秀能够在服务器编程环境中占据一定地位,是和它良好支持一系列业界标准密切相关的。Session就是它提供的基础设施之一。作为一个程序员,你可以不介意具体在客户端是如何实现,就方便的实现简单的基于session的用户管理。现在对于处理在线用户,有几种不同的处理方法。
AV5U4nvr`r)Y ;qE1izYB2q{
.Z&n_N(~,\wPe
一种是页面刷新由用户控制,服务器端控制一个超时时间比如30分钟,到了时间之后用户没有动作就被踢出。这种方法的优点是,如果用户忘了退出,可以防止别人恶意操作。缺点是,如果你在做一件很耗时间的事情,超过了这个时间限制,submit的时候可能要再次面临登陆。如果原来的叶面又是强制失效的话,就有可能丢失你做的工作。在实现的角度来看,这是最简单的,Server端默认实现的就是这样的模式。
O6q)Idb?h I$TEXun:Gd l'i

1u4J` m]2m(R 另一种方式是,站点采用框架结构,有一个Frame或者隐藏的iframe在不断刷新,这样你永远不会被踢出,但是服务器端为了判断你是否在线,需要定一个发呆时间,如果超过这个发呆时间你除了这个自动刷新的页面外没有刷新其他页面的话,就认为你已经不在线了。采取这种方式的典型是xici.net。 他的优点是可以可以利用不断的刷新实现一些类似server-push的功能,比如网友之间发送消息。
_9h+U e.{b5q Fe3M:XijW
\ O H c2Cte.on m
不管哪一种模式,为了实现浏览当前所有的在线用户,还需要做一些额外的工作。Servlet API中没有得到Session列表的API。 +k)tjiv.cl
.V5D _9aBkGl

2@kQtN"qD a 可以利用的是Listener. Servlet 2.2和2.3规范在这里略微有一些不一样。2.2中HttpSessionBindingListener可以实现当一个HTTPSession中的Attribute变化的时候通知你的类。而2.3中还引入了HttpSessionAttributeListener.鉴于我使用的环境是Visual age for Java 4和JRun server 3.1,他们还不直接支持Servlet 2.3的编程,这里我用的是HttpSessionBindingListener. UY nqM A8h

uOCc)dN C2s
H3S%PV2Pe*m.eCx G 需要做的事情包括做一个新的类来实现HttpSessionBindingListener接口。这个接口有两个方法: Lt;B`7gox#\WH
u&u'pCoMP]{
@5z(A/[#S&F2O
public void valueBound(HttpSessionBindingEvent event) 'Vh-`3Bh}uP4m

]l*dL"B Y;yRO public void valueUnbound(HttpSessionBindingEvent event) #ZurFn/Y~ s
_'C aY1I;~ a)k]L8m

0U|f7d c 当你执行Session.addAttribute(String,Object)的时候,如果你已经把一个实现了HttpSessionBindingListener接口的类加入为Attribute,Session会通知你的类,调用你的valueBound方法。相反,Session.removeAttribute方法对应的是valueUndound方法。 A"^ R [g#b/O/P
9_ a!W,MF$F*~tF?M

St_qA+hn&H public class HttpSessionBinding implements javax.servlet.http.HttpSessionBindingListener
\M$D"a&N/wl.I/C b )?{8B#O*M&s
{ dr0r\2O)|Jp6D y

moB!~ d?+jC9C HE  ServletContext application = null;
DE0{i"M,x*w4I 4GG?xu6Y+z2g K.EI
7LVaz/gx6z
 public HttpSessionBinding(ServletContext application) -BTR;G'I"b)a
\ ^/\-Qr#W1C
 {
\+Ij w2H.j/Vg 3R9N(tg-n9R Q
super(); `2u8ADJ }4YbM

V m(M Z F4mG if (application ==null) v;E~.vf#Co"Wm.LD4p)g
3^1l"n6e;d8Gx
 throw new IllegalArgumentException("Null application is not accept.");
-K9N Gn ?J%vJh'dk 8C(|9K cm
this.application = application;
)c&E7cID 4pY9QBjj'h d w
 }
$ro.U#?1sV,@ j
#kUfM%B{ p&S
ex"p]4A  public void valueBound(javax.servlet.http.HttpSessionBindingEvent e) #m I1Oe+R
N9MT:X"FG R'I u
 {
6} uT3Zz*Q
:[.Yr:W3w2QX[,g [x Vector activeSessions = (Vector) application.getAttribute("activeSessions");
+I[ujj+y Z{;j
;~ [&n@;p3XV1T if (activeSessions == null)
9~x2`K0]*YP+v*i v |ghIs0\(B
{ *dr@5}$m]

,bwy3o'o$s,C%i(l  activeSessions = new Vector(); /w#Ar[[.H#l
g\ }!d&Rs
} 9li'z"AuX7R
2FO0K'j+m.\y
M6b ]|\X(s5t
JDBCUser sessionUser = (JDBCUser)e.getSession().getAttribute("user"); f J P4q6q)sNn/f+p

BYD^X&YF if (sessionUser != null)
z@!pn'|(p}p 5MX-?Jm3q
{
:rqqE#t j)F1H`t
!H(uk9t L  activeSessions.add(e.getSession()); [5B0F&G[,wun8n{

yC Zg6`8? }
9{bE7R5m C1O
Z?4hReU application.setAttribute("activeSessions",activeSessions); YPOd{ s+M~[

Ck$\&D0b:S  }
6m5X;G sx"vj_-Sp
BB!]Rd.B6Q#h :DA#v2}s ]]F
 public void valueUnbound(javax.servlet.http.HttpSessionBindingEvent e)
kz:`g8O6d9RI 6[ G[4cqp
 { 9\.k{f3r

"x;q;i0or[ JDBCUser sessionUser = (JDBCUser)e.getSession().getAttribute("user"); LR.b%j*KS?
)FdL)A Z3e;]
if (sessionUser == null)
vPbTr w ;oF l RE&},Y Tud
{ -}*c,l.Mqb,a

ZGu ha.@  Vector activeSessions = (Vector) application.getAttribute("activeSessions"); -q1sa+w"x9x%C
^X c5^ _{c8q.i
 if (activeSessions != null) xS)e$C N]d {U

-b*M6X}ce]+h  {
j'j*d;};X,W6J;B
[#N.W[^;D'rQ activeSessions.remove(e.getSession().getId()); K6a-^kd'Oe
bEoB ^7w#II8p1w|H
application.setAttribute("activeSessions",activeSessions);
9M5Fu,pBQJ D*] !G8iXV;Q2^.c
 }
)?4CE,[)F#^ `.Z
:x&n7y+T^7a| }
z*TG;j(h.t&ZKYrz
-^A*h4I:nsA  } cj~0a-k:O-qk
\df5Z1]D
}

黑侠 发表于 2008-5-1 02:04

假设其中的JDBCUser类是一个任意User类。在执行用户登录时,把User类和HttpSessionBinding类都加入到Session中去。 ,A.\uIJ1C.fC:Z

)T|(HhehP8_`j {,^{EyvMx
这样,每次用户登录后,在application中的attribute "activeSessions"这个vector中都会增加一条记录。每当session超时,valueUnbound被触发,在这个vector中删去将要被超时的session.
#Wp?%D9Kry 6H6tnR Vy8b Qu-AF}y

"Yw hL l8z| public void login() &TQ8Pb_Zb)X5r

d/\ j+m1u.s2@E throws ACLException,SQLException,IOException &{vI@$Y&X

s*tUjqjEL9O&n {
7``&eiH
Sq2X^X;~Bv?  /* get JDBC User Class */
Tf:K NL2S
\9}S,J.a K3c_C  if (user != null)
/{ _;Q!E6CV
jEODLQH  {
V#ZKs}$d2QSB q9wR2xTP,g
logout(); bc9UR}DeY h Um
5k9P9Vq+?D/o
 } qEK3{%\"CN:\ T0y
l6CKA6vwbY
 {
Z3r,@;\SJ:J+B_ v;[H|m [#E3h _
// if session time out, or user didn't login, save the target url temporary.
} Q&B bV3m
:[9B@"YJ'H*[ (f]4s:wW#ma`
JDBCUserFactory uf = new JDBCUserFactory();
Zvnwna!a g )c.V:]u/}QhVx0@

U;O`hnw*x,E~N if ( (this.request.getParameter("userID")==null) || (this.request.getParameter("password")==null) ) h0J.e2N?4I2f1|
tV'} A\&c
{
$SHp(Ps%Mo)Ph 9k"T_;a#ss+]bG!LM)@
 throw new ACLException("Please input a valid userName and password.");
}0y&PD,pRv@*X3H)JZ:A &l1H"Ts2?:]
} 1o ?x)K9hL

r$n[9M.kO'W
Zi:w7d7CZ z#oY JDBCUser user = (JDBCUser) uf.UserLogin(
j l~cvJ-r1ga7G@ ny`OOz
 this.request.getParameter("userID"),
ms0g w.x)UF%M X
}r.i M3T^  this.request.getParameter("password") );
y4DQ)D-S U b["g0` ~ y9s@I)i*o{J
 user.touchLoginTime(); !FDkU4Z:fV@9U

1i)u#r6qA:RLi  this.session.setAttribute("user",user);
mv)T5Fp vY"^5Y
T)iP Z y7Ey/_  this.session.setAttribute("BindingNotify",new HttpSessionBinding(application)); M g:l2b[&z x
5x7?4y3XC"T1M}
} 'b:p+o4M)c
ISu*gq
 }
HHt0`5RH A
M:M yGgU:u-A A"~-JO-@*h
Login的时候,把User和这个BindingNotofy目的的类都加入到session中去。logout的时候,就要主动在activeSessions这个vector中删去这个session. +nT;P[$F

Y;d~+u{M7wy-b mf8`k'R3L
public void logout()
w^&D R e X#_W;CA.|?L~
throws SQLException,ACLException
dm2QS1JTu+yyE %p4tQ$CA]'B
{ W7iDv W'`
6s?@)K)d
 if (this.user == null && this.session.getAttribute("user")==null)
4t2jP%H3N7k3L %L$e(M n;M9j?0o
 { I%Dq6N4K4k5fWq2^
k5Mg}F:J P&n
return; H(C,MX9l/iJj x h

1|gYyK  }
-xd0F9B;O
_%^ yGL X%Q9tQ9LB1q
 Vector activeSessions = (Vector) this.application.getAttribute("activeSessions"); f"M7eXR/g%O
/v3~ h5Pbn1C W
 if (activeSessions != null) *QW6z+n i7| o"t

_6F8EBG#a2_-V#q  {
k9k7Zd5A_
n#oU]D z activeSessions.remove(this.session);
_!l%nuF :llJ4s8V
application.setAttribute("activeSessions",activeSessions); $W z#Y(g?
/q'}L&yy6R6R h%x
 }
&tmum`.]!~ A&S
f BJr:|A #}Vi%v+y`` ]
 java.util.Enumeration e = this.session.getAttributeNames(); $\_#m5qy"lbj+F(f Et
}$aij\
q:U'|z_~+iN
 while (e.hasMoreElements()) &y:l2HyS|b
{7k`t.tRAF
 { R/@8K]U2[EN2H
I@X@ s
String s = (String)e.nextElement(); K*he4W"?i J*Hq j H8J

'HE"tb2B7s.\Z#G this.session.removeAttribute(s); ^5^/L6K8s?p r

kE%bgbu&v0{~  }
v,v,G(?FN q,g2X mY4n?
 this.user.touchLogoutTime(); B'N!\ NK N$@&vVM

!L8p(Eu#o:[[}D  this.user = null;
:k{QP?{
g:nd.X.c~-p }
u[#a*P(IKk\P
!z\ u@R ~RIyN cy+my#Ty O1i
这两个函数位于一个HttpSessionManager类中.这个类引用了jsp里面的application全局对象。这个类的其他代码和本文无关且相当长,我就不贴出来了。 $n*d]pf)`k
O L*_'o&ef
下面来看看JSP里面怎么用。
#g7P:i'F9J;D:`3Ts $?'u'c)Hr4`z \1Hb
L:^0p;DF/sa(E
假设一个登录用的表单被提交到doLogin.jsp, 表单中包含UserName和password域。节选部分片段: g6RO} X

_}G)P jhw!T t I$w;X W9n
<%
Q Y1t|{:M5@5t{7c/W !n&L!c tB
HttpSessionManager hsm = new HttpSessionManager(application,request,response); ?+d(AC6| |fa
'Di LO] r
try nL$k*_3f7I+X
)@ ST9y(|{ C
{
,p} Kf Z;rMH {9m 7qe0Y4~nI
 hsm.login();
:Mbz0w!^h)P +o1b`p XD"N[
}
[;h#n3N4y+xLi u R7kF M9h
catch ( UserNotFoundException e)
N#|oE E :jR8F.BN5X0b(R
{ .a'?ca,Cw}Wp
L1N tRJs IA6t
 response.sendRedirect("InsufficientPrivilege.jsp?detail=User%20does%20not%20exist."); |G%M&KX
lDMW IP2C
 return; ;tk0anl2]!\
-L G m"X0J@"kVN
}
U'o\vj#HE.w
dS8}(^5G,n2s({qR catch ( InvalidPasswordException e2)
'm5?3r0FIar
JdcB$f"tq%M { 1|!AL5A7u7d*c

+y.i.[ F u]9y  response.sendRedirect("InsufficientPrivilege.jsp?detail=Invalid%20Password");
c4tGt6T.J.@*cR 2z1?7`/u:` ci
 return;
r7?'Z]:L [\/l%U7~
%|&vXB6VE W }
XNwD!o
+|Qt+g$`b(x9P ` catch ( Exception e3) 0m.h.Y9t"D:kY?
l"{OyZMlS
{ :x$?6M/r J
Ct.u%[q"I5p!O
 %> Error:<%=e3.toString() %><br> r7E2{{R:z"Q@

"f U/q_Ni1u7C&G  Press <a href="login.jsp">Here</a> to relogin.
7\N8OU r2^e)u r*I `8M3j+L
 <% return;
nR1EY(}t&Y&l u9r$LF+QK S6h(^ w
}
*}U4N3g'y1BUH9ZV jqS\r P*n[
response.sendRedirect("index.jsp"); 0H,UQ I8{
"fv0B7ie1{ ~ o
%>
'v3O)Z DO:n:G A#~\6f5]#Tm9J+Kb
-_)N;Ur:P x D)Xpb2]
再来看看现在我们怎么得到一个当前在线的用户列表。
'u Ao-CPOW1T"O a!q&W;p%_6y8AJ
c uH4D&]Wl \
<body bgcolor="#FFFFFF"> pQy q'D

"u~xlzN9Ar <table cellspacing="0" cellpadding="0" width="100%"> yc y7h+`~Y

}S^}B9w`? 1LCL(B;z4w1N
<tr > &NgfZ Q

s4T&KOW5D9_DO <td style="width:24px">SessionId :G1dq/p$c3FO AAq
K5Un?(?e,Fle2q
</td> 7PT;XS6}9S
0V _`7n7]#OJh5Rd
<td style="width:80px" >User a S|/acfO0f

5[NfM1\,|i </td>
.o8hcfaJ3A"u :j7xl^,Q"O&``t
<td style="width:80px" >Login Time
Oy;e?]O1reU 8d!n%b"tk4i
</td>
W.T!|6N] Tp n K+A5|5O)\~'s KK
<td style="width:80px" >Last Access Time ,h:]7gBO&Px

X y Rxki` </td> x~v(g^
:E X4JWii0v
</tr>
~[l u5T?'K&ke 1L)Bw q3t~/u G
<% ^Rs(d Oa

C-}~ x:IlU Vector activeSessions = (Vector) application.getAttribute("activeSessions");
w1G7u aVg%BL XEGMn"S
if (activeSessions == null)
&~Cj$R}fUH
)zv(g^"b#n:Z8f5Q8X4? { &O-`0i`2n7ApVJ

+[!sd*f3n(po*u  activeSessions = new Vector(); bPU$R,M:n

]3\8[xd F*qn!r  application.setAttribute("activeSessions",activeSessions); -[4p;M%U\(M
AYZt P2f%dR(S
}
'p6j}:D+b)E"S
[K @d i5h1@7B
qT)Bg2y8e;@%x"Q:{%[ Iterator it = activeSessions.iterator();
"XfD2P'^%e
5q L%a)uF)_ while (it.hasNext()) $Q6k8WLk\

6fd4\o/^o$o'Z1yw8P!Z {
m+Q` |2` ^Cu1d3HO2@
 HttpSession sess = (HttpSession)it.next();
Fj sWK:Hk
,H7M(ljG  JDBCUser sessionUser = (JDBCUser)sess.getAttribute("user"); cQ X$akI5S;T x

B `w8Z`xz&@"d&E  String userId = (sessionUser!=null)?sessionUser.getUserID():"None"; yU-w0f `u%j9@ l

:Y K]r%jv'w0V %> t]4N$L_W s$Mp

(Q;_S `]%B]H7W-sL <tr> IF1K-gv\!_ {

D2x8^ h6f0?Qs <td nowrap=''><%= sess.getId() %></td> 7OX.|tT;^C*{(H"c&p
x klmR6}&SH
<td nowrap=''><%= userId %></td>
n9qbG ^c]Wg4\ ij&k P1O-Xe7j
<td nowrap=''> %hQdu#a)LGu6Sl
Q7@ b ]&iT
<%= BeaconDate.getInstance( new Java.util.Date(sess.getCreationTime())).getDateTimeString()%></td>
M$O&uqNf1gp
D [},yu S.It <td class="<%= stl %>3" nowrap=''>
0?u%x|1e1K lNj2M _ "p/Q;S8z1xPn+^
<%= BeaconDate.getInstance( new java.util.Date(sess.getLastAccessedTime())).getDateTimeString()%></td>
N8ok6A+k !_ LzvR#k9V
</tr>
,s1w$~,^c?p S/]1V]{){
<%
FgZE-]Y Y _C;zq#y$XM
}
7h_"pdLA&["D r
yaF.o3@*QO %> 9nO*`,l.L+]&N"\&e
:l$K Dd(Z Cb Y @
</table>
t*|6H}?*K(P7g
"j$H"@+YB </body>
?c j yDzU/}/` V0Qvhf(Zx,fJ ]

;dK lo }3A$Es C+C/lZJo#F 以上的代码从application中取出activeSessions,并且显示出具体的时间。其中BeaconDate类假设为格式化时间的类。
){/^b}Hz K9m
#\ p hG-YI#F1G t&F%D%w1M(] B
这样,我们得到了一个察看在线用户的列表的框架。至于在线用户列表分页等功能,与本文无关,不予讨论。 T0wz)z QN$I

;|0W qs:}r0mc (LAn6a+tv
这是一个非刷新模型的例子,依赖于session的超时机制。我的同事sonymusic指出很多时候由于各个厂商思想的不同,这有可能是不可信赖的。考虑到这种需求,需要在每个叶面刷新的时候都判断当前用户距离上次使用的时间是否超过某一个预定时间值。这实质上就是自己实现session超时。如果需要实现刷新模型,就必须使用这种每个叶面进行刷新判断的方法。

页: [1]

Powered by 我爱电脑技术论坛 Archiver 6.1.0  © 2001-2007 本SEO插件由网络人站长论坛出品