即时通信系统Openfire分析之二:主干程序分析
引言
宇宙大爆炸,于是开始了万物生衍,从一个连人渣都还没有的时代,一步步进化到如今的花花世界。
然而沧海桑田,一百多亿年过去了….
好复杂,但程序就简单多了,main()函数运行,敲个回车,一行Hello World就出来了,所以没事多敲敲回车,可以练手感….
一、程序入口
Java的程序入口是main方法,Openfire也不例外。可以全局检索一下”void main”,可以看到,Openfire的main函数有两个:
(1)org.jivesoftware.openfire.launcher.Launcher类,以图形界面的形式启动
public static void main(String[] args) throws AWTException {
new Launcher();
}
(2)org.jivesoftware.openfire.starter.ServerStarter类,以服务的形式启动
public static void main(String [] args) {
new ServerStarter().start();
}
一般Openfire都做为服务的形式运行,所以我们重点关注ServerStarter类即可。如果是用eclipse调试Openfire工程,Run Configure中的Main Class也是设置这个类的完整路径。
org.jivesoftware.openfire.starter.ServerStarter类
|-- main()方法
|-- start()
ServerStarter.start( )方法加载配置,并用类加载的方法,实例化org.jivesoftware.openfire.XMPPServer。
XMPPServer
XMPPServer实例化之后,直接调用本类中的start( )方法,于是系统开始跑起来。运行逻辑如下:
|-- XMPPServer()构造方法
|-- start()
|-- 加载 conf/openfire.xml,设置host、xmpp.domain等全局参数
|-- 验证数据库是否可用
|-- 加载、初始化、并启动openfire Module
|-- 加载并启动 plugins
|-- 启动监听服务:listener.serverStarted()
下面从XMPPServer.start( )方法开始,跟一下Openfire在启动的过程中,处理了哪些业务,以此来分析Openfire启动流程。
三、服务启动流程分析
1、XMPPServer.start()方法
添加了部分注释,如下:
public void start() {
try { //初始化配置
initialize();
// 插件管理
File pluginDir = new File(openfireHome, "plugins");
pluginManager = new PluginManager(pluginDir); if (!setupMode) {
// 验证数据库是否可用,验证方法也很简单,就是执行一句sql语句,没有异常表示可用。
verifyDataSource();
//加载module
loadModules();
//初始化module
initModules();
//启动modeule
startModules();
}
// 服务器流量计算类,用来计算服务器写入和读取的字节数,包括C-S,S-S或扩展的组件和连接的流量
ServerTrafficCounter.initStatistics(); //启动插件
pluginManager.start(); //打印启动日志
String startupBanner = LocaleUtils.getLocalizedString("short.title") + " " + version.getVersionString() +
" [" + JiveGlobals.formatDateTime(new Date()) + "]";
logger.info(startupBanner);
System.out.println(startupBanner); started = true; //通知其他的监听服务,服务器已启动
for (XMPPServerListener listener : listeners) {
listener.serverStarted();
}
}
catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
System.out.println(LocaleUtils.getLocalizedString("startup.error"));
shutdownServer();
}
}
从上面代码和注释的内容,可以确确看出,启动的过程,主要就做了这几件事:
(1)初始化
(2)加载启动各个模块
(3)加载启动各个插件
(4)启动监听
下面,分别括概性的分析这几个部分,目的是使读者对Openfire的启动有个大致的印象,即可。
2、初始化,initialize()方法
在Openfire的安装目录,有一个openfire.xml配置文件,初始化主要是将配置文件中的信息载入系统,并处理一些与进程和缓存等相关的工作。
private void initialize() throws FileNotFoundException {
//用于确定openfire的工作目录,以及配置文件,并构造相应的File实例
//这里不做深入,将处理的内容列举如下:
//1. openfire配置文件所在的相对路径(conf/openfire.xml)。
//2. 获取openfire工作目录的绝对路径homeProperty
//3. 来获取配置文件的File实例verifyHome
locateOpenfire(); startDate = new Date(); //获取计算机名
try {
host = InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException ex) {
logger.warn("Unable to determine local hostname.", ex);
}
if (host == null) {
host = "127.0.0.1";
} version = new Version(4, 0, 3, Version.ReleaseStatus.Release, -1);
if ("true".equals(JiveGlobals.getXMLProperty("setup"))) {
setupMode = false;
} if (isStandAlone()) {
//设置服务器异常关机时执行的函数ShutdownHookThread(),当服务器异常关机时,必然会执行shutdownServer函数,关闭一些模块、执行一些监听函数等等
logger.info("Registering shutdown hook (standalone mode)");
Runtime.getRuntime().addShutdownHook(new ShutdownHookThread()); //启动一个定时线程,该线程监听控制台的输入,如果为“exit”,则调用System.exit退出openfire进程。
TaskEngine.getInstance().schedule(new Terminator(), 1000, 1000);
} loader = Thread.currentThread().getContextClassLoader(); try {
//初始化了org.jivesoftware.util.cache.DefaultLocalCacheStrategy实例,该实例和缓存有关
CacheFactory.initialize();
} catch (InitializationException e) {
e.printStackTrace();
logger.error(e.getMessage(), e);
} //migrateProperty()方法用于将数据从xml载入到数据库,并处理一些配置信息
JiveGlobals.migrateProperty("xmpp.domain"); name = JiveGlobals.getProperty("xmpp.domain", host).toLowerCase(); JiveGlobals.migrateProperty(Log.LOG_DEBUG_ENABLED);
Log.setDebugEnabled(JiveGlobals.getBooleanProperty(Log.LOG_DEBUG_ENABLED, false)); // Update server info
//最后初始化了XMPPServerInfoImpl实例
xmppServerInfo = new XMPPServerInfoImpl(name, host, version, startDate); initialized = true;
}
3、Module和Plugin 的加载
从XMPPServer.start( )的方法执行的内容来看,主要加载两大主体,一个是module,一个是plugin,这两部分可以说是整套系统的所有功能实现。下面对这两个部分,先做一个简述。具体的机制,在后续另起章节描述分析。
(1)Module
Openfire的核心功能都依靠module实现,所有的module都继承自BasicModule,而BasicModule实现了Module接口。
Module接口类定义了如下方法列表:
String getName();
void initialize(XMPPServer server);
void start();
void stop();
void destroy();
从方法名可以看出,它描述了所有的module在整个生命周期内应调用的方法。
而BaseModule则对Module进行了空实现,所有的module对BaseModule中的方法选择性覆写。
各个module在XMPPServer启动之初,被装载在一个容器中:
Map<Class, Module> modules
通过递归的方式,调用module所覆写的initialize()、start()、stop()、destroy()等方法,实现对module的管理。
module的加载,有一点需要留意下:ConnectionManager是在最后加载,源码中有如下代码段及注释:
// Load this module always last since we don't want to start listening for clients
// before the rest of the modules have been started
loadModule(ConnectionManagerImpl.class.getName());
Openfire的连接管理、端口监听,都在ConnectionManager这个模块中进行处理,这也是它为何要放在最后一个加载的原因。
(2)plugin
plugin的启动,是在module之后。
pluginManager.start()方法中启动了PluginMonitor线程:
public void start() {
executor = new ScheduledThreadPoolExecutor(1);
// See if we're in development mode. If so, check for new plugins once every 5 seconds.
// Otherwise, default to every 20 seconds.
if (Boolean.getBoolean("developmentMode")) {
executor.scheduleWithFixedDelay(pluginMonitor, 0, 5, TimeUnit.SECONDS);
}
else {
executor.scheduleWithFixedDelay(pluginMonitor, 0, 20, TimeUnit.SECONDS);
}
}
线程的执行run()方法如下:
@Override
public void run() {
......
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(pluginDirectory, new DirectoryStream.Filter<Path>() {
@Override
public boolean accept(Path pathname) throws IOException {
String fileName = pathname.getFileName().toString().toLowerCase();
return (fileName.endsWith(".jar") || fileName.endsWith(".war"));
}})) {
for (Path jarFile : directoryStream) {
......
if (Files.notExists(dir)) {
unzipPlugin(pluginName, jarFile, dir);
}
......
}
}
...... // Load all plugins that need to be loaded.
for (Path dirFile : dirs) {
if (Files.exists(dirFile) && !plugins.containsKey(dirFile.getFileName().toString())) {
loadPlugin(dirFile);
}
}
...... // Trigger event that plugins have been monitored
firePluginsMonitored();
......
}
PluginMonitor线程的主要处理:解压插件目录下所有拓展名为jar和war的插件,用loadPlugin( )装载该插件,最后通过firePluginsMonitored( )函数调用插件的监听函数。
firePluginsMonitored( )方法中,调用插件的监听函数pluginsMonitored( ):
private void firePluginsMonitored() {
for(PluginManagerListener listener : pluginManagerListeners) {
listener.pluginsMonitored();
}
}
PluginManagerListener.pluginsMonitored()监听函数,在ConnetionMamagerImpl模块启动时实现。
在ConnetionMamagerImpl.startListeners()方法,省略一些无关的代码,如下:
private synchronized void startListeners()
{
PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
if (!pluginManager.isExecuted()) {
pluginManager.addPluginManagerListener(new PluginManagerListener() {
public void pluginsMonitored() {
......
}
});
return;
}
......
}
也就解释了插件的启动是在module启动之后。
事实上也可以理解:modules为openfire自带模块,plugins我们可以称为外来者。openfire需要对plugins进行管理、以及各种响应,那么自然需要其自身各个模块首先运作起来,这可以理解为一个主次顺序。
4、最后,启动监听服务
// Notify server listeners that the server has been started
for (XMPPServerListener listener : listeners) {
listener.serverStarted();
}
通知PubSubModule、PresenceManagerImpl、MultiUserChatServiceImpl等module监听启动。
至此,openfire完成了启动。
四、服务关闭
讲了系统的启动,接下来稍微提一下系统的stop。
服务关闭相对就简单一些,当收到控制能上的exit指令、或者启动过程之中出现了异常时, 就会调用关闭程序,通知其他的服务模块关闭监听、所有的模块和插件都停止并注销、关闭数据库资源、关闭线程的监听等。
这里贴一下代码:
private void shutdownServer() {
shuttingDown = true;
ClusterManager.shutdown();
// Notify server listeners that the server is about to be stopped
for (XMPPServerListener listener : listeners) {
try {
listener.serverStopping();
} catch (Exception ex) {
logger.error("Exception during listener shutdown", ex);
}
}
// If we don't have modules then the server has already been shutdown
if (modules.isEmpty()) {
return;
}
logger.info("Shutting down " + modules.size() + " modules ...");
// Get all modules and stop and destroy them
for (Module module : modules.values()) {
try {
module.stop();
module.destroy();
} catch (Exception ex) {
logger.error("Exception during module shutdown", ex);
}
}
// Stop all plugins
logger.info("Shutting down plugins ...");
if (pluginManager != null) {
try {
pluginManager.shutdown();
} catch (Exception ex) {
logger.error("Exception during plugin shutdown", ex);
}
}
modules.clear();
// Stop the Db connection manager.
try {
DbConnectionManager.destroyConnectionProvider();
} catch (Exception ex) {
logger.error("Exception during DB shutdown", ex);
} // Shutdown the task engine.
TaskEngine.getInstance().shutdown(); // hack to allow safe stopping
logger.info("Openfire stopped");
}
OK,主干程序就分析到此。Openfire中的消息机制是怎么样的,各个模块是如何协作,插件又该怎么编写,在后续的章节中解答。
希望这一系列的文章对您有所帮助,Over!
即时通信系统Openfire分析之二:主干程序分析的更多相关文章
- Openfire分析之二:主干程序分析
引言 宇宙大爆炸,于是开始了万物生衍,从一个连人渣都还没有的时代,一步步进化到如今的花花世界. 然而沧海桑田,一百多亿年过去了-. 好复杂,但程序就简单多了,main()函数运行,敲个回车,一行Hel ...
- 基于XMPP的即时通信系统的建立(二)— XMPP详解
XMPP详解 XMPP(eXtensible Messaging and Presence Protocol,可扩展消息处理和现场协议)是一种在两个地点间传递小型结构化数据的协议.在此基础上,XMPP ...
- 即时通信系统Openfire分析之六:路由表 RoutingTable
还是从会话管理说起 上一章,Session经过预创建.认证之后,才正常可用.认证时,最重要的操作,就是将Session加入到路由表,使之拥用了通信功能. 添加到至路由表的操作,是在SessionMan ...
- 即时通信系统Openfire分析之三:ConnectionManager 连接管理
Openfire是怎么实现连接请求的? XMPPServer.start()方法,完成Openfire的启动.但是,XMPPServer.start()方法中,并没有提及如何监听端口,那么Openfi ...
- 即时通信系统Openfire分析之一:Openfire与XMPP协议
引言 目前互联网产品使用的即时通信协议有这几种:即时信息和空间协议(IMPP).空间和即时信息协议(PRIM).针对即时通讯和空间平衡扩充的进程开始协议SIP(SIMPLE)以及XMPP.PRIM与 ...
- 即时通信系统Openfire分析之四:消息路由
两个人的孤独 两个人的孤独,大抵是,你每发出去一句话,都要经由无数网络.由几百个计算机处理后,出在他的面前,而他就在你不远处. 连接管理之后 Openfire使用MINA网络框架,并设置Connect ...
- 即时通信系统Openfire分析之五:会话管理
什么是会话? A拨了B的电话 电话接通 A问道:Are you OK? B回复:I have a bug! A挂了电话 这整个过程就是会话. 会话(Session)是一个客户与服务器之间的不中断的请求 ...
- 即时通信系统Openfire分析之七:集群配置
前言 写这章之前,我犹豫了一会.在这个时候提集群,从章节安排上来讲,是否合适?但想到上一章<路由表>的相关内容,应该不至于太突兀.既然这样,那就撸起袖子干吧. Openfire的单机并发量 ...
- 即时通信系统Openfire分析之八:集群管理
前言 在第六章<路由表>中,客户端进行会话时,首先要获取对方的Session实例.获取Session实例的方法,是先查找本地路由表,若找不到,则通过路由表中的缓存数据,由定位器获取. 路由 ...
随机推荐
- Codeforces Round #105 (Div. 2) D. Bag of mice 概率dp
题目链接: http://codeforces.com/problemset/problem/148/D D. Bag of mice time limit per test2 secondsmemo ...
- 【Coursera】因子分析模型
一.协方差矩阵 协方差矩阵为对称矩阵. 在高斯分布中,方差越大,数据分布越分散,方差越小发,数据分布越集中. 在协方差矩阵中,假设矩阵为二维,若第二维的方差大于第一维的方差,则在图像上的体现就是:高斯 ...
- java中方法传入参数时:值传递还是址传递?
JAVA中的数据类型有两大类型: ① 基本数据类型:逻辑型(boolean).文本型(char).整数型(byte.short.int.long).浮点型(float.double) ② 引用数据类型 ...
- Maya学习笔记
软件: Maya 2016 : 参考教材: Maya 2016 中文版标准教程 ; 改变视图颜色 [窗口]|[设置/首项选择]|[颜色设置]|[3D视图]: 观察视图 旋转视图 Alt + 鼠标左键 ...
- Alpha版本冲刺(五)
目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:翟丹丹 组员7:何家伟 组员8:政演 组员9:黄鸿杰 组员10:刘一好 组员11:何宇恒 展示 ...
- C语言词频统计设计
项目需求: 1.设计一个词频统计小软件,对给定的英文文章进行单词频率的统计. 2.文章中相应的标点不计入统计. 3.将统计结果以从大到小的排序方式输出. 设计: 1.因为功能相对简单,采用C语言直接进 ...
- GDI+缩放图片
uses WinAPI.GDIPAPI, WinAPI.GDIPOBJ; var Bitmap1: TGPBitmap; Bitmap2: TBitmap; Graphic: TGPGraphi ...
- Ubuntu安装使用中的一些注意事项
在win7上安装VMware workstations10.0 ,在VMware workstations10.0上安装Ubuntu14.04 64位时,关于网络的连接注意: win7 网络连接里上的 ...
- C语言版kafka消费者代码运行时异常kafka receive failed disconnected
https://github.com/edenhill/librdkafka/wiki/Broker-version-compatibility如果使用了broker版本是0.8的话, 在运行例程时需 ...
- 利用ZooKeeper简单实现分布式锁
1.分布式锁的由来: 在程序开发过程中不得不考虑的就是并发问题.在java中对于同一个jvm而言,jdk已经提供了lock和同步等.但是在分布式情况下,往往存在多个进程对一些资源产生竞争关系,而这些进 ...