高性能聊天系统

  作者:板桥banq

<<Java实用系统开发指南>>

本章以建立一个聊天系统为例,介绍如何使用J2SE 1.4非堵塞I/ONI/O)为核心开发一个高性能的实时互动服务器系统,这个高性能服务器系统可以拓展为更广阔的应用,如游戏系统、社区系统或者数据实时采集系统。

1.1  系统需求

聊天交流是目前互联网提供的主要内容。聊天系统有多种实现方式,类似ICQ属于一种点对点的聊天系统,还有一种是基于Socket的集中式聊天系统,这种聊天系统需要登录统一的聊天服务器,每个人的聊天信息其他人都可以看到,类似一种会议室,当然,两个人之间也可以进行保密的私语。

在基于Socket的聊天系统中,主要有两种角色:服务器和客户端,不同的客户端登录集中式的服务器,通过服务器将一个客户端发出的信息推送到其他所有客户端。

基于Socket的聊天系统最早实现是使用网页刷新方式,通过客户端不断地自动刷新,将服务器端整个页面内容下载到客户端显示,这种方式的聊天速度慢,而且有刷屏现象,很快被更新的聊天技术所替代。

聊天系统在客户端和服务器之间主要传送的是文字信息,服务器端只需要把最新的文字信息推送到客户端,这样减少了网络传输内容,节省了网络传输的时间,无疑提高了聊天速度。这种“推”技术是目前基于Socket聊天系统的主要实现技术。

一个基于Socket的聊天系统有下列具体功能要求:

1)客户端和服务器必须保持随时随地的连接。这有别于普通Web浏览的连接方式。在使用浏览器访问服务器时,先由客户端发出HTTP协议,然后服务器响应处理这个客户端的响应,再返回处理结果;请求(Request)和响应(Response)是一种一对一的前后因果关系。

而在基于Socket的聊天系统中,客户端发出聊天信息的同时,客户端也在接受服务器发送过来的其他人的聊天信息,因此,请求和响应不存在那种前后对应关系,是两种分别独立进行的流程。

因为服务器任何时候都可能发送信息到客户端,因此,客户端和服务器一旦建立连接,必须能让服务器在以后发送中寻找定位到这个连接。

2)在速度性能方面,聊天系统提出了更高的要求。在网络连接的薄弱环节I/O通信方面,要求能够实现无堵塞地、流畅地数据读写。在面对几百个甚至更多的客户端同时发出连接信息的情况下,服务器要求能够保持高性能的并发处理机制,迅速地完成这几百个并发请求的处理和发送任务。

3)在扩展性和伸缩性方面,聊天系统也提出了一定的要求。当一台服务器不能满足要求时,必须在客户端不知晓的情况下,通过不断增加服务器就能方便地拓展聊天系统的整体处理能力。对于客户端用户来说,这些服务器群都象征一个统一的服务器,不需要他们在进入聊天室之前先选择具体的服务器;也没有单个聊天室最大人数的限制,如果可以,服务器群可以支撑一个巨大容量的聊天室。

1.2  架构设计

本系统的设计核心是Socket底层通信,基于快速稳定的Socket底层通信架构,不但可以实现聊天系统,还可以实现其他如游戏、数据采取等实时性要求较高的系统,甚至可以建立一个快速的平台服务器系统。相比J2EE服务器系统,该平台系统的最大优势就是精简的代码带来的高性能。

当然,如果单纯追求高性能和速度,也许直接使用汇编就可以。使用Java设计这样的实时系统,实际还有一种很重要的目的,即追求高度的可扩展性和伸缩性。

因此,本系统设计必须将高性能和高伸缩性两个方面和谐地统一起来,不能盲目追求性能而破坏面向对象的编程风格和模式;也不能因为追求更大的重用性,建立太多复杂的中间层,其实这方面J2EE已经做得很好,有关J2EE的应用将在以后章节重点讨论。

当然,高性能应该是本系统的主要特色,为了实现本系统高效率的并发处理性能,设计上将采取Reactor模式实现自我快速触发;网络通信上,使用非堵塞I/O进行流畅地数据读写,在应用逻辑上,通过非堵塞的多线程技术实现并发功能处理。特别是J2SE 1.4以后版本推出的非堵塞I/O,将使得Java表现出和C语言同样的优越性能。

1.2.1  Java事件模型

事件只有在被触发时才会发生,它的发生是程序系统无法预料和计划的。例如,火警探测器只有在发生火警时才会触发,但是火警的发生是无法事先预料的,它处于时刻可能发生当中。为了能响应这些无法预料发生的事件,必须建立一套事件处理机制,以预备在发生事件时实现相应的处理。

