[100分讨论] 如何判断一个用户是否在线!? 在每个用户登录以后我都把他的用户信息放到SESSION中了,如何得到在线用户的列表呢? 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 从session里读出来就是了,最好用bean。 用session比较好吧,加个online 你不还是要判断他是否离线了?不是每个用户都会来注销自己的登陆。 在 APPLICATION 里建个 用户数组, 每个用户登陆后 将他的信息 保存到SESSION 和 该数组里,如果SESSION死掉 就在书组 里找到该信息 并删除 我觉得用个专门的bean比较好。用户登陆的时候在bean里面添加相应信息,session失效的时候从bean里面删除。想显示在线用户列表的话就直接从bean里面读取。 同意楼上的,需要考虑bean的生命周期。也可以考虑用servlet:)推荐 同意楼上的说法以前在论坛中用过的一个方法public static void checkUserStats(HttpServletRequest request,HttpServletResponse response,String stats){ String userName=GCookie.getCookieValue(request,"UJBBUName",""); String userPassword=GCookie.getCookieValue(request,"UJBBUPSW",""); String ip=request.getRemoteAddr(); String[] addr=IPLocalizer.search(request); String comeFrom=""; for(int i=0;i<addr.length;i++) comeFrom+=addr[i]; String actCome=request.getHeader("X_FORWARDED_FOR"); long statUserID=Long.parseLong(StringUtils.replace(ip,".","")); boolean onlineSign=false; String browser=request.getHeader("User-Agent"); ResultSet rs; try{ String sql=""; DBConnect dbc=new DBConnect(); try{ sql="select * from bbs_online where id=?"; dbc.prepareStatement(sql); dbc.setLong(1,statUserID); //dbc.setBytes(2,userName.getBytes("GBK")); rs=dbc.executeQuery(); if(rs.next()) onlineSign=true; else onlineSign=false; rs.close(); } catch(Exception e){ onlineSign=false; } if("".equals(userName)){ if(!onlineSign){ sql="insert into bbs_online (id,username,userclass,ip,startime,lastimebk,browser,stats,actforip,ComeFrom,actCome) values("+statUserID+",?,?,?,?,?,?,?,?,?,?)"; dbc.prepareStatement(sql); //dbc.setLong(1,statUserID); dbc.setString(1,"guest"); dbc.setString(2,"客人".getBytes("GBK").toString()); dbc.setString(3,ip.getBytes("GBK").toString()); dbc.setString(4,Format.getDateTime()); dbc.setString(5,Format.getDateTime()); dbc.setString(6,browser); dbc.setString(7,stats.getBytes("GBK").toString()); if(actCome==null) { dbc.setString(8,"null"); dbc.setString(10,"null"); } else { dbc.setString(8,actCome); dbc.setString(10,actCome); } dbc.setString(9,comeFrom.getBytes("GBK").toString()); dbc.executeUpdate(); dbc.clearParameters(); } else{ sql="update bbs_online set lastimebk=?,lastime=?,ComeFrom=?,actCome=?,stats=? where id="+statUserID; System.out.println("1"+Format.getDateTime()+"2"+Format.getStrDateTime()+"3"+comeFrom.getBytes("GBK")+"4"+actCome+"5"+stats.getBytes("GBK")+"kill"); dbc.prepareStatement(sql); dbc.setString(1,Format.getDateTime()); dbc.setString(2,Format.getStrDateTime()); dbc.setString(3,"'"+comeFrom.getBytes("GBK").toString()+"'"); if(actCome==null) { dbc.setString(4,"null"); } else{ dbc.setString(4,actCome); } dbc.setString(5,"'"+stats.getBytes("GBK").toString()+"'"); //dbc.setLong(6,statUserID); dbc.executeUpdate(); sql="update bbs_online set ComeFrom=? where id="+statUserID; dbc.prepareStatement(sql); dbc.setString(1,"'"+comeFrom.getBytes("GBK").toString()+"'"); dbc.executeUpdate(); dbc.clearParameters(); } } else{ sql="select * from bbs_online where ID="+statUserID+" or username=?"; dbc.prepareStatement(sql); //dbc.setLong(1,statUserID); dbc.setBytes(1,userName.getBytes("GBK")); rs=dbc.executeQuery(); if(rs.next()) onlineSign=true; else onlineSign=false; dbc.clearParameters(); try{ User theUser=new User(userName,userPassword,4); if(onlineSign){ sql="update bbs_online set id="+statUserID+",userName=?,userClass=?,lastimebk=?,lastime=?,ComeFrom=?,actCome=?,stats=? where id=? or username=?"; dbc.prepareStatement(sql); //dbc.setLong(1,statUserID); dbc.setBytes(1,userName.getBytes("GBK")); dbc.setBytes(2,getUserClass(theUser.getUserClass()).getBytes("GBK")); dbc.setString(3,Format.getDateTime()); dbc.setString(4,Format.getStrDateTime()); dbc.setBytes(5,comeFrom.getBytes("GBK")); dbc.setString(6,actCome); dbc.setBytes(7,stats.getBytes("GBK")); dbc.setLong(8,statUserID); dbc.setBytes(9,userName.getBytes("GBK")); dbc.executeUpdate(); dbc.clearParameters(); } else{ sql="insert into bbs_online(id,username,userclass,ip,startime,lastimebk,browser,stats,actforip,ComeFrom,actCome) values("+statUserID+",?,?,?,?,?,?,?,?,?,?)"; dbc.prepareStatement(sql); //dbc.setLong(1,statUserID);//statUserID); dbc.setString(1,userName.getBytes("GBK").toString()); dbc.setString(2,getUserClass(theUser.getUserClass()).getBytes("GBK").toString()); dbc.setString(3,ip); dbc.setString(4,Format.getDateTime()); dbc.setString(5,Format.getStrDateTime()); dbc.setString(6,browser); dbc.setString(7,stats.getBytes("GBK").toString()); System.out.println("tttt"+actCome); if(actCome==null){ dbc.setString(8,"null"); dbc.setString(10,"null"); } else{ dbc.setString(8,actCome); dbc.setString(10,actCome); } dbc.setString(9,comeFrom.getBytes("GBK").toString()); dbc.executeUpdate(); dbc.clearParameters(); } } catch(Exception e){ if(!onlineSign){ sql="insert into bbs_online (id,username,userclass,ip,startime,lastimebk,browser,stats,actforip,ComeFrom,actCome) values("+statUserID+",?,?,?,?,?,?,?,?,?,?)"; dbc.prepareStatement(sql); //dbc.setLong(1,statUserID); dbc.setString(1,"guest"); dbc.setString(2,"客人".getBytes("GBK").toString()); dbc.setString(3,ip.getBytes("GBK").toString()); dbc.setString(4,Format.getDateTime()); dbc.setString(5,Format.getDateTime()); dbc.setString(6,browser); dbc.setString(7,stats.getBytes("GBK").toString()); dbc.setString(8,actCome); dbc.setString(9,comeFrom.getBytes("GBK").toString()); dbc.setString(10,actCome); dbc.executeUpdate(); dbc.clearParameters(); } else{ sql="update bbs_online set lastimebk=?,lastime=?,ComeFrom=?,actCome=?,stats=? where id="+statUserID; System.out.println("1"+Format.getDateTime()+"2"+Format.getStrDateTime()+"3"+comeFrom.getBytes("GBK")+"4"+actCome+"5"+stats.getBytes("GBK")+"kill"); dbc.prepareStatement(sql); dbc.setString(1,Format.getDateTime()); dbc.setString(2,Format.getStrDateTime()); dbc.setString(3,"'"+comeFrom.getBytes("GBK").toString()+"'"); if(actCome==null) { dbc.setString(4,"null"); } else{ dbc.setString(4,actCome); } dbc.setString(5,"'"+stats.getBytes("GBK").toString()+"'"); //dbc.setLong(6,statUserID); dbc.executeUpdate(); dbc.clearParameters(); } } } sql="delete from bbs_online where now()>date_add(lastimebk,interval 20 minute)"; dbc.executeUpdate(sql); sql="select Maxonline from config"; rs=dbc.executeQuery(sql); rs.next(); int oldMaxOnLine=rs.getInt(1); sql="select count(*) from bbs_online"; ResultSet tmprs=dbc.executeQuery(sql); tmprs.next(); int newMaxOnLine=tmprs.getInt(1); if(newMaxOnLine>oldMaxOnLine){ sql="update config set Maxonline="+newMaxOnLine+",MaxonlineDate=now()"; dbc.executeUpdate(sql); ForumPropertiesManager.resetManager(); } dbc.close(); } catch(Exception e){ e.printStackTrace(); } } 由于本人的水平实在有限,拜托yzwkk(流星飞雨)能不能帮我给您的程序加一点注释呢? 在 APPLICATION 里建个 用户数组, 每个用户登陆后 将他的信息 保存到SESSION 和 该数组里,如果SESSION死掉 就在书组 里找到该信息 并删除我觉得用个专门的bean比较好。用户登陆的时候在bean里面添加相应信息,session失效的时候从bean里面删除。想显示在线用户列表的话就直接从bean里面读取。session是如何死掉和失效的呢? 超时,直接关闭IE,网络断线,客户端死机,这些情况session会自动死掉(失效)?是立即死掉(失效)还是等待一段时间? package sys.user;import util.db.DB;import java.sql.*;import util.string.StringUtil;import sun.jdbc.rowset.CachedRowSet;import javax.servlet.http.*;import java.util.ArrayList;import sys.all.SysLockIp;public class UserSession implements HttpSessionBindingListener { private String userName; private String userId; private String userType = ""; private String gotoUrl; private boolean isLogin; private String password; public static ArrayList userList = new ArrayList(); public void valueBound(HttpSessionBindingEvent event) { boolean dup = false; for (int i = 0; i < this.userList.size(); i++) { UserSession u = (UserSession)this.userList.get(i); if (u != null && u.getUserName() != null && u.getUserId().equals(this.getUserId())) { // forbid add a duplicate user dup = true; } } if (!dup) { this.userList.add(this); } System.out.println(this.userId + " added into userlist"); } public void valueUnbound(HttpSessionBindingEvent event) { this.userList.remove(this); System.out.println(this.userId + " remove from userlist"); } } session是如何死掉和失效的呢? 超时,直接关闭IE,网络断线,客户端死机,这些情况session会自动死掉(失效)?是立即死掉(失效)还是等待一段时间? jsp跟asp不一样,asp里面可以在global.asa里的session_OnStart和session_OnEnd的接口函数里实现在线用户的统计和跟踪功能,jsp没有直接的实现。但是jsp提供了另外一种机制,可以实现HttpSessionListener接口,实现对Session的跟踪(类似的还有HttpSessionAttributesListener和HttpContextListener用于跟踪Session的Attribute变化和Application的变化)。以下是我的一个简单的在线用户统计的实例(楼主的问题不太一样,但核心问题都是一样的)://MySessionListener.javapackage test;import javax.servlet.http.HttpSessionEvent;import javax.servlet.http.HttpSessionListener;/** * @author Jass */public class MySessionListener implements HttpSessionListener { private static int onlineUsers = 0; /* (non-Javadoc) * @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent) */ public void sessionCreated(HttpSessionEvent httpSessionEvent) { // TODO Auto-generated method stub onlineUsers++; //String sId = httpSessionEvent.getSession().getId(); //System.out.println("New Session created!Session ID:" + sId); //System.out.println(onlineUsers + " users online!"); } /* (non-Javadoc) * @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent) */ public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { // TODO Auto-generated method stub onlineUsers--; //String sId = httpSessionEvent.getSession().getId(); //System.out.println("Session Destroyed!Session ID:" + sId); //System.out.println(onlineUsers + " users online!"); }} 有了这两个函数,类似的功能都可以实现。 再在web.xml的root下加入以下元素就OK了: <listener> <listener-class>test.MySessionListener</listener-class> </listener> 首先、十分感谢大家的热心帮助! gks_cn(981530) ,Jass(Jass) ,你们两位的代码我看懂了,也是解决我问题的思想,其他人的想法也很好,但不够成熟、简洁。但是、请问gks_cb(981530)您的代码中,在ArrayList里面保存了一个用户的列表,我可以通过判断列表中是否有该用户来确定用户是否在线,这个类是一个servlet,我在后台的bean中如何获取这个列表呢?这点我还是有点不大明白,请指教! 用户列表应该是个static的变量,也就是可以全局访问的,放在哪个类里都是一个效果!直接访问就行了呀。你也可以单独建一个Bean保存这个变量和其他类似变量,只要保证是static的就行了(推荐)。 顺便说一句,listener只有在servlet2.3以上才能支持,web.xml的dtd用http://java.sun.com/dtd/web-app_2_3.dtd 页面问题请教 求用NetBeans工具和JSF框架的一个小程序 请高手解决一下在MyEcplise6.0中Jquery的问题 JspSmartUpload上传图片如何与图片属性在一个页面上实现 请问做 JSP 开发用哪个 Linux 版本开发比较合适? 请问JSP中如何获得jdk,JSP和数据库驱动的版本? 我创建了一个web工程 在myeclipse中怎样进行断点调试 如何设置 求教??? 一个关于taglib的问题 mysql的jdbc使用问题 新手求教: TOMCAT连接池失效?大家进来帮帮忙~ 用jsp编写邮箱注册和登陆????
如果SESSION死掉 就在书组 里找到该信息 并删除
以前在论坛中用过的一个方法
public static void checkUserStats(HttpServletRequest request,HttpServletResponse response,String stats){
String userName=GCookie.getCookieValue(request,"UJBBUName","");
String userPassword=GCookie.getCookieValue(request,"UJBBUPSW","");
String ip=request.getRemoteAddr();
String[] addr=IPLocalizer.search(request);
String comeFrom="";
for(int i=0;i<addr.length;i++)
comeFrom+=addr[i];
String actCome=request.getHeader("X_FORWARDED_FOR");
long statUserID=Long.parseLong(StringUtils.replace(ip,".",""));
boolean onlineSign=false;
String browser=request.getHeader("User-Agent");
ResultSet rs;
try{
String sql="";
DBConnect dbc=new DBConnect();
try{
sql="select * from bbs_online where id=?";
dbc.prepareStatement(sql);
dbc.setLong(1,statUserID);
//dbc.setBytes(2,userName.getBytes("GBK"));
rs=dbc.executeQuery();
if(rs.next())
onlineSign=true;
else
onlineSign=false;
rs.close();
}
catch(Exception e){
onlineSign=false;
}
if("".equals(userName)){
if(!onlineSign){
sql="insert into bbs_online (id,username,userclass,ip,startime,lastimebk,browser,stats,actforip,ComeFrom,actCome) values("+statUserID+",?,?,?,?,?,?,?,?,?,?)";
dbc.prepareStatement(sql);
//dbc.setLong(1,statUserID);
dbc.setString(1,"guest");
dbc.setString(2,"客人".getBytes("GBK").toString());
dbc.setString(3,ip.getBytes("GBK").toString());
dbc.setString(4,Format.getDateTime());
dbc.setString(5,Format.getDateTime());
dbc.setString(6,browser);
dbc.setString(7,stats.getBytes("GBK").toString());
if(actCome==null)
{
dbc.setString(8,"null");
dbc.setString(10,"null");
}
else
{
dbc.setString(8,actCome);
dbc.setString(10,actCome);
}
dbc.setString(9,comeFrom.getBytes("GBK").toString());
dbc.executeUpdate();
dbc.clearParameters();
}
else{
sql="update bbs_online set lastimebk=?,lastime=?,ComeFrom=?,actCome=?,stats=? where id="+statUserID;
System.out.println("1"+Format.getDateTime()+"2"+Format.getStrDateTime()+"3"+comeFrom.getBytes("GBK")+"4"+actCome+"5"+stats.getBytes("GBK")+"kill");
dbc.prepareStatement(sql);
dbc.setString(1,Format.getDateTime());
dbc.setString(2,Format.getStrDateTime());
dbc.setString(3,"'"+comeFrom.getBytes("GBK").toString()+"'");
if(actCome==null)
{
dbc.setString(4,"null");
}
else{
dbc.setString(4,actCome);
}
dbc.setString(5,"'"+stats.getBytes("GBK").toString()+"'");
//dbc.setLong(6,statUserID);
dbc.executeUpdate();
sql="update bbs_online set ComeFrom=? where id="+statUserID;
dbc.prepareStatement(sql);
dbc.setString(1,"'"+comeFrom.getBytes("GBK").toString()+"'");
dbc.executeUpdate();
dbc.clearParameters();
}
}
else{
sql="select * from bbs_online where ID="+statUserID+" or username=?";
dbc.prepareStatement(sql);
//dbc.setLong(1,statUserID);
dbc.setBytes(1,userName.getBytes("GBK"));
rs=dbc.executeQuery();
if(rs.next())
onlineSign=true;
else
onlineSign=false;
dbc.clearParameters();
try{
User theUser=new User(userName,userPassword,4);
if(onlineSign){
sql="update bbs_online set id="+statUserID+",userName=?,userClass=?,lastimebk=?,lastime=?,ComeFrom=?,actCome=?,stats=? where id=? or username=?";
dbc.prepareStatement(sql);
//dbc.setLong(1,statUserID);
dbc.setBytes(1,userName.getBytes("GBK"));
dbc.setBytes(2,getUserClass(theUser.getUserClass()).getBytes("GBK"));
dbc.setString(3,Format.getDateTime());
dbc.setString(4,Format.getStrDateTime());
dbc.setBytes(5,comeFrom.getBytes("GBK"));
dbc.setString(6,actCome);
dbc.setBytes(7,stats.getBytes("GBK"));
dbc.setLong(8,statUserID);
dbc.setBytes(9,userName.getBytes("GBK"));
dbc.executeUpdate();
dbc.clearParameters();
}
else{
sql="insert into bbs_online(id,username,userclass,ip,startime,lastimebk,browser,stats,actforip,ComeFrom,actCome) values("+statUserID+",?,?,?,?,?,?,?,?,?,?)";
dbc.prepareStatement(sql);
//dbc.setLong(1,statUserID);//statUserID);
dbc.setString(1,userName.getBytes("GBK").toString());
dbc.setString(2,getUserClass(theUser.getUserClass()).getBytes("GBK").toString());
dbc.setString(3,ip);
dbc.setString(4,Format.getDateTime());
dbc.setString(5,Format.getStrDateTime());
dbc.setString(6,browser);
dbc.setString(7,stats.getBytes("GBK").toString());
System.out.println("tttt"+actCome);
if(actCome==null){
dbc.setString(8,"null");
dbc.setString(10,"null");
}
else{
dbc.setString(8,actCome);
dbc.setString(10,actCome);
}
dbc.setString(9,comeFrom.getBytes("GBK").toString());
dbc.executeUpdate();
dbc.clearParameters();
}
}
catch(Exception e){
if(!onlineSign){
sql="insert into bbs_online (id,username,userclass,ip,startime,lastimebk,browser,stats,actforip,ComeFrom,actCome) values("+statUserID+",?,?,?,?,?,?,?,?,?,?)";
dbc.prepareStatement(sql);
//dbc.setLong(1,statUserID);
dbc.setString(1,"guest");
dbc.setString(2,"客人".getBytes("GBK").toString());
dbc.setString(3,ip.getBytes("GBK").toString());
dbc.setString(4,Format.getDateTime());
dbc.setString(5,Format.getDateTime());
dbc.setString(6,browser);
dbc.setString(7,stats.getBytes("GBK").toString());
dbc.setString(8,actCome);
dbc.setString(9,comeFrom.getBytes("GBK").toString());
dbc.setString(10,actCome);
dbc.executeUpdate();
dbc.clearParameters();
}
else{
sql="update bbs_online set lastimebk=?,lastime=?,ComeFrom=?,actCome=?,stats=? where id="+statUserID;
System.out.println("1"+Format.getDateTime()+"2"+Format.getStrDateTime()+"3"+comeFrom.getBytes("GBK")+"4"+actCome+"5"+stats.getBytes("GBK")+"kill");
dbc.prepareStatement(sql);
dbc.setString(1,Format.getDateTime());
dbc.setString(2,Format.getStrDateTime());
dbc.setString(3,"'"+comeFrom.getBytes("GBK").toString()+"'");
if(actCome==null)
{
dbc.setString(4,"null");
}
else{
dbc.setString(4,actCome);
}
dbc.setString(5,"'"+stats.getBytes("GBK").toString()+"'");
//dbc.setLong(6,statUserID);
dbc.executeUpdate();
dbc.clearParameters();
} }
}
sql="delete from bbs_online where now()>date_add(lastimebk,interval 20 minute)";
dbc.executeUpdate(sql);
sql="select Maxonline from config";
rs=dbc.executeQuery(sql);
rs.next();
int oldMaxOnLine=rs.getInt(1);
sql="select count(*) from bbs_online";
ResultSet tmprs=dbc.executeQuery(sql);
tmprs.next();
int newMaxOnLine=tmprs.getInt(1);
if(newMaxOnLine>oldMaxOnLine){
sql="update config set Maxonline="+newMaxOnLine+",MaxonlineDate=now()";
dbc.executeUpdate(sql);
ForumPropertiesManager.resetManager();
}
dbc.close();
}
catch(Exception e){
e.printStackTrace();
}
}
如果SESSION死掉 就在书组 里找到该信息 并删除
我觉得用个专门的bean比较好。用户登陆的时候在bean里面添加相应信息,session失效的时候从bean里面删除。想显示在线用户列表的话就直接从bean里面读取。session是如何死掉和失效的呢?
超时,直接关闭IE,网络断线,客户端死机,这些情况session会自动死掉(失效)?
是立即死掉(失效)还是等待一段时间?
import java.sql.*;
import util.string.StringUtil;
import sun.jdbc.rowset.CachedRowSet;
import javax.servlet.http.*;
import java.util.ArrayList;
import sys.all.SysLockIp;public class UserSession
implements HttpSessionBindingListener {
private String userName;
private String userId;
private String userType = "";
private String gotoUrl;
private boolean isLogin;
private String password;
public static ArrayList userList = new ArrayList();
public void valueBound(HttpSessionBindingEvent event) {
boolean dup = false;
for (int i = 0; i < this.userList.size(); i++) {
UserSession u = (UserSession)this.userList.get(i);
if (u != null && u.getUserName() != null &&
u.getUserId().equals(this.getUserId())) { // forbid add a duplicate user
dup = true;
}
}
if (!dup) {
this.userList.add(this);
}
System.out.println(this.userId + " added into userlist");
} public void valueUnbound(HttpSessionBindingEvent event) {
this.userList.remove(this);
System.out.println(this.userId + " remove from userlist");
}
}
超时,直接关闭IE,网络断线,客户端死机,这些情况session会自动死掉(失效)?
是立即死掉(失效)还是等待一段时间?
package test;import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;/**
* @author Jass
*/
public class MySessionListener implements HttpSessionListener {
private static int onlineUsers = 0; /* (non-Javadoc)
* @see javax.servlet.http.HttpSessionListener#sessionCreated(javax.servlet.http.HttpSessionEvent)
*/
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
// TODO Auto-generated method stub
onlineUsers++;
//String sId = httpSessionEvent.getSession().getId();
//System.out.println("New Session created!Session ID:" + sId);
//System.out.println(onlineUsers + " users online!");
} /* (non-Javadoc)
* @see javax.servlet.http.HttpSessionListener#sessionDestroyed(javax.servlet.http.HttpSessionEvent)
*/
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
// TODO Auto-generated method stub
onlineUsers--;
//String sId = httpSessionEvent.getSession().getId();
//System.out.println("Session Destroyed!Session ID:" + sId);
//System.out.println(onlineUsers + " users online!");
}}
有了这两个函数,类似的功能都可以实现。
再在web.xml的root下加入以下元素就OK了: <listener>
<listener-class>test.MySessionListener</listener-class>
</listener>
gks_cn(981530) ,Jass(Jass) ,你们两位的代码我看懂了,也是解决我问题的思想,其他人的想法也很好,但不够成熟、简洁。
但是、请问gks_cb(981530)您的代码中,在ArrayList里面保存了一个用户的列表,我可以通过判断列表中是否有该用户来确定用户是否在线,这个类是一个servlet,我在后台的bean中如何获取这个列表呢?这点我还是有点不大明白,请指教!
顺便说一句,listener只有在servlet2.3以上才能支持,web.xml的dtd用http://java.sun.com/dtd/web-app_2_3.dtd