IM概念和Jabber协议
从现在开始,我们将建立一个基础的程序,在以后的章节都要使用。我们将开始开发一个基本地jabber服务。以此为基础,我们在以后章节增强它的功能。
3.1一个基本的Jabber服务
如果你曾经写过服务类程序,一个基本的Jabber服务的代码设计对你来说是非常熟悉的。即使没有,也不用担心。服务类程序从根本来说是非常简单的。最大的困难和复杂度在于附加的一些特征,作为一个大规模的服务器,需要安全有效的应付多用户并发的情况。
本书中设计的Jabber服务是一个简单的可靠的以及遵循jabber协议。简单是为了我们更容易的理解和修改,因为我们感兴趣的地方以及的目的是学习典型服务器的重要特征。一旦我们明白了这些基本特征,修改和扩展是非常容易。
一个简单的jabber服务需要做三件事:
l 连接到客户端
l 读取jabber xml流
l 对客户端的输入作出反应
令人不可思议的是,我们实现这些事情只需要寥寥几个java类就可以实现。在这一节,我们建立一个基本的jabber服务实现这三个任务。
3.1.1我们的目标
在开始之前,设置一个明确的目标是非常有帮助的。目标指导我们设计和实施的准则。在系统中,我们会用到以下准则:
简单——要求事情简单纯粹是从美学观点来看的。另外,简单的软件意味着更加可靠,容易理解和易于修改。后面的两个准则是在此基础上的。保持事物的简单性,我们将会有一个好的开端。
服从标准——我们开发一个Jabber服务的目的是明白和阐明jabber协议。很明晰,我们不想为了正确的实现而失去教育的意义。现在最合适的效果是介于这之间的。
容易理解——代码必须是容易理解的。一个混乱的难以理解的代码是很难说明问题和解释他们。我们要实现的是倾斜的代码。
便于修改——虽然代码是用来学习的。你可能更加希望他用在真实世界中,在许多时候,你根据你的需要修改。我们将尽量使它易于修改,无论面对什么情况。
同时也要明确我们的软件暂时不会做的事情:安全性,伸缩性,可管理性,有效性行和运行速度。一个一般快的机器就可以应付20个人的网络系统,足够中小部门的使用。
3.1.2我们的服务代码
我们的代码将接受和处理来自客户端的连接,解析xml流,依照jabber命令发出xml流。软件的启动被严格的限制,但是当我们附加新的jabber协议支持,扩展了服务特性和功能。
有意思的是我们可以使得我们的软件实现jabber的高级功能。在第十章,他们包含了服务器安全的实现,在现有系统中嵌入jabber功能,集成其他企业资源。
3.1.3基本的服务设计
我的jabber服务被分开为三个基本模块:
Session Pool:一个jabber客户服务器会话集合。每一个会话管理一个人java.net.Socket连接和SessionID和jabberID等元数据。
XML解析:xml解析类处理输入的jabber xml数据,把他们转变成java 包对象。Xml解析类依赖于简单xmlAPI(SAX)解析。
Packet处理:服务器对输入的Packet作出反应。在多数情况下,服务简单的将Packet路由到特定的输入终端。然而许多Packet必须经过处理,生成一个或多个回复Packet。
把所有的Packet对象集合到packetQueue进行处理,
PacketQueue:这个类是关于Packet对象的基本队列集合结构。PacketQueue参与到Packet处理模块对Packet对象的xml解析以及存储Packet对象到PacketQueue中。Packet处理把Packets对象一处队列并处理它们。PacketQueue的设计是线程安全的,允许单独的服务器线程同步处理packetQueue。
Jabber Packets——Packets类存储的信息数据包包含了jabber服务器与客户端互发JabberXml数据包。
当客户端连接到服务器,基本的服务操作开始。Server将为此连接建立一个会话对象,并开始解析进入的xml。Xml解析后转变成packet对象被推入到packet队列。一个packet处理的集合把packet从队列中取出,处理它们并产生输出的packet,使用session索引找到正确的session对象的xml输出流,并且输出xml到流中。
session池,xml解析,和packet队列在一起互相协助处理jabber packet。我们将在packet处理类中实现多个协议。现在我们提供了最基本的输入packet路由。我们遍历jabber协议知道将添加更多的packet处理。
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是[email protected]/home,连接服务器。SessionIndex将查找JabberID,在jidIndex中找到它,返回合适的Session。现在,假设消息的地址是[email protected]。SessinIndex类在JIdIndex中查找,但没有找到。然而当SessionIndex在userIndex中查找,找到iain,返回连接[email protected]/home的Session。
最后,考虑一下消息地址iain@shigeoka/work。SessionIndex类在jidIndex中查找失败,但是能够看到iain实体在userIndex并且返回Session附在[email protected]/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遵循着先到先服务的原则。换句话说,如果你连接以下客户端:
一个发送到[email protected]的消息将别发送到[email protected]/home。如果[email protected]/home没有连接,那么消息发送到[email protected]给[email protected]/work。这些不是标准的jabber路由动作,但是是在第8章之前,在我们添加用户帐户,用户出席和支持协议之前,能做到得到最好的状态。当我们在这里使用用户出席支持,我们将能够实现更加老道的,优先的路由元数据按照指定的jabber标准。使用SessionIndex的关键是包处理类关联的QueueThread类。
3.3xml解析子系统
XML解析是jabber服务所做的最复杂的任务了,然而,对于java的Coder们来说,这个任务显然是小儿科,因为我们用完善的Java Sax解析库来做这些工作。我们仅仅需要对查找出处理这些的方法。
服务器的XML解析类的任务就是将xml流信息写入Packet对象,存储的PacketQueue。我们用packet和packetQueue类开始我们的xml解析过程
3.3.1描述Jabber包
jabber协议包含了客户端与服务器端交换xml碎片的办法。我们把这些xml碎片作为一个包来提交。我们把这些信息包装到java对象中,他们工作使用起来要比xml字符串简单的多。使用java对象还可以享受到java的类型检测,继承以及多态等特征的好处。
预览jabber协议就可以知道它有三个核心包需要操心:
<message></message>
,<presence></presence>
<iq></iq>
另外需要我们注意的是打开的和关闭的流标签以及流错误包:
<stream:stream>
<stream:error></stream:error>
</stream:stream>
支持这些包和标签从根本上说是很简单的。在本章最后,我们的jabber服务将能够识别和控制他们。第一步是识别他们的一般特征和封装他们到java类。
Jabber包其实是一个xml片段,因此可以把它当成一个mini的xml文档。在java中又很多的方法处理xml文档。最流行的依据w3cdom标准。大部分的javaxml解析库包含标准的支持w3cdom的java类。在dom中,xml文档时类似于树的结构体。
我们需要的xml表示不是如dom标准所能胜任的。我们知道我们接受各种各样的xml文档和我们指明的java对象。另外,不使用dom也可使我们的代码避免依附到dom库和建立服务器所需的代码。最后,我们的包类必须做比呈现xml片段更多的功能。Packets类必须能充当如下两个角色:
Pack Store——packets类是一个主要的数据储存类。信息能够被标准的java方法调用。我们能够存储包到类似w3cdom的树形数据结构。
XML writer——packet类能够知道到怎么创建自己的xml字符串表示方式。这个特征允许其他类从xml字符串呈现对象转换成的packet对象。
一个单独的xml解析类在本章最后将从接受的xml字符串转变成packet对象。Packet类的数据结构反映了xml片段的结构。例如,考虑如下xml包:
<message to='recipient'>
<subject>hello</subject>
<body>How are you doing?</body>
</message>
我们可以把它们组织到三个包中:
Packet: message (attribute to='recipient)
Packet: subject
String (value "hello")
Packet: body
String (value "How are you doing?");
在这样的数据结构中,我们能够看到一个包有一个元素名,零个或多个属性名值对,零个或多个子节。一个packet的子对象是一个字符串或其packet对象。另外,每个包有一个相关的命名空间。
作为一个java类,我们能够存储packet的子节到java.util.List类型的List中。他的属性在java.util.Hashtable,并且其他值是字符串。如果这个包没有父节点,包的parent值是null。
这个类包括三个构造器:
The Packet class constructors
public class Packet {
public Packet(String element){
setElement(element);
}
public Packet(String element, String value){
setElement(element);
children.add(value);
}
public Packet(Packet parent,
String element,
String namespace,
Attributes atts){
setElement(element);
setNamespace(namespace);
setParent(parent);
//Copy attributes into hashtable
for (int i = 0; i < atts.getLength(); i++){
attributes.put(atts.getQName(i), atts.getValue(i));
}
}
parent和children成员变来那个负责packet的树形结构和他们的子节。我们能够用LinkedList类型的类存储packet和Strings。另外,packet的命名空间和元素名必须被存储成字符串。
String namespace;
public void setNamespace(String name) { namespace = name; }
public String getNamespace() { return namespace; }
String element;
public void setElement(String element) { this.element = element; }
public String getElement() { return element; }
Packet parent;
public Packet getParent() { return parent; }
public void setParent(Packet parent){
this.parent = parent;
if (parent != null){
parent.children.add(this);
}
}
LinkedList children = new LinkedList();
public LinkedList getChildren() {return children;}
packet提供了几个构造器。假设我们用个如下的xml包:
<item>
ItemValue
<sub-item>sub-item value</sub-item>
<sub-item>another value</sub-item>
</item>
有三个典型的典型任务,其他的类将在packet中执行:
l 获取给定元素名的子包。(例如:<sub-item>sub-item value</sub-item>).
l 获取packet的第一个字符串值。
l 获取字符串值相关的第一个子包。
第一个方法是从一个给定的元素名定位第一个子包。他在getFirstChild()中实现。例如,考虑最近的<itme>xml包。你能够在itme包对象中调用getFirstChild(“sub-itme”),得到元素名为sub-item的子包。
public Packet getFirstChild(String subelement){
Iterator childIterator = children.iterator();
while (childIterator.hasNext()){
Object child = childIterator.next();
if (child instanceof Packet){
Packet childPacket = (Packet)child;
if (childPacket.getElement().equals(subelement)) {
return childPacket;
}
}
}
return null;
}
另外一些基本的任务是依据一个元素获得字符串值。例如,我们得到了<sub-item>子包,我们能够知道他的值(“sub-item value”)。你能够通过调用<sub-item>的包对象getValue()方法得到。
public String getValue(){
StringBuffer value = new StringBuffer();
Iterator childIterator = children.iterator();
while (childIterator.hasNext()){
Object valueChild = childIterator.next();
if (valueChild instanceof String){
value.append((String)valueChild);
}
}
return value.toString().trim();
}
甚至有许多类似的情况,当我们想知道sub-packet的子节的字符串值。再上一个例子中,我们通过调用子包的getFirstChild(“sub-item”),得到<sub-item>的子包值。然后调用getValue()得到他的字符串值。有了这些方便的方法,我们能够更方便的结合到getChildValue()方法中。public String getChildValue(String subelement){
Packet child = getFirstChild(subelement);
if (child == null){
return null;
}
return child.getValue();
}
输入包经常依赖于会话上下文和其它动作路由。每一个在我们的jabber服务中的client或server能够有一个相关的session对象出处session上下文。这个packet存储这个会话对象的引用。
Session session;
public void setSession(Session session) { this.session = session; }
public Session getSession() {
if (session != null){
return session;
}
if (parent != null){
return parent.getSession();
}
return null;
}
许多jabber协议依赖于packet属性翻译包相关信息和他的动作。Packet类存储属性到java.util.Hashtable。另外,他提供了几个方便的方法访问大部分的一般packet属性:
l to——包的接受对象
l from——包发送者
l id——包ID,唯一标识包
l type——包类型。依据协议而定。
Hashtable attributes = new Hashtable();
public String getAttribute(String attribute) {
return (String)attributes.get(attribute);
}
public void setAttribute(String attribute, String value) {
if (value == null){
removeAttribute(attribute);
} else {
attributes.put(attribute,value);
}
}
public void removeAttribute(String attribute){
attributes.remove(attribute);
}
public void clearAttributes(){
attributes.clear();
}
public String getTo() { return (String)attributes.get("to"); }
public void setTo(String recipient) { setAttribute("to",recipient); }
public String getFrom() { return (String)attributes.get("from"); }
public void setFrom(String sender){ setAttribute("from",sender); }
public String getType() { return (String)attributes.get("type"); }
public void setType(String type){ setAttribute("type",type); }
public String getID() { return (String)attributes.get("id"); }
public void setID(String ID) { setAttribute("id",ID); }
}
最后,packet类能够把它自己写成一个xml字符串,依靠java.io.Writer。创建xml呈现的过程包括遍历树顺序,输出合适的元素,属性,以及子节。
public void writeXML() throws IOException {
writeXML(session.getWriter());
}
public void writeXML(Writer out) throws IOException{
out.write("<");
out.write(element);
//Output the attributes for the element
Enumeration keys = attributes.keys();
while (keys.hasMoreElements()){
String key = (String)keys.nextElement();
out.write(" ");
out.write(key);
out.write("='");
out.write((String)attributes.get(key));
out.write("'");
}
//Empty element
if (children.size() == 0){
out.write("/>");
out.flush();
return;
}
out.write(">");
//Iterate over each child
Iterator childIterator = children.iterator();
while (childIterator.hasNext()){
Object child = childIterator.next();
//Send value to Writer
if (child instanceof String){
out.write((String)child);
//Or recursively write its children's XML
} else {
((Packet)child).writeXML(out);
}
}
out.write("</");
out.write(element);
out.write(">");
out.flush();
}
public String toString(){
try {
StringWriter reply = new StringWriter();
writeXML(reply);
return reply.toString();
} catch (Exception ex){
}
return "<" + element + ">";
}
}
packet置入,接受来源,一个单独的服务端包队列。
3.3.2 服务的焦点,packetQueue类
packetQueue类是一个受限的响应集合的基本数据结构类。然而,他是服务器端的信息流的焦点。Packet从客户端聚集到packetQueue。然后packet按照packet的元素名发散出去。许多操作方法能够被服务器激活,并且同步到packetQueue。
Comand操作设计模式:
For those familiar with design patterns, our packet handling system is a minor
variation on the Command Processor design pattern.9
“The Command Processor design pattern separates the request for a service
from its execution. A command processor component manages requests as
separate objects, schedules their execution, and provides additional services
such as the storing of request objects for later undo.
的jabber服务中,xml解析充当命令控制器的作用,接受请求和转换
在我们的jabber服务,XML解析动作作为command控制器接受请求,转换xml流到命令。QueeueThread充当命令处理机获得paceket并调度他们的执行。不管像不像命令处理模式,packets是不会自己处理他们自己的运行。双重功能的命令处理供应者和命令处理代替我们的packet处理类,提供我们的整个的包处理功能。
使用comandprocessor模式的好处是:
l 请求触发的可扩展性——支持选择启动的方法是的能够很容易的桥连我们的jabber服务和其它message系统。
l 请求的操作和数量上的可扩展性——快速改变jabber协议是非常重要的。
l 相关服务的可编程——在特殊情况下,我们只要稍稍修改QueueThread就能够容易的记录packets和重放jabber会话。
l 应用的可测试性——QueueThread是一个极好的接入点,为我们测试xml解析和packet处理。一旦开启,记录和重放session是一个巨大的帮助。
l 并发——包和他们的处理是一个相对分离的计算。当线程同时执行时,QueueThread能够容易的分配处理。
Commander processor模式有一些缺陷:
l 效率的损耗——转换数据格式和提供中间处理步骤需要而外的计算时间的存储空间。
l 潜在着过多的命令类——大部分时候,我们避免把所有的包表现为单一的,一般的paceket类。我们为简单性而降低了一半的呈现效率。另外,一个packet类能够呈现不定数量的packet类。我们的服务必须建立额为的逻辑来垂腭这些包。
PacketQueue必须:
l 存储包——接受packet对象推入packetQueue的底
l 回收包——允许packet对象退出packetQueue,并移除它
l 线程安全——无障碍的允许多线程同步从packetQueeue 中push和pull packet。
l 提供线程同步——如果PacketQueue是空的,调整pull包之前让调用者停止一会儿直到一个新的packet进入队列,或者线程终止。线程能够同步的使用它们的操作和保存服务资源。
操作packet对象的线程能够有效的不断的从packetqueue拉出packet对象。PacketQueue自己必须确保这些工作的线程只能够在packetQueue正常有效的情况下执行。
packetQueeue的对象是非常简单的。一个java.util.LinkedList对象,用来存储Packets和一点用来支持服务器多线程环境的基本要素。
在下面高亮的部分提示了方法push()和pull()是线程安全保护的。每一个push()方法的调用时引起了notifyAll()唤醒等待的pull()处理线程。Pull()方法的定时器在队列是空的时候调用wait()方法。
public class PacketQueue {
//Actual storage is handled by a java.util.LinkedList
LinkedList queue = new LinkedList();
public synchronized void push(Packet packet){
queue.add(packet);
notifyAll();
}
public synchronized Packet pull(){
try {
while (queue.isEmpty()) {
wait();
}
} catch (InterruptedException e){
return null;
}
return (Packet)queue.remove(0);
}
}
loop是必须的,因为push()方法调用notifyAll(),唤醒所有等待的pull()。如果仅仅一个packet被推进队列,第一个线程执行移除从队列中。所有其他等待的线程继续运行,队列是空的,他们定时wait(),直到下一个包的到来。
如果你不能理解java线程代码,你可以信任这些工作,写一些测试验证一下,或者学习关于java线程的知识。我已经尽力将本书的中关于thread的代码最小化,但是服务代码倾向于沉重的线程。如果你希望学习到线程,有几个很好的书可以看。另外,java在线文档也提供免费指导。
现在,我们看看用jabberInputHandle类处理xml解析。
3.3.5java中的 SAX 解析(略)
3.4包处理和服务器线程
我最终需要的jabber服务器,包括服务包处理类和服务器端线程。包处理是继承自PacketListerer接口的简单对象,处理包对象。我们用的包处理类实现我们知道的jabber协议。
在这一章,我们将创建三个packet处理类:
l 当未关闭的<stream:stream>发送时一个OpenStreamhandler类为每一个连接创建Sessiong对象。
l 一个简单的messagehandler类,实现Jabber包路由初始版本。
l 当发送关闭标签<stream:stream>时候,CloseStreamhandler类经关闭连接。
服务的线程系统有三个类组成,每一个类代表一个线程执行:
l Server——主应用程序类和主执行线程。这个类创建jabber服务socket和接受新的jabber客户连接。
l ProcessTread——服务器线程类接受连接处理。他创建一个jabberInputHandler和一个输入xml流过程。ProcessThread线程处理和连接一一相对。
l QueueThread——这个线程类从PacketQueeue得到包然后他打发给适当的包处理类。在jabber服务中仅仅有一个活动的queueThread对象。
在同一时间有许多线程在服务上执行,然而,只有三种类型的需要你的关心,并且只有ProcessThread有超过一个的实例在执行。理解服务工作的最后办法,看看3.6图,显示了服务器上都发生了什么。
我们看看服务的主应用首先启动。他的首要任务是创建QueueThread类。新的queueThread立即调用pull()接受PacketQueue的包。QueueThread定时是因为队列中没有包。服务然后等待这用java.net.ServerSocekt.accept()调用连接。
在服务器上一个客户连接。这促使服务器创建一个processThread。这个ProcessTrhead用saX解析开启解析输入xml流和我们的JabberInputhandler类。当客户端发送xml包到服务器,ProcessTrhead的解析类创建包对象并调用push()加入到PacketQueue。
队列中新的Packet唤醒QueueThread。QueeuThread确定包已经被推入队列,然后用当前处理类处理它。包处理发送输出xml包到客户端。
注意仅有一个服务线程接受连接,并且一个QueueThread处理从packetQueue的包对象。然而,每一个客户链接有一个活动的processThread。有共享执行线程的方法处理输入的客户连接。另外,服务器有许多的包处理线程。然而,在我们的服务中,我们使他们尽量的简单。
从服务的主类创建QueueThread和ProcessThread,我们首先看到有两个帮助线程类,在运行服务助力额之前。QueueThread是个更加的复杂,我们就在这开始。