3.2会话池维持客户端连接
一个典型的jabber服务将维持许多并发的,长时间的客户端连接。每一个会话在客户端和服务器端定义一个上下文包,并在他们之间通行。每一Session的上下文都必须为维持每一个连接保持连接。它它包含了如下信息:
l Session保持连接的jabberID
l Session保持连接的StreamID
l 被Session用到的java.net.Socket以及对应的java.io.Reader/Writer对象
l Session状态(disconnected,connected,streaming,authenticated)
这个连接的集合和他的维持信息被封装在一个Session对象中。另外,所有的在服务器中的会话在包信息存取都是非常容易丢失的。我们开发一个集中的SessionIndex类就是为了保持我们的活动的会话不丢失以及保证通过jabberID能够找到会话。
我们首先看看Session类。
3.2.1会话类抽象一个连接
会话类提供了一个方便的session上下文信息分组的办法。类开始声明了两个构造器,基本的数据域和访问方法。我们也提供了两个为SessionSocket对象读写功能的方法。大部分时候,session对象用到java.io.Writer写信息到SessionSocket的outputStream中,或者Java.io.Reader读取信息。通过创建和保存一个Reader和Werter,使用者可以不用得到Socket,自己就可以得到Input/OutputStream,和创建Reader/writer。
public class Session{
public Session(Socket socket) { setSocket(socket); }
public Session() { setStatus(DISCONNECTED); }
JabberID jid;
public JabberID getJID() { return jid; }
public void setJID(JabberID newID) { jid = newID; }
String sid;
public String getStreamID() { return sid; }
public void setStreamID(String streamID) { sid = streamID; }
Socket sock;
public Socket getSocket() { return sock; }
public void setSocket(Socket socket) {
sock = socket;
setStatus(CONNECTED);
}
Writer out;
public Writer getWriter() throws IOException {
if (out == null){
out = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
}
return out;
}
Reader in;
public Reader getReader() throws IOException {
if (in == null){
in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
}
return in;
}
最让人感兴趣的是session里的状态管理代码。通过几个类我们能够知道怎么在状态发生改变同时session状态发生改变。我们在下面的章节将会在客户端代码中真切的体会。为了支持这些功能,我们用的会话状态事件模型可以在Swing类中发现。
在此模型中,注册监听事件。当一个事件被触发,会话会通知注册的监听事件。这个过程一时间看来有些小混乱。但是其实这是一条康庄大道,是解决多个对象监控另外对象的变量的一个好办法。ListinListing 3.2 The Session class st atus event code
LinkedList statusListeners = new LinkedList();
public boolean addStatusListener(StatusListener listener){
return statusListeners.add(listener);
}
public boolean removeStatusListener(StatusListener listener){
return statusListeners.remove(listener);
}
public static final int DISCONNECTED = 1;
public static final int CONNECTED = 2;
public static final int STREAMING = 3;
public static final int AUTHENTICATED = 4;
int status;
public int getStatus() { return status; }
public synchronized void setStatus(int newStatus){
status = newStatus;
ListIterator iter = statusListeners.listIterator();
while (iter.hasNext()){
StatusListener listener = (StatusListener)iter.next();
listener.notify(status);
}
}
}
一个Java.util.LinkedList类用来维持一个Session监听列表。加入和删除一个状态是非常容易的。所有的状态时间监听必须继承StatusListener接口。
The StatusListener interface
public interface StatusListener {
public void notify(int status);
}
它只有一个notify()方法,被用在setStatus()方法发送一个事件信息给监听器。为了连贯性,我也为我设想的Session类的4个状态定义几个标准值。
会话类抽象了在客户端和服务器端的一个网络连接和jabberSesison。服务器将处理许多同步的Session,通一个组织结构为jabberID定位Sessions。SessionIndex类承担这些职责。
3.2.3SessionIndex类提供session查找
SessionIndex的主要职责是更具jabberID查找Session对象。对服务器来说按照jabberID定位到正确的Session对象是很重要的,因为多数的jabber包来自于jabber用户。服务器必须根据收到的jabberID定位到适当的session并回复包到客户端。
为实现这些功能,SessonIndex类包括两个Java.util.HashTable对象:userIndex和JidIndex。UserIndex Hashtable提供了Session的用户和Session对象的映射。JidIndex提供了Session的jabberID字符串和Session对象之间的映射。
使用SessonIndex引导session的查找使用以下算法:
l 检查如果接收者的jabber id是在jidindex 。比较使用
精确匹配允许客户发送信息到其他特定客户。
l 如果没有,查看jabberId的用户名在userIndex中
l 如果没有找到,返回null。
使用这个算法服务器能够显示一个合理的消息回路。例如,假设一个客户端的jabberID是iain@shigeoka.com/home,连接服务器。SessionIndex将查找JabberID,在jidIndex中找到它,返回合适的Session。现在,假设消息的地址是iain@shigeoka.com。SessinIndex类在JIdIndex中查找,但没有找到。然而当SessionIndex在userIndex中查找,找到iain,返回连接iain@shigeoka.com/home的Session。
最后,考虑一下消息地址iain@shigeoka/work。SessionIndex类在jidIndex中查找失败,但是能够看到iain实体在userIndex并且返回Session附在iain@shigeoka.com/home。因此,消息被传到适当的用户但是是备用的资源。这个动作符合jabber消息路由规定。
执行的类可以简单明了的管理这些映射。
public class SessionIndex {
Hashtable userIndex = new Hashtable();
Hashtable jidIndex = new Hashtable();
public Session getSession(String jabberID){
return getSession(new JabberID(jabberID));
}
public Session getSession(JabberID jabberID){
String jidString = jabberID.toString();
Session session = (Session)jidIndex.get(jidString);
if (session == null){
LinkedList resources = (LinkedList)userIndex.get(jabberID.getUser());
if (resources == null){
return null;
}
session = (Session)resources.getFirst();
}
return session;
}
public void removeSession(Session session){
String jidString = session.getJID().toString();
String user = session.getJID().getUser();
if (jidIndex.containsKey(jidString)){
jidIndex.remove(jidString);
}
LinkedList resources = (LinkedList)userIndex.get(user);
if (resources == null){
return;
}
if (resources.size() <= 1){
userIndex.remove(user);
return;
}
resources.remove(session);
}
public void addSession(Session session){
jidIndex.put(session.getJID().toString(),session);
String user = session.getJID().getUser();
LinkedList resources = (LinkedList)userIndex.get(user);
if (resources == null){
resources = new LinkedList();
userIndex.put(user,resources);
}
resources.addLast(session);
}
}
如你所见,我在userIndex中为每一个用户名使用了一个Sessions的LinkedList,messages遵循着先到先服务的原则。换句话说,如果你连接以下客户端:
iain@shigeoka.com/home
iain@shigeoka.com/work
一个发送到iain@shigeoka.com的消息将别发送到iain@shigeoka.com/home。如果iain@shigeoka.com/home没有连接,那么消息发送到iain@shigeoka.com给iain@shigeoka.com/work。这些不是标准的jabber路由动作,但是是在第8章之前,在我们添加用户帐户,用户出席和支持协议之前,能做到得到最好的状态。当我们在这里使用用户出席支持,我们将能够实现更加老道的,优先的路由元数据按照指定的jabber标准。使用SessionIndex的关键是包处理类关联的QueueThread类。