引言

  宇宙大爆炸,于是开始了万物生衍,从一个连人渣都还没有的时代,一步步进化到如今的花花世界。

  然而沧海桑田,一百多亿年过去了….

  好复杂,但程序就简单多了,main()函数运行,敲个回车,一行Hello World就出来了,所以没事多敲敲回车,可以练手感….

  一、程序入口

  Java的程序入口是main方法,Openfire也不例外。可以全局检索一下”void main”,可以看到,Openfire的main函数有两个:

  (1)org.jivesoftware.openfire.launcher.Launcher类,以图形界面的形式启动

  1. public static void main(String[] args) throws AWTException {
  2. new Launcher();
  3. }

  (2)org.jivesoftware.openfire.starter.ServerStarter类,以服务的形式启动

  1. public static void main(String [] args) {
  2. new ServerStarter().start();
  3. }

  一般Openfire都做为服务的形式运行,所以我们重点关注ServerStarter类即可。如果是用eclipse调试Openfire工程,Run Configure中的Main Class也是设置这个类的完整路径。

  org.jivesoftware.openfire.starter.ServerStarter类

  1. |-- main()方法
  2. |-- start()

  ServerStarter.start( )方法加载配置,并用类加载的方法,实例化org.jivesoftware.openfire.XMPPServer。

  XMPPServer

  XMPPServer实例化之后,直接调用本类中的start( )方法,于是系统开始跑起来。运行逻辑如下:

  1. |-- XMPPServer()构造方法
  2. |-- start()
  3. |-- 加载 conf/openfire.xml,设置hostxmpp.domain等全局参数
  4. |-- 验证数据库是否可用
  5. |-- 加载、初始化、并启动openfire Module
  6. |-- 加载并启动 plugins
  7. |-- 启动监听服务:listener.serverStarted()

  下面从XMPPServer.start( )方法开始,跟一下Openfire在启动的过程中,处理了哪些业务,以此来分析Openfire启动流程。

  三、服务启动流程分析

  1、XMPPServer.start()方法

    添加了部分注释,如下:

  1. public void start() {
  2. try {
  3.  
  4. //初始化配置
  5. initialize();
  6. // 插件管理
  7. File pluginDir = new File(openfireHome, "plugins");
  8. pluginManager = new PluginManager(pluginDir);
  9.  
  10. if (!setupMode) {
  11. // 验证数据库是否可用,验证方法也很简单,就是执行一句sql语句,没有异常表示可用。
  12. verifyDataSource();
  13. //加载module
  14. loadModules();
  15. //初始化module
  16. initModules();
  17. //启动modeule
  18. startModules();
  19. }
  20. // 服务器流量计算类,用来计算服务器写入和读取的字节数,包括C-S,S-S或扩展的组件和连接的流量
  21. ServerTrafficCounter.initStatistics();
  22.  
  23. //启动插件
  24. pluginManager.start();
  25.  
  26. //打印启动日志
  27. String startupBanner = LocaleUtils.getLocalizedString("short.title") + " " + version.getVersionString() +
  28. " [" + JiveGlobals.formatDateTime(new Date()) + "]";
  29. logger.info(startupBanner);
  30. System.out.println(startupBanner);
  31.  
  32. started = true;
  33.  
  34. //通知其他的监听服务,服务器已启动
  35. for (XMPPServerListener listener : listeners) {
  36. listener.serverStarted();
  37. }
  38. }
  39. catch (Exception e) {
  40. e.printStackTrace();
  41. logger.error(e.getMessage(), e);
  42. System.out.println(LocaleUtils.getLocalizedString("startup.error"));
  43. shutdownServer();
  44. }
  45. }

  从上面代码和注释的内容,可以确确看出,启动的过程,主要就做了这几件事:

  (1)初始化

  (2)加载启动各个模块

  (3)加载启动各个插件

  (4)启动监听

  下面,分别括概性的分析这几个部分,目的是使读者对Openfire的启动有个大致的印象,即可。

  2、初始化,initialize()方法

  在Openfire的安装目录,有一个openfire.xml配置文件,初始化主要是将配置文件中的信息载入系统,并处理一些与进程和缓存等相关的工作。 

  1. private void initialize() throws FileNotFoundException {
  2. //用于确定openfire的工作目录,以及配置文件,并构造相应的File实例
  3. //这里不做深入,将处理的内容列举如下:
  4. //1. openfire配置文件所在的相对路径(conf/openfire.xml)。
  5. //2. 获取openfire工作目录的绝对路径homeProperty
  6. //3. 来获取配置文件的File实例verifyHome
  7. locateOpenfire();
  8.  
  9. startDate = new Date();
  10.  
  11. //获取计算机名
  12. try {
  13. host = InetAddress.getLocalHost().getHostName();
  14. }
  15. catch (UnknownHostException ex) {
  16. logger.warn("Unable to determine local hostname.", ex);
  17. }
  18. if (host == null) {
  19. host = "127.0.0.1";
  20. }
  21.  
  22. version = new Version(4, 0, 3, Version.ReleaseStatus.Release, -1);
  23. if ("true".equals(JiveGlobals.getXMLProperty("setup"))) {
  24. setupMode = false;
  25. }
  26.  
  27. if (isStandAlone()) {
  28. //设置服务器异常关机时执行的函数ShutdownHookThread(),当服务器异常关机时,必然会执行shutdownServer函数,关闭一些模块、执行一些监听函数等等
  29. logger.info("Registering shutdown hook (standalone mode)");
  30. Runtime.getRuntime().addShutdownHook(new ShutdownHookThread());
  31.  
  32. //启动一个定时线程,该线程监听控制台的输入,如果为“exit”,则调用System.exit退出openfire进程。
  33. TaskEngine.getInstance().schedule(new Terminator(), 1000, 1000);
  34. }
  35.  
  36. loader = Thread.currentThread().getContextClassLoader();
  37.  
  38. try {
  39. //初始化了org.jivesoftware.util.cache.DefaultLocalCacheStrategy实例,该实例和缓存有关
  40. CacheFactory.initialize();
  41. } catch (InitializationException e) {
  42. e.printStackTrace();
  43. logger.error(e.getMessage(), e);
  44. }
  45.  
  46. //migrateProperty()方法用于将数据从xml载入到数据库,并处理一些配置信息
  47. JiveGlobals.migrateProperty("xmpp.domain");
  48.  
  49. name = JiveGlobals.getProperty("xmpp.domain", host).toLowerCase();
  50.  
  51. JiveGlobals.migrateProperty(Log.LOG_DEBUG_ENABLED);
  52. Log.setDebugEnabled(JiveGlobals.getBooleanProperty(Log.LOG_DEBUG_ENABLED, false));
  53.  
  54. // Update server info
  55. //最后初始化了XMPPServerInfoImpl实例
  56. xmppServerInfo = new XMPPServerInfoImpl(name, host, version, startDate);
  57.  
  58. initialized = true;
  59. }

  3、Module和Plugin 的加载

  从XMPPServer.start( )的方法执行的内容来看,主要加载两大主体,一个是module,一个是plugin,这两部分可以说是整套系统的所有功能实现。下面对这两个部分,先做一个简述。具体的机制,在后续另起章节描述分析。

  (1)Module

  Openfire的核心功能都依靠module实现,所有的module都继承自BasicModule,而BasicModule实现了Module接口。

  Module接口类定义了如下方法列表:

  1. String getName();
  2. void initialize(XMPPServer server);
  3. void start();
  4. void stop();
  5. void destroy(); 

  从方法名可以看出,它描述了所有的module在整个生命周期内应调用的方法。

  而BaseModule则对Module进行了空实现,所有的module对BaseModule中的方法选择性覆写。

  各个module在XMPPServer启动之初,被装载在一个容器中:

  1. Map<Class, Module> modules

  通过递归的方式,调用module所覆写的initialize()、start()、stop()、destroy()等方法,实现对module的管理。

  module的加载,有一点需要留意下:ConnectionManager是在最后加载,源码中有如下代码段及注释:

  1. // Load this module always last since we don't want to start listening for clients
  2. // before the rest of the modules have been started
  3. loadModule(ConnectionManagerImpl.class.getName());

  Openfire的连接管理、端口监听,都在ConnectionManager这个模块中进行处理,这也是它为何要放在最后一个加载的原因。

  (2)plugin

  plugin的启动,是在module之后。

  pluginManager.start()方法中启动了PluginMonitor线程:

  1. public void start() {
  2. executor = new ScheduledThreadPoolExecutor(1);
  3. // See if we're in development mode. If so, check for new plugins once every 5 seconds.
  4. // Otherwise, default to every 20 seconds.
  5. if (Boolean.getBoolean("developmentMode")) {
  6. executor.scheduleWithFixedDelay(pluginMonitor, 0, 5, TimeUnit.SECONDS);
  7. }
  8. else {
  9. executor.scheduleWithFixedDelay(pluginMonitor, 0, 20, TimeUnit.SECONDS);
  10. }
  11. }

  线程的执行run()方法如下:

  1. @Override
  2. public void run() {
  3. ......
  4. try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(pluginDirectory, new DirectoryStream.Filter<Path>() {
  5. @Override
  6. public boolean accept(Path pathname) throws IOException {
  7. String fileName = pathname.getFileName().toString().toLowerCase();
  8. return (fileName.endsWith(".jar") || fileName.endsWith(".war"));
  9. }})) {
  10. for (Path jarFile : directoryStream) {
  11. ......
  12. if (Files.notExists(dir)) {
  13. unzipPlugin(pluginName, jarFile, dir);
  14. }
  15. ......
  16. }
  17. }
  18. ......
  19.  
  20. // Load all plugins that need to be loaded.
  21. for (Path dirFile : dirs) {
  22. if (Files.exists(dirFile) && !plugins.containsKey(dirFile.getFileName().toString())) {
  23. loadPlugin(dirFile);
  24. }
  25. }
  26. ......
  27.  
  28. // Trigger event that plugins have been monitored
  29. firePluginsMonitored();
  30. ......
  31. }

  PluginMonitor线程的主要处理:解压插件目录下所有拓展名为jar和war的插件,用loadPlugin( )装载该插件,最后通过firePluginsMonitored( )函数调用插件的监听函数。

  firePluginsMonitored( )方法中,调用插件的监听函数pluginsMonitored( ):

  1. private void firePluginsMonitored() {
  2. for(PluginManagerListener listener : pluginManagerListeners) {
  3. listener.pluginsMonitored();
  4. }
  5. }

  PluginManagerListener.pluginsMonitored()监听函数,在ConnetionMamagerImpl模块启动时实现。

  在ConnetionMamagerImpl.startListeners()方法,省略一些无关的代码,如下:

  1. private synchronized void startListeners()
  2. {
  3. PluginManager pluginManager = XMPPServer.getInstance().getPluginManager();
  4. if (!pluginManager.isExecuted()) {
  5. pluginManager.addPluginManagerListener(new PluginManagerListener() {
  6. public void pluginsMonitored() {
  7. ......
  8. }
  9. });
  10. return;
  11. }
  12. ......
  13. }

  也就解释了插件的启动是在module启动之后。

  事实上也可以理解:modules为openfire自带模块,plugins我们可以称为外来者。openfire需要对plugins进行管理、以及各种响应,那么自然需要其自身各个模块首先运作起来,这可以理解为一个主次顺序。

  4、最后,启动监听服务

  1. // Notify server listeners that the server has been started
  2. for (XMPPServerListener listener : listeners) {
  3. listener.serverStarted();
  4. }

  通知PubSubModule、PresenceManagerImpl、MultiUserChatServiceImpl等module监听启动。

  至此,openfire完成了启动。

 四、服务关闭

  讲了系统的启动,接下来稍微提一下系统的stop。

  服务关闭相对就简单一些,当收到控制能上的exit指令、或者启动过程之中出现了异常时, 就会调用关闭程序,通知其他的服务模块关闭监听、所有的模块和插件都停止并注销、关闭数据库资源、关闭线程的监听等。

  这里贴一下代码:

  1. private void shutdownServer() {
  2. shuttingDown = true;
  3. ClusterManager.shutdown();
  4. // Notify server listeners that the server is about to be stopped
  5. for (XMPPServerListener listener : listeners) {
  6. try {
  7. listener.serverStopping();
  8. } catch (Exception ex) {
  9. logger.error("Exception during listener shutdown", ex);
  10. }
  11. }
  12. // If we don't have modules then the server has already been shutdown
  13. if (modules.isEmpty()) {
  14. return;
  15. }
  16. logger.info("Shutting down " + modules.size() + " modules ...");
  17. // Get all modules and stop and destroy them
  18. for (Module module : modules.values()) {
  19. try {
  20. module.stop();
  21. module.destroy();
  22. } catch (Exception ex) {
  23. logger.error("Exception during module shutdown", ex);
  24. }
  25. }
  26. // Stop all plugins
  27. logger.info("Shutting down plugins ...");
  28. if (pluginManager != null) {
  29. try {
  30. pluginManager.shutdown();
  31. } catch (Exception ex) {
  32. logger.error("Exception during plugin shutdown", ex);
  33. }
  34. }
  35. modules.clear();
  36. // Stop the Db connection manager.
  37. try {
  38. DbConnectionManager.destroyConnectionProvider();
  39. } catch (Exception ex) {
  40. logger.error("Exception during DB shutdown", ex);
  41. }
  42.  
  43. // Shutdown the task engine.
  44. TaskEngine.getInstance().shutdown();
  45.  
  46. // hack to allow safe stopping
  47. logger.info("Openfire stopped");
  48. }

  

  OK,主干程序就分析到此。Openfire中的消息机制是怎么样的,各个模块是如何协作,插件又该怎么编写,在后续的章节中解答。

  希望这一系列的文章对您有所帮助,Over!

