使用dwr实现javaweb实时聊天通讯网页版的时候可能会遇到以下问题:
1、因为每次刷新页面都创建一个scriptsession而产生发送一条消息接收方收到多条消息的问题;
问题分析:
scriptsession不同于普通的session,普通的session在我们第一次访问网站的时候创建,再次访问此网站不会再次创建session(session有效期内,且没有关闭浏览器或清除缓存),而scriptsession则是每打开一个页面或者刷新页面都会生成新的scriptsession,并且短时间内原有的scriptsession并没有销毁(有的文档上说的是5分钟),所以会同时存在多个scriptsession,一般的定向发送消息会使用Browser.withAllSessionsFiltered(ScriptSessionFilter filter, Runnable task)该方法,具体实现如下:
Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
public boolean match(ScriptSession session) {
if (session.getAttribute("userId") == null)
return false;
else
return (sendtoId.indexOf((String)session.getAttribute("userId"))>=0);
}
}, new Runnable() {
private ScriptBuffer script = new ScriptBuffer();
public void run() {
Collection<ScriptSession> sessions = Browser.getTargetSessions();
for (ScriptSession scriptSession : sessions) {
script.appendCall("showTotalNum", totalUnCheckedNum);
scriptSession.addScript(script);
}
}
});
所以在存在多个scriptsession的情况下就回出现发送一条消息但是接收到多条消息的问题。
解决方案:
自己创建dwr监听类DWRScriptSessionListener.java,在初始化的时候使用自己的监听类,自己的监听类中定义Map来保存有效的scriptsession,定向发送消息时从此Map中获取scriptsession,完成发送,就能避免重复发送
DWRScriptSessionListener.java代码如下:
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.directwebremoting.ScriptSession;
import org.directwebremoting.WebContextFactory;
import org.directwebremoting.event.ScriptSessionEvent;
import org.directwebremoting.event.ScriptSessionListener;
import com.ches.bean.UnitRegister;
public class DWRScriptSessionListener implements ScriptSessionListener {
// 维护一个Map key为session的Id, value为ScriptSession对象
public static final Map<String, ScriptSession> scriptSessionMap = new HashMap<String, ScriptSession>();
/**
* ScriptSession创建事件
*/
@Override
public void sessionCreated(ScriptSessionEvent event) {
HttpSession session = WebContextFactory.get().getSession();
UnitRegister user = (UnitRegister) session.getAttribute("user");
ScriptSession scriptSession = event.getSession();
if(user!=null){
scriptSession.setAttribute("userId", user.getId());
scriptSessionMap.put(user.getId(), scriptSession); // 添加scriptSession
}else{
scriptSession.setAttribute("userId", session.getId());
scriptSessionMap.put(session.getId(), scriptSession); // 添加scriptSession
}
}
/**
* ScriptSession销毁事件
*/
@Override
public void sessionDestroyed(ScriptSessionEvent event) {
if(event.getSession() != null&&!event.getSession().isInvalidated()){
event.getSession().invalidate();
}
}
/**
* 获取所有ScriptSession
*/
public static Map<String, ScriptSession> getScriptSessions() {
return scriptSessionMap;
}
}
如何使用自己定义的监听类:
原始监听的代码为:
public void init() throws ServletException {
try {
Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener listener = new ScriptSessionListener() {
@Override
public void sessionCreated(ScriptSessionEvent event) {
HttpSession session = WebContextFactory.get().getSession();
UnitRegister user = (UnitRegister) session.getAttribute("user");
if(user!=null){
event.getSession().setAttribute("userId", user.getId());
}else{
event.getSession().setAttribute("userId", session.getId());
}
}
@Override
public void sessionDestroyed(ScriptSessionEvent arg0) {
}
};
manager.addScriptSessionListener(listener);
} catch (Exception e) {
e.printStackTrace();
}
}
使用自己的监听类则改为:
public void init() throws ServletException {
try {
Container container = ServerContextFactory.get().getContainer();
ScriptSessionManager manager = container.getBean(ScriptSessionManager.class);
ScriptSessionListener listener = new DWRScriptSessionListener();
manager.addScriptSessionListener(listener);
} catch (Exception e) {
e.printStackTrace();
}
}
发送信息处的代码处理为:
Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
public boolean match(ScriptSession session) {
return true;
}
}, new Runnable() {
private ScriptBuffer script = new ScriptBuffer();
public void run() {
Map<String, ScriptSession> sessions = DWRScriptSessionListener.getScriptSessions();
ScriptSession scriptSession = sessions.get(sendtoId);
if(scriptSession!=null){
script.appendCall("showMessage",totalUnCheckedNum , unCheckedNum, thisId, thisName, sendtoId, sendtoName, sendMessage);
scriptSession.addScript(script);
}
}
});
2、关闭聊天界面后无法收到消息
问题分析:
解决了问题1之后,按照以上的思路要想在其他页面显示和反馈收到的消息将无法实现,因为只给最后的保存在自定义的map中的scriptsession发送消息,就只能在打开的对话页面接收消息,关闭对话页面继续浏览其他页面则无法接收消息。
解决方案:
同时使用原始的发送消息策略和问题1中改造后的发送消息策略,即
(1)Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
public boolean match(ScriptSession session) {
if (session.getAttribute("userId") == null)
return false;
else
return (sendtoId.indexOf((String)session.getAttribute("userId"))>=0);
}
}, new Runnable() {
private ScriptBuffer script = new ScriptBuffer();
public void run() {
Collection<ScriptSession> sessions = Browser.getTargetSessions();
for (ScriptSession scriptSession : sessions) {
script.appendCall("showTotalNum", totalUnCheckedNum);
scriptSession.addScript(script);
}
}
});
(2)Browser.withAllSessionsFiltered(new ScriptSessionFilter() {
public boolean match(ScriptSession session) {
return true;
}
}, new Runnable() {
private ScriptBuffer script = new ScriptBuffer();
public void run() {
Map<String, ScriptSession> sessions = DWRScriptSessionListener.getScriptSessions();
ScriptSession scriptSession = sessions.get(sendtoId);
if(scriptSession!=null){
script.appendCall("showMessage",totalUnCheckedNum , unCheckedNum, thisId, thisName, sendtoId, sendtoName, sendMessage);
scriptSession.addScript(script);
}
}
});
说明:1、没有放在自定义map中的scriptsession在销毁之前一直存在的。
2、发送消息策略(1)和(2)中的回调函数名称要区分,不能使用同一名称。