高性能聊天系统
作者:板桥banq
1.6 应用层设计和实现
前面章节设计完成了网络连接的API。网络连接被封装在黑匣子中,从而实现了最大程度的可重用性,同时为各种应用系统建立了一个底层平台系统。下面简单地通过建立聊天测试应用系统,展示一下该平台的使用和运行。
聊天应用系统分客户端和服务器端,而服务器端有两种Socket实现:基于TCP和基于UDP。
下面将以客户端发送5个字符串信息到达TCP服务器,服务器处理后,再返回“come back again!”的字符串至客户端;然后客户端再发5个字符串到达UDP服务器,将接受同样的返回结果。
通过整个流程的测试,可以确定平台能够正常运行,而且客户端能够根据自己的选择决定使用TCP协议还是UDP协议。这两个协议可以一起使用,互相发挥各自的特长。
1.6.1 客户端聊天测试
客户端聊天应用层调用该模块测试代码如下(程序1-12):
程序1-12
public class ChatClient implements Runnable {
private final static String module = ChatClient.class.getName();
private ConnectionFactory connFactory = ConnectionFactory.getInstance(
ConnectionFactory.CLIENT);
public void run() {
try {
String url = "220.112.110.61";
int port = 81;
TcpConnect(url, port); //TCP连接测试
UdpConnect(url, 82); //UDP连接测试
} catch (Exception ex) {
Debug.logError(ex, module);
}
}
public void TcpConnect(String url, int port) throws Exception {
Debug.logVerbose(" --> begin to send TCP", module);
Connection conn = connFactory.getTcpConnection();
conn.open(url, port);
for (int i = 0; i < 5; i++) {
String msg = i + "helloI 碰 am Peng \r\n" + "this is two line" + i;
conn.writeString(msg); //发送数据
Debug.logVerbose("send TCP msg=" + msg, module);
}//TCP 请求发送完毕,下面准备接受服务器响应
System.out.println(" send TCP ok ................");
for (int i = 0; i < 5; i++) {
String result = conn.readString();
System.out.println(" TCP Response result =" + result);
}
conn.close();
}
public void UdpConnect(String url, int port) throws Exception {
Debug.logVerbose(" --> begin to send UDP", module);
Connection conn = connFactory.getUdpConnection();
conn.open(url, port);
for (int i = 5; i < 8; i++) {
//发送的字符串
String msg = i + "hello I am Peng \r\n" + "this is two line" + i;
conn.writeString(msg); //发送数据
Debug.logVerbose("send UDP msg=" + msg, module);
}//UDP 请求发送完毕,下面准备接受服务器响应
System.out.println(" send UDP ok ....................");
for (int i = 5; i < 8; i++) {
String result = conn.readString();
System.out.println("get UDP Response result =" + result);
}
conn.close();
}
public static void main(String[] args) {
Thread chat = new Thread(new ChatClient());
chat.start();
}
}
从ChatClient可以看出,对底层Socket操作变成了非常简单的几句调用,而且这几句接口Connection调用完全封装了Socket操作、Queue操作以及数据序列化或反序列化等操作。应该说是一个比较成功的接口API实现,主要功劳要归结于设计模式的成功使用。
1.6.2 服务器聊天测试
因为客户端同时进行TCP和UDP连接,所以服务器端要分别启动两种Socket连接,TCP连接如下:
public class ChatTCPServer implements Runnable {
private ConnectionFactory connFactory = ConnectionFactory.getInstance(
ConnectionFactory.TCPSERVER);
public void run() {
try {
while (true) {
tcpConnect();
}
} catch (Exception ex) {
}
}
private void tcpConnect() throws Exception {
Connection conn = connFactory.getTcpConnection();
//从客户端读取发送的信息
String clientMsg = conn.readString();
System.out.println("clientMsg==" + clientMsg);
//聊天逻辑处理核心
StringBuffer buff = new StringBuffer(clientMsg);
buff.append("come back again!");
String newclientMsg = buff.toString();
//向客户端发送响应信号
conn.writeString(newclientMsg);
System.out.println("send :" + newclientMsg);
}
public static void main(String[] args) {
Thread chat = new Thread(new ChatTCPServer());
chat.start();
}
}
UDP服务器和上述TCP服务器代码基本一致。不同的是connFactory的获取方式。其他对Connection的调用基本一致,UDP服务器代码如下:
public class ChatUDPServer implements Runnable {
private ConnectionFactory connFactory = ConnectionFactory.getInstance(
ConnectionFactory.UDPSERVER); //获得的是UDP连接
public void run() {
try {
while (true) {
udpConnect();
}
} catch (Exception ex) {
}
}
private void udpConnect() throws Exception {
Connection conn = connFactory.getUdpConnection(); //此处与TCP不同
//从客户端获取数据
String clientMsg = conn.readString();
System.out.println("clientMsg==" + clientMsg);
//聊天逻辑处理核心
StringBuffer buff = new StringBuffer(clientMsg);
buff.append("come back again!");
String newclientMsg = buff.toString();
//发送处理结果
conn.writeString(newclientMsg);
System.out.println("send :" + newclientMsg);
}
public static void main(String[] args) {
Thread chat = new Thread(new ChatUDPServer());
chat.start();
}
}
通过以上聊天测试案例可以发现,使用Connection和ConnectionFactory实现了对Socket的简单而且统一的调用,大大方便了应用系统的二次开发。
关于聊天系统的深入开发可以由有兴趣的读者进一步完成。
1.7 性能测试
由于本系统对性能要求相当高,因此必须对系统平台进行性能测试以及内存泄漏等检查。
性能测试一般需要借助第3方工具Jprofiler和Borland的Optimizeit。后者除了提供Profiler(全局性能测量系统)以外,还有Thread Debugger(线程调试程序)和Code Coverage(代码覆盖)等功能。
下面将使用Optimizeit的Profiler来检查一下本系统平台底层的性能指标。打开Profiler,选择StartUp的Application,从Browser中选择本系统的ChatUDPServer;选中Options中的Open console,可以从控制台看到运行状态,在Source Path栏目中加入本系统源码所在的目录,如D:\Jserver\src;同时在Class Path中加入本系统支持的库文件,否则不能运行。
从Profiler中运行ChatUDPServer,然后启动客户端测试程序,不断地循环发送数据,观察服务器的Profiler。
首先,检查CPU Profiler,看看是什么程序占据CPU时间比较长,启动记录器,记录一段时间后停止,打开System条目,如图1-10所示。
图1-10 CPU Profiler
黑条表示CPU的56%执行ChatUDPServer的udpConect()方法。双击黑条,可以查看具体源代码所在的行,由于updConnect方法是服务器不断读取数据和处理发送数据,因此理所当然应该占据CPU的大部分利用率。
通过CPU Profiler可以发现是否有非主要功能在浪费CPU,如果是这样,就要从设计上重新考虑和设计了。
第二步进行内存监测,可以查看是否有内存泄漏,如图1-11所示。
图1-11 内存监测
图1-11显示了每个类的对象数目大小。在运行过程中,这个数字是不断变大增长的,但是通过自动的垃圾收集,这个数目又会自动降低;如果系统中有无法被垃圾回收机制回收的对象,那么这个数目会不断增长,这就表示该系统有内存泄漏发生了。
内存泄漏通常是代码使用Collection这些集合,将对象引用放入后,没有将其取出,致使这些对象无法被垃圾回收机制回收,因为垃圾回收机制只回收那些内存中没有任何一个地方对之进行引用的对象。即只要有一个地方实行了引用,这个对象就无法被回收,通过长时间运行,内存被这些垃圾对象消耗殆尽,发生内存泄漏。
双击图1-11中任何一个Class,特别是Object[],可以发现有哪些具体的类使用了这些Class。如果发生内存泄漏,可以追寻到具体代码,这样可帮助开发者在巨大代码中找到内存泄漏的地方。如果没有Optimizeit这样的工具帮助,寻找内存泄漏的代码犹如大海捞针。
性能测试是Java系统必须单独进行的一项测试,它一般是在功能测试以后实行,通过这两项测试,才能保证软件产品的成熟和稳定。很多Java系统并不重视性能测试,所以交给客户运行后,经常发生运行一段时间死机等问题,或者访问量一旦增加,速度性能就变慢,甚至死机,这些单纯通过增加服务器硬件投资也是无法解决的,必须进行严谨的性能测试。
1.8 小结
本章主要通过一个聊天系统底层平台的建立,介绍了Java系统中有关性能设计和开发的相关知识,探讨了如何在性能设计和可重用性设计这两者之间实现平衡考虑。
Java多线程机制虽然属于非常底层的机制,在以后的J2EE项目开发中,基本不涉及到线程概念,但是,J2EE的运行平台却是构架在Java的多线程等多种底层机制基础上。JSP/Servlet的使用实际就是线程的使用,因此,了解掌握Java线程和多线程概念,是J2EE实战技术中不可或缺的一个环节。
本章通过聊天系统的演练,展示了Java线程使用技巧,同时讨论了一种基于线程的、数据处理的队列Queue机制。
Java的线程技术在一些小型或嵌入式系统有相当广泛的应用,例如Java的Application或Applet以及J2ME等客户端开发中。在服务器端,除非是对实时运行性能要求苛刻的专用系统,在更多基于数据库的信息系统项目中,J2EE框架还是首选的架构设计。
J2EE应用系统虽然在单机性能上落后于本章介绍的基于线程和Socket直接开发的实时系统,但是,J2EE提供的分布式、集群多服务器计算环境将提供比单机性能更加强大的运行性能。更主要的是,在软件设计的可重用性、可维护性方面要远胜于直接使用线程设计的系统。这也是大多数应用系统使用Java这样的高级语言编制,而不是使用汇编等这样低级语言编制一样,计算机软件的设计质量在硬件飞速发展的今天已经变得更重要了。
全文完