Openfire分析之二:主干程序分析的更多相关文章

  1. 即时通信系统Openfire分析之二:主干程序分析

    引言 宇宙大爆炸,于是开始了万物生衍,从一个连人渣都还没有的时代,一步步进化到如今的花花世界. 然而沧海桑田,一百多亿年过去了…. 好复杂,但程序就简单多了,main()函数运行,敲个回车,一行Hel ...

  2. vscode源码分析【二】程序的启动逻辑,第一个窗口是如何创建的

    上一篇文章:https://www.cnblogs.com/liulun/ (小广告:我做的开源免费的,个人知识管理及自媒体营销工具“想学吗”:https://github.com/xland/xia ...

  3. C#程序分析

    一.程序及问题 阅读下面程序,请回答如下问题: 问题1:这个程序要找的是符合什么条件的数? 问题2:这样的数存在么?符合这一条件的最小的数是什么? 问题3:在电脑上运行这一程序,你估计多长时间才能输出 ...

  4. vscode源码分析【四】程序启动的逻辑,最初创建的服务

    第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 第三篇:vscode源码分析[三]程序的启动逻辑,性能问题的追踪 ...

  5. vscode源码分析【三】程序的启动逻辑,性能问题的追踪

    第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 启动追踪 代码文件:src\main.js 如果指定了特定的启动参 ...

  6. Java练习小题_求一个3*3矩阵对角线元素之和,矩阵的数据用行的形式输入到计算机中 程序分析:利用双重for循环控制输入二维数组,再将a[i][i]累加后输出。

    要求说明: 题目:求一个3*3矩阵对角线元素之和,矩阵的数据用行的形式输入到计算机中 程序分析:利用双重for循环控制输入二维数组,再将 a[i][i] 累加后输出. 实现思路: [二维数组]相关知识 ...

  7. (IOS)BaiduFM 程序分析

    本文主要分享下楼主在学习Swift编程过程中,对GitHub上的一个开源app BaiduFM的研究心得. 项目地址:https://github.com/belm/BaiduFM-Swift 一.项 ...

  8. Linux内核--网络栈实现分析(二)--数据包的传递过程--转

    转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的”(上 ...

  9. Linux内核分析(二)----内核模块简介|简单内核模块实现

    原文:Linux内核分析(二)----内核模块简介|简单内核模块实现 Linux内核分析(二) 昨天我们开始了内核的分析,网上有很多人是用用源码直接分析,这样造成的问题是,大家觉得很枯燥很难理解,从某 ...

随机推荐

  1. SpringMVC原理及非注解配置详解

    1. Spring介绍 Spring MVC是Spring提供的一个强大而灵活的web框架.借助于注解,Spring MVC提供了几乎是POJO的开发模式,使得控制器的开发和测试更加简单. 这些控制器 ...

  2. IT软件管理人员的职业路线(从技术经理到总经理) - CEO之公司管理经验谈

    技术.业务和管理永远是工作的一个话题.笔者今天就根据自身的经验,通过这三个方面介绍下IT软件管理人员的职业路线.前面写过一个文(IT软件技术人员的职位路线(从程序员到技术总监) - 部门管理经验谈), ...

  3. bootstrap-table 怎么自定义搜索按钮实现点击按钮进行查询

    bootstrap-table自带搜索框感觉有点丑,我们可以把搜索功能单独拉出来放到页面的某一个位置. 首先我们看一下官方演示: 如果你感觉集成的检索框不太好看,而且我们也不想让搜索框和列表放到一块去 ...

  4. 【JAVASCRIPT】React 学习 - 登录实战

    摘要 实现一个登录的react 组件, 包含组件更新.ajax 交互.渲染新组建. 代码 <head> <meta charset="utf-8"> < ...

  5. 【HTML】section

    1.  定义 标签定义文档中的节(section.区段).比如章节.页眉.页脚或文档中的其他部分. 2. div.section . article的区别 div: 本身没有任何语义,用作布局以及样式 ...

  6. strtok函数 分类: c++ 2014-11-02 15:24 214人阅读 评论(0) 收藏

    strtok函数是cstring文件中的函数 strtok函数是cstring文件中的函数 其功能是截断字符串 原型为:char *strtok(char s[],const char *delin) ...

  7. webpack2 项目

    webpack2 项目   实例gif图: 目录截图: 目录介绍: dist目录(最后生成的目录,里面文件为配置webpack自动生成的): c/:css文件夹; i/:img文件夹; j/:js文件 ...

  8. MAC上安装EndNote破解版的链接文件 以及某些安装好后有可能替换执照文件的方法

    一款非常好用的论文写作软件不多形容,开整: X7 mac版本(非免破解版本)链接: 点击我 X8 mac版本(大客户版本,免破解非常好用) 点击我 X8 windows版本(大客户版本,免破解非常好用 ...

  9. UE4 C++BeginPlay And BlueprintBeginPlay

    今天遇到了一个诡异的问题,经过几个小时的煎熬终于找到了原因.mmmp 如果有一个类AActorChild,这个AActorChild继承自AActor,再有一个蓝图类BPAActorChild. 蓝图 ...

  10. Redis-简单实现星形主从配置

    高级参考(https://www.zhihu.com/question/21419897) 简单应用场景 现在配置redis 星形 集群, 有三台服务器, 怎样实现? 复制redis.conf两份, ...