import javax.servlet.;import javax.servlet.annotation.WebListener;import javax.servlet.http.HttpSessionAttributeListener;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;import javax.servlet.http.HttpSessionBindingEvent;@WebListener()public class MyRequestListener implements ServletRequestListener{ private ServletContext sc; private Integer count; @Override //要求被初始化 Request public void requestInitialized(ServletRequestEvent sre) { //获取全局域 sc = sre.getServletContext(); //将count从全局域中获取出来 count = (Integer) sc.getAttribute("count"); System.out.println(count); count++; System.out.println(count); sc.setAttribute("count",count); }}
import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.HttpSessionAttributeListener;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;import javax.servlet.http.HttpSessionBindingEvent;@WebListener()public class MyServletContextListener implements ServletContextListener{ private ServletContext sc; @Override //Application被初始化的时候创建 public void contextInitialized(ServletContextEvent sce) { Integer count = 0; //获取全局域 sc = sce.getServletContext(); //将count放入到全局域中 sc.setAttribute("count",count); }}
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html> <head> <title>$Title$</title> </head> <body> <center><h1>You are the ${applicationScope.count} customer to visit. </h1></center> </body></html>
这种缺点地做法导致的是每刷新一次页面 就会导致count进行累加操作,终极产生缺点的在线人数,以是此时想到不应该监听Request域,而该当监听Session域。
监听Session域在第二次监听Session域之后,创造每次刷新页面后不改变count但是在启动不同的浏览器后count++会实现,但是,这样做并不是我们要统计的在线人数,以是此种做法缺点。由于代码只是将原来写在Request监听器中的代码转移到Session监听器中,以是其他没变的代码将不重复。
import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.HttpSessionAttributeListener;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;import javax.servlet.http.HttpSessionBindingEvent;@WebListener()public class MySessionListener implements HttpSessionListener{ private ServletContext sc; private Integer count; @Override //当对话产生时激活此方法 public void sessionCreated(HttpSessionEvent se) { sc = se.getSession().getServletContext(); count = (Integer) sc.getAttribute("count"); count++; sc.setAttribute("count",count); }}
这时我们创造对付在线人数的统计,不是网页访问的次数,也不是浏览器打开的个数,对需求的理解的缺点理解。以是精确的做法是统计其IP的数量,这样的话,不管你在一台电脑上开启多少客户端,都会只有一个。
精确的统计方法
统计其IP的数量,将IP的数量作为当前的在线人数,那么如何统计IP的数量呢?这样将会导出以下问题:
如何获取用户的IP?IP将如何存储?如何判断IP之前已经存在?现在来办理这些问题:
只能从要求中获取通过2、3问题,我们想到了凑集(List),由于凑集不仅可以存储任何字符串,还可以通过遍历来判断之前是否有重复的IP涌现。到了这里又冒出来一个问题凑集(List)放到哪个域里呢?
ServletContext域
import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.HttpSessionAttributeListener;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;import javax.servlet.http.HttpSessionBindingEvent;import java.util.ArrayList;import java.util.List;@WebListener()public class MyServletContextListener implements ServletContextListener{ private ServletContext sc; @Override //Application被初始化的时候创建 public void contextInitialized(ServletContextEvent sce) { //创建一个链表来存储IP List<String> ips = new ArrayList<>(); sc = sce.getServletContext(); //将创建好的链表工具,放到Application域中 sc.setAttribute("ips",ips); }}
由于IP只能在Request域中获取,以是遍历判断在Request域中进行。
import javax.servlet.;import javax.servlet.annotation.WebListener;import javax.servlet.http.;import java.util.List;@WebListener()public class MyRequestListener implements ServletRequestListener{ private HttpServletRequest sr; private String clientIp; private ServletContext sc; private List<String> ips; private HttpSession session; @Override //要求被初始化 Request public void requestInitialized(ServletRequestEvent sre) { //从要求域中获取IP sr = (HttpServletRequest) sre.getServletRequest(); clientIp = sr.getRemoteAddr(); session = sr.getSession(); session.setAttribute("clientIp",clientIp); //测试 // System.out.println("clientIp = "+ clientIp); //获取Application域中的List sc = sre.getServletContext(); ips = (List<String>) sc.getAttribute("ips"); //遍历ips for (String ip : ips) { if (clientIp.equals(ip)) return; } ips.add(clientIp); sc.setAttribute("ips",ips); }}
由于要统计在线人数,以是要设置退出按钮,点击退出按钮之后,由于要从List域中移除,以是利用Session域监听器来判断session回话的关闭
import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.;import java.util.List;@WebListener()public class MySessionListener implements HttpSessionListener{ private ServletContext sc; private List<String> ips; private HttpSession session; private Object clientIp; @Override public void sessionDestroyed(HttpSessionEvent se) { sc = se.getSession().getServletContext(); ips = (List<String>) sc.getAttribute("ips"); session = se.getSession(); clientIp = session.getAttribute("clientIp"); //删除ip,如何获取IP,但是不可以从session获取到IP //由于Session获取不到Request //一个Session包含多个Request //一个Request只对应一个Session 以是获取不到,这时只能先从Request域中获取到的ips,放置到Session域 //然后从Session 域中读取 ips.remove(clientIp); // session一失落效就立时将此IP从链表中移除是缺点的 //该当看此IP是否有其余的回话存在,如果有的话不能删除 }}
此处代码是页面点击关闭后,激活的退出方法
import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;import java.io.IOException;@WebServlet(name = "LogoutServlet",urlPatterns = "/logoutServlet")public class LogoutServlet extends HttpServlet { private HttpSession session; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //从域中获取一个session,设置为false 如果域中存在一个session,则直接获取,如果不存在,则返回一个空的session session = request.getSession(false); if (session != null){ //使session失落效 session.invalidate(); //失落效后,须要进行的操作,List链表中须要减去,用到了Session域监听器 } } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); }}
在jsp页面进行读取的时候,由于ips因此List链表的形式存在的,以是要想判断当前在线人数,以是必须要判断链表的长度,所以是applicationScope.ips.size()
<%@ page contentType="text/html;charset=UTF-8" language="java" %><html> <head> <title>$Title$</title> </head> <body> <center><h1>You are the ${applicationScope.ips.size()} customer to visit. </h1><br> <h3><a href="${pageContext.request.contextPath}/logoutServlet"> 安全退出 </a></h3> </center> </body></html>
好了?,这时候,程序写完了,如何判断呢?
此时,我们的程序是支配在本地的Tomcat上的,对付本台电脑,只有一个IP,如何实现多个IP呢?实在啊我们的电脑可以有三个IP,在访问做事器的时候,做事器的IP多写几个,相称于本机的IP多出来几个。是哪三个IP呢?
1、默认clientIp : 0:0:0:0:0:0:0:1
2、127.0.0.1
这时大家可能会问127.0.0.1和localhost有什么差异呢,其实在这里要区分三个观点:
localhost、127.0.0.1 和 本机IP之间的差异:
localhost即是127.0.0.1,不过localhost是域名,127.0.0.1是IP地址。localhost和127.0.0.1不须要联网,都是本机访问。本机IP须要联网,本机IP是本机或外部访问, 本机 IP 便是本机对外放开访问的IP地址,这个网址就 是与物理网卡绑定的IP地址。3、IPv4地址:192.168.1.110
这样就很完美的实现了本地三个IP的测试。
写到这里,彷佛已经可以大略的测试当前在线人数,大概仔细的人会创造在Session域被销毁的方法中的注释中创造一些猫腻。大家可以仔细想想,如果客户端用不同的浏览器,相同的IP去访问呢?点击退出后,会不会涌现缺点情形呢?答案是会的。演示结果如下图
最完美的代码
以是在点击退出登录的按钮之后,不可以直接将IP移除,要判断有没有其余的回话存在,如果有其余的回话存在,此IP是不可以删掉的,问题由此变的繁芜了,由于还要统计此IP所发出的会话有多少。
整体思路:
在全局域中,将不是直接将iP存放在List的链表中,而因此一个Map的形式存在,Map的键为String类型,Key为List类型,List中存放的是当前IP所引发的会话工具,这样就可以统计,一个IP触发的sessions有多少个。
通过调用Map的get方法,将当前IP最为参数,将可以获取到他所引发的会话凑集。但是,此凑集可能为空,由于有可能当前IP一次也没有访问此页面,以是在List为空的时候好要创建一个ArrayList来存放sessions,然后将变革后的List重新写回到Map,再将变革后的Map写回到全局域中 。这样创建过程基本完成。
然后考虑销毁过程,IP还需方法放到Session域中,当session被销毁的时候,该当把当前Session从List 中删除,但是Map中此sessions对应的IP可是不能直接删,要判断List中的sessions的个数(Entry工具),个数为1的时候才可以删除,不然就不可以删除。
以是,要将当前IP通过Request域存放到当前Session域中,
然后,要考虑的问题是,每次刷新页面后sessions的个数会增加,这是缺点的,缘故原由是什么?
答案是,由于在存放sessions的时候,创建数组直接进行的添加,这样的话,每次一刷新页面,就会导致sessions的添加,以是在此之前该当判断,sessions中是否有此session,有的话直接跳出。
这样添加就没问题了
Servlet域中添加Map在Map中,须要利用键值对的办法,Key为IP,Value为List,那么List中存放什么呢?存放的是此IP发出的所有回话的HttpSession的工具,以是List的泛型是HttpSession。
要求,在要求中,由于将当前Session 工具存放到List中, List在Map中,Map在全局域中,以是首先得从全局域获取到Map,然后,从Map中获取由当前IP所发出的所有Session的组成的List,判断当前的List是否为NULL,若为NULL,则创建List,否则,将当前SessioncurrentSession放入List中。
import javax.servlet.;import javax.servlet.annotation.WebListener;import javax.servlet.http.;import java.util.ArrayList;import java.util.List;import java.util.Map;@WebListener()public class MyRequestListener implements ServletRequestListener{ private HttpServletRequest sr; private String clientIp; private ServletContext sc; private List<String> ips; private HttpSession currentSession; private Map<String,List<HttpSession>> map; private List<HttpSession> sessions; @Override //要求被初始化 Request public void requestInitialized(ServletRequestEvent sre) { //从要求域中获取IP sr = (HttpServletRequest) sre.getServletRequest(); clientIp = sr.getRemoteAddr(); currentSession = sr.getSession(); //将当前Session 工具存放到List中, List在Map中,Map在全局域中, sc = sre.getServletContext(); map = (Map<String, List<HttpSession>>) sc.getAttribute("map"); //从Map中获取由当前IP所发出的所有Session的组成的List sessions = map.get(clientIp); //判断当前的List是否为NULL,若为NULL,则创建List,否则,将当前Session放入List if (sessions == null){ sessions = new ArrayList<>(); }// 遍历List的session 工具,若有则不添加,若没有则添加 for (HttpSession session : sessions) { if (session == currentSession) return; } sessions.add(currentSession); //将变革过的List重新写回到Map map.put(clientIp,sessions); //再将变革的Map写回到全局域中 sc.setAttribute("map",map); //将当前IP放入到当前Session currentSession.setAttribute("clientIp",clientIp); }}
ServletContext
这里将不该用ips了,以是将其删除
import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;@WebListener()public class MyServletContextListener implements ServletContextListener{ private ServletContext sc; @Override //Application被初始化的时候创建 public void contextInitialized(ServletContextEvent sce) { //创建一个Map,key为IP,value为该IP上所发出的会话的工具 Map<String,List<HttpSession>> map = new HashMap<>(); sc = sce.getServletContext(); //将map放到全局域中 sc.setAttribute("map",map); }}
Session监听器
接下来阐发Session的删除事情,获取当前Session工具,这里有之前通报过来的IP,在进行删除操作的时候,要把稳此处,删除的是List中的sessions,删除之后,还要判断其IP的是否要删除,如果List中没有该元素,则解释当前IP所发出的会话全部关闭,就可以从map中将当前IP对应的Entry工具删除,否则,当前IP所发出的会话任存在,那么利用put方法将变革过的List写回到map。
import javax.servlet.ServletContext;import javax.servlet.ServletContextEvent;import javax.servlet.ServletContextListener;import javax.servlet.annotation.WebListener;import javax.servlet.http.;import java.util.List;import java.util.Map;@WebListener()public class MySessionListener implements HttpSessionListener{ private ServletContext sc; private List<String> ips; private HttpSession currentSession; private String clientIp; private Map<String,List<HttpSession>> map; private List<HttpSession> sessions; @Override public void sessionDestroyed(HttpSessionEvent se) { sc = se.getSession().getServletContext(); currentSession = se.getSession(); clientIp = (String) currentSession.getAttribute("clientIp"); map = (Map<String, List<HttpSession>>) sc.getAttribute("map"); //从Map中获取List sessions = map.get(clientIp); //从List中删除当前Session工具 sessions.remove(currentSession); //如果List中没有该元素,则解释当前IP所发出的会话全部关闭,就可以从map中 //将当前IP对应的Entry工具删除 //若List中仍有元素,当前IP所发出的会话任存在,那么将变革过的List写回到map if (sessions.size() == 0){ map.remove(clientIp); }else { map.put(clientIp,sessions); } sc.setAttribute("map",map); }}
由于处理的退出的页面/logoutServlet不须要做任何不同的处理,以是这里将不再重复。
由于在jsp用到了JSP标准库,以是到导两个包。
<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %><html> <head> <title>$Title$</title> </head> <body> <center><h1>You are the ${applicationScope.map.size()} customer to visit. </h1><br> <h3><a href="${pageContext.request.contextPath}/logoutServlet"> 安全退出 </a><br></h3> <h2> <c:forEach items="${map}" var="entry"> ${entry.key }=${entry.value.size()}<br> </c:forEach> </h2> </center> </body></html>
末了 测试成功,这便是一个完美的统计当前用户的在线人数。
来源:https://mp.weixin.qq.com/s/gM-lyh_9FeFKRRAJlPY83g