通常,在一个事件处理框架里有下列3种角色。

·       源目标:事件发生者。

·       监视者:将监察侦听事件的发生,事件发生后,它会被通知到。

·       处理者:事件发生后,它将实现一定的行为动作,也就是处理事件。

Java中有几种模式表达这3种角色之间的关系。

监视模式是一种最常用的模式,GOF《设计模式》中的观察者模式和监视模式类似,这将在以后章节讨论。

在监视模式中,监视者本身也兼任处理者的角色,3种角色被实现为两个独立对象,源目标为一个对象,而监视者和处理者两个角色同位于一个对象中。

例如,一个可视化JavaBean对象Button属于源目标,它通过addActionListener绑定一个监视者对象java.awt.event.ActionListener的子类来完成事件触发机制,当它被用户点按时,ActionListener的子类将完成相应点按事件的处理,如图1-1所示。

nio

1-1  Java GUI中的事件处理

以本系统为例,当用户输入服务器端地址和端口,单击连接按钮后,将启动连接远程服务器线程,其中部分重要方法如下:

connectButton.addActionListener(new ClientFrame_connectButton_actionAdapter(this));

void connectButton_actionPerformed(ActionEvent e) {

        //启动连接服务器线程

        NonBlockingSocket nonBlockingSocket = new NonBlockingSocket(url, port);

        nonBlockingSocket.setDaemon(true);

        nonBlockingSocket.start();

}

连接按钮connectButton通过addActionListener方法加入了一个监视者对象,监视者ClientFrame_connectButton_actionAdapter代码如下:

class ClientFrame_connectButton_actionAdapter

implements java.awt.event.ActionListener {

  ClientFrame adaptee;

  ClientFrame_connectButton_actionAdapter(ClientFrame adaptee) {

    this.adaptee = adaptee;

  }

  public void actionPerformed(ActionEvent e) {

    adaptee.connectButton_actionPerformed(e);

  }

}

上述代码中,通过适配器模式将事件处理委托源目标实现,这就产生了第二种事件处理模式——委托模式。

委托模式使源目标、监视者和处理者3种角色各自实现为独立的3个对象,在这3个对象之间传输的是事件,这个模式在系统复杂时会经常使用,例如在J2EEB/S架构中,前台JSP的表单提交事件后,由MVC模式中的Servlet获得事件数据,经过简单封装成事件对象后,委托给后台EJB层实现进一步处理,如图1-2所示。

1-2中,客户端浏览器将表单事件直接提交到服务器端的JSP/ServletsJSP/Servlets作为事件的监视者,接收到事件后,并不对事件立即进行处理,而是委托给JavaBeans/EJB进行复杂的逻辑或运算处理。

nio

1-2  B/S多层结构委托模式

以上两种模式特点是至少有两个对象分别代表事件的3个角色,而在Reactor模式中,则是在一个对象中绑定了这3种角色,Reactor的意思是自我触发、自主激活的意思。

J2SE 1.4版本中的新特性非堵塞I/ONonblocking I/O)提供了基于Reactor模式的实现,这大大简化了基于Socket的应用和编程,如图1-3所示。

nio

1-3  Reactor模式

在非堵塞I/O API监视器是其一个重要的类Selector被监视的源目标是可以被Selector联系的SelectableChannel基本也属于Selector的相关部分),事件类型有是否有接受的连接OP_ACCEPT、是否可以连接OP_CONNECT、是否可以读取OP_READ和是否可以写入OP_WRITE

监视器Selector主要是监视这些事件,一旦发生,生成SelectionKey对象,Selector是自我触发、自我激活的,因此是典型的Reactor模式实现,其原理将在后面章节详细讨论。

但是,在非堵塞I/O API中,并不是由Selector来实现事件的处理,事件处理是由Selector激活出来后,通过其他处理器Handler来实现处理,开发者使用非堵塞I/O API需要做的工作就是:获取Selector激发的事件,然后根据相应事件类型,编制自己的处理器代码来进行具体处理。例如,如果是可读取事件,那么编制代码从SelectableChannel读取数据包,然后处理这个数据包。

在非堵塞I/O API中,使用Reactor模式将事件发生和事件处理两个部分实现分离解耦,事件发生部分只负责事件的激活,而事件处理由专门的处理器实现具体处理。

下页

EDA事件驱动架构

Reactor模式和NIO

Java NIO原理和使用

Netty原理和使用