• 即时通信系统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!

  • 相关阅读:
    MySQL主主同步方案
    Mysql增量备份与恢复
    配置合适的存储引擎
    基于Amoeba读写分离
    部署myaql主从异步复制
    MySQL完全备份操作
    echo 命令详解
    ELK 基本部署
    zabbix 简介
    基于 Git Tag 发布及回滚代码
  • 原文地址:https://www.cnblogs.com/Fordestiny/p/7465185.html
Copyright © 2020-2023  润新知