高性能聊天系统
作者:板桥banq
1.4.3 访问者模式实现
访问者模式提供了一种针对集合中各种元素实现统一访问的解决模式,在本系统中,接口层针对各种可能的应用层设计,面对的是各种可能数据对象。如何访问这些数据对象,然后将它们统一转换成可以放入Queue的ByteArrayOutputStream类型?其他Queue的操作也能采取这种方案统一解决。
访问者模式的使用关键是确定访问者和被访问者的角色。在这里,被访问者是应用层需要放入Queue中的各种数据对象,而访问者访问这些数据对象有两个操作行为:放入Queue中和从Queue中取出。
那么,设计访问者接口如下:
public interface QueueWorker {
final static int REQUEST = 1;
final static int RESPONSE = 2;
final static MessageQueue messageQueue = MessageQueue.getInstance();
public void run(int msgType, Linkable object) throws Exception;
}
访问者是一个Queue操作工,它访问各种数据对象,然后实行对Queue的操作,被访问者是那些数据对象,建立被访问者接口如下:
public interface Linkable {
public void accpet(QueueWorker worker) throws Exception;
public OutputStream getOutputStream();
public void setInputStream(InputStream in);
}
被访问者是一种可实行LinkerList操作的接口,有一个统一接受访问的方法accept。
下面需要具体实现被访问者:
以String类型数据对象为具体元素,代码如下:
public class StringType implements Linkable {
private String content = null;
private int msgType; //是Request信号还是Response信号
private ByteBuffer byteBuffer = null;
public StringType(int msgType) {
this.msgType = msgType;
}
public String getContent() {
return content;
}
public void setContent(String content){
this.content = content;
}
public void accpet(QueueWorker worker) throws Exception {
worker.run(msgType, this);
}
public OutputStream getOutputStream() {
OutputStream outputStream = null;
try {
//将String转换成ByteArrayOutputStream
outputStream = DataTypeHelper.writeString(content);
} catch (Exception ex) {
}
return outputStream;
}
public void setInputStream(InputStream in) {
try {
//将ByteArrayInputStream转换成String
this.content = DataTypeHelper.getString(in);
} catch (Exception ex) {
}
}
}
其他数据类型如Object等可参考String的实现方法。
访问者QueueWorker有两种具体实现,分别是加入Queue的操作工QueueAddWorker和从Queue中取出元素的操作工QueueTakeWorker。QueueAddWorker代码如下:
public class QueueAddWorker implements QueueWorker{
public void run(int msgType, Linkable object) throws Exception {
//获得元素转换后的outputStream对象
OutputStream outputStream = object.getOutputStream();
if (msgType == REQUEST) {//根据不同的消息类型放入不同的Queue中
messageQueue.pushRequest(outputStream);
} else if (msgType == RESPONSE) {
messageQueue.pushResponse(outputStream);
}
}
}
QueueTakeWorker的代码如下:
public class QueueTakeWorker implements QueueWorker{
public void run(int msgType, Linkable object) throws Exception {
InputStream bin = null;
OutputStream outputStream = object.getOutputStream();
if (msgType == REQUEST) {
bin = (InputStream) messageQueue.removeReqFirst();
} else if (msgType == RESPONSE) {
bin = (InputStream) messageQueue.removeResFirst();
}
//将取出的InputStream类型对象实行各自转换
object.setInputStream(bin);
bin.close();
}
}
以上代码见CD程序1-8。
至此,实现访问者模式的数据转换和Queue操作模块基本完成。在这个转换模块中,针对请求Request信号,根据不同的数据类型,转换成统一的类型放入Queue中,然后再从Queue中取出,转换成各自的数据类型;针对Response信号实现了同样的情况,这样在客户端和服务器端都统一调用这个模块,实现了统一的数据类型。
当然,使用访问者模式后,相关的代码可能变多了。如果在这里不使用模式,可能是只有很少的几行代码,但必须进行详细的注解说明。如果应用层使用的是String类型应该怎样做;使用XXX类型应该怎样做,需要明确告知Queue中放置的是InputStream和OutputStream型,相当于在代码这里插入一个“使用说明书”,这样其他程序员扩展新的应用时才能够明白。
相反,如果在这里使用了模式,相应的警示性代码说明就不需要。因为代码质量已经提高,在访问者模式中封装了Queue的类型放置和提取,对于其他程序员再次使用要方便容易得多。
推而广之,在很多情况下,都存在两种实体:事物流程和事物流程的管理。事物流程的管理是对事物流程实现管理监督的,无论再好的事物流程管理,都没有事物流程本身实现自动自律管理来得更加有效。这也是为什么采取软件系统实现很多处理过程自动化的目的,因为软件系统本身具有强迫性,可以迫使很多管理目的能够非常自然地实现。
1.4.4 协议封装
前面架构设计中已经分析过,目前HTTP协议是防火墙友好型协议,可以穿透大多数防火墙而不被拦截,因此数据包发送可以使用HTTP协议进行包装后再发送。
在本系统中,由于应用层可能有不同的数据类型需要实现Socket发送和接受。因此,如果将数据协议包装工作设定在接口层和应用层之间,势必又带来了数据类型问题的困扰,因此明智之举是放在接口层和Socket之间。当Socket底层从接口层的Queue中获取数据对象后,再进行协议包装,然后通过I/O发送,服务器方在Socket I/O接受到数据后,去除协议包装,再将正文内容放入接口层Queue中。
目前是采取HTTP协议,考虑到为其他协议留下扩展的余地,有可能根据不同的应用,要增加新的协议,所以协议和数据的包装过程必须是能够替换或者扩展的。那么现在需要将这个包装过程封装起来,和外界包括Socket I/O部分切断过多的联系。这样在扩展协议时,才不会需要修改Socket I/O部分的代码。
工厂方法模式是封装创建过程的模式。在本例中,协议和数据结合的传送数据包是最终产品,而这个结果过程需要封装。工厂方法模式正好可以解决这个问题。关于工厂模式的更多研究和讨论见以后章节。
建立一个WrapFactory抽象类,专门是实现协议和数据结合包装的类,代码如下(程序1-9):
程序1-9
/**
* 数据包装工厂
* 默认是使用HTTP协议包装数据,也可以拓展成其他协议
* <p>Copyright: Jdon.com Copyright (c) 2003</p>
* <p>Company: 上海极道计算机技术有限公司</p>
* @author banq
* @version 1.0
*/
public abstract class WrapFactory {
private static WrapFactory factory = new com.jdon.jserver.http.HttpWrapFactory();
public static WrapFactory getInstance() {
return factory;
}
public abstract byte[] getRequest(byte[] bytes);
public abstract byte[] getResponse(byte[] bytes);
public abstract byte[] getContentFromRequest(byte[] bytes) throws Exception;
public abstract byte[] getContentFromResponse(byte[] bytes) throws Exception;
}
在这个抽象类中,需要实现具体4个行为:获得包含协议的请求信号数据包;获得包含协议的响应信号数据包;从请求数据包中分离获得正文;从响应数据包中分离获得正文。
无论采取哪个协议,这4个行为都是必须要实现的。该抽象类默认是以HttpWrapFactory为具体实现,HttpWrapFactory是HTTP协议的数据封装实现。代码如下:
public class HttpWrapFactory extends com.jdon.jserver.connector.WrapFactory {
private String httpPOSTHeader = null;
public HttpWrapFactory() {
httpPOSTHeader = HttpHeadHelper.getPOSTHeader("", 0);
}
public byte[] getRequest(byte[] bytes){
return HttpHeadHelper.assembleRequest(bytes);
}
public byte[] getResponse(byte[] bytes){
return HttpHeadHelper.assembleResponse(bytes);
}
public byte[] getContentFromRequest(byte[] bytes) throws Exception{
return HttpHeadHelper.getContent(bytes);
}
public byte[] getContentFromResponse(byte[] bytes) throws Exception{
return HttpHeadHelper.getContent(bytes);
}
}
HttpWrapFactory将每个行为具体落实又委托给帮助类HttpHeadHelper实现,在HttpHeadHelper中具体实现数据正文和HTTP协议头部组合过程,以及从数据包中分离开HTTP协议头部以及数据正文等过程。
那么具体如何使用这个WrapFactory?以UDPClient为例子,将其Socket读写部分的代码改写为如下:
ByteArrayOutputStream outByte =
(ByteArrayOutputStream) messageQueue.removeReqFirst();
//下面是增加的一句
byte[] request = wrapFactory.getRequest(outByte.toByteArray());
ByteBuffer buffer = ByteBuffer.wrap(request);
keyChannel.write(buffer);
原来的代码是直接从Queue中取出后,转为byte数组直接包装进入ByteBuffer发送出去,现在在进入ByteBuffer之前,进行协议封装,使用WrapFactory对象的getRequest方法,获得一个包含HTTP协议头部信息的总数据包,然后再将这个总数据包发送出去。
下页