NJ4X源码阅读分析笔记系列(二)—— nj4x-ts初步分析

nj4x-ts简介

nj4x-ts是NJ4X项目中的终端服务器,参考我的上篇博客NJ4X源码阅读分析笔记系列(一)——项目整体分析中的图:

方框中就是nj4x-ts的角色。

nj4x-ts起着承上启下的作用,nj4x-ts提供了面向中断的网络通讯接口,操作底层的C++函数操作mt4的程序。然后让整个系统跑起来。

包结构

包结构如图:

作用如下:

  • gui 界面
  • io 引用dll库,文件操作
  • jmx JMX服务器
  • net 网络通讯相关
  • xml xml操作类

编译

编译的时候,会出现问题,大致就是找不到dll,到时候要注意调整dll的路径,编译会直接生成课执行jar包,exe可执行程序程序,如下图:

其中antrun文件夹下是ant的编译脚本。

这样就编译完了整个项目。

启动过程

nj4x-ts的启动过程非常值得一提。

入口函数

入口函数在com.jfx.ts.net.TerminalServer类中。

首先开始读取main函数的参数args,我们一般不给这个写参数。

if (args.length % 2 == 0) {
                for (int i = 0; i < args.length; i++) {
                    String pn = args[i++];
                    String pv = args[i];
                    System.setProperty(pn, pv);   //指定系统的全局属性
                }
            }

然后加载dll库:

LibrariesUtil.initEmbeddedLibraries();   

initEmbeddedLibraries()加载dll之前,LibrariesUtil有一个静态块,静态块在构造函数之前运行,用以判断操作系统的位数,以及初始化程序的相关路径:

static {
        String osName = System.getProperty("os.name");
        isWindows = osName.toLowerCase().contains("windows");
        String arch = System.getProperty("os.arch");
        isX64 = arch.contains("amd64") || arch.contains("x64");
        LIBS_DIR = System.getProperty("program_data_dir", "C:\\ProgramData\\nj4x") + "\\bin";
//        LIBS_DIR = System.getProperty("user.dir");
    }

然后再调用initEmbeddedLibraries()函数,根据位数选择相应的dll。奇怪的是,作者并没有直接复制dll,而是直接二进制读取原路径的dll,然后在目标路径新建一个dll,把二进制写入新的dll文件,然后再System.load(),很奇怪,也许作者这样做是想确保每次的dll都是最新的。代码如下:

final File libFile = new File(libFileName);   //     C:\ProgramData\nj4x\bin\PSUtils_x64.dll
//
final InputStream in = nativeLibraryUrl.openStream();
final OutputStream out = new BufferedOutputStream(new FileOutputStream(libFile));
//
int len;
byte[] buffer = new byte[160000];
while ((len = in.read(buffer)) > -1)
    out.write(buffer, 0, len);  //写入dll文件
out.close();
in.close();
//
System.load(libFile.getAbsolutePath()); //加载PSUtil_X64.dll这个dll

至此,dll加载完毕。

接下来就判断程序是不是以管理员身份登录的:

asAdministrator = PSUtils.asAdministrator();

具体方法在com.jfx.ts.io.PSUtils中:

public static boolean asAdministrator() {
    if (!isAdministrator()) {
        String compat_layer = System.getenv().get("__COMPAT_LAYER");
        if (compat_layer == null || !compat_layer.contains("RunAsAdmin")) {
            return false;
        }
    }
    return true;
}

isAdministrator()是一个native静态方法:

public static native boolean isAdministrator();

这个方法是使用的PSUtils_x64.dll的native方法。因为com.jfx.ts.io.PSUtils中有一个静态块:

// Load the dll that exports functions callable from java
static {
    if (!LibrariesUtil.IS_OK) {
        String dllFileName = null;
        try {
            if (LibrariesUtil.isX64) {
                dllFileName = "PSUtils_x64.dll";
            } else {
                dllFileName = "PSUtils.dll";
            }
            String libFileName = LibrariesUtil.LIBS_DIR + File.separator + 
            System.load(libFileName);
        } catch (Throwable t) {
            t.printStackTrace();
            try {
                System.load(dllFileName);
            } catch (Exception e) {
                System.exit(2);
            }
        }
    }
}

没错,这就是战斗民族的代码。

接下来就是确定最大线程数,不知道战斗民族为什么把代码写的如此复杂:

 //最大线程数,不知道为什么要搞得这么复杂
            MAX_TERMINAL_STARTUP_THREADS = Integer.parseInt(System.getProperty("max_terminal_connection_threads", "" +
                    (AVAILABLE_PROCESSORS >= 24 ? AVAILABLE_PROCESSORS / 2
                            : (AVAILABLE_PROCESSORS >= 12 ? AVAILABLE_PROCESSORS / 3
                            : (AVAILABLE_PROCESSORS > 3 ? AVAILABLE_PROCESSORS - 2
                            : AVAILABLE_PROCESSORS)))));

然后判断,要不要启动专家系统,默认是不启动的。

IS_DEPLOY_EA_WS = System.getProperty("deploy_EA_WS", "false").equals("true");

作者在代码中大量的使用System.getProperty()方法操作参数,思路不错。

真正的部署程序在这个方面里面

 deploy(port);

deploy(port)方法首先打印出所有的系统环境变量和程序设置的参数:

Logger logger = TS.LOGGER;
//
logger.info("--");
logger.info("--");
logger.info("--");
logger.info("-- TS " + TS.NJ4X + " STARTUP --");
logger.info("-- " + TS.NJ4X_UUID + " --");
logger.info("--");
logger.info("--");
logger.info("--");
logger.info("-- System properties --");
for (Map.Entry e : new TreeMap<>(System.getProperties()).entrySet()) {
    logger.info("" + e.getKey() + "=" + e.getValue());
}
logger.info("-- Environment --");
for (Map.Entry e : new TreeMap<>(System.getenv()).entrySet()) {
    logger.info("" + e.getKey() + "=" + e.getValue());
}
logger.info("-- Deployment --");

然后实例化TS类,TS作用下一节再分析。

接下来就部署WebService服务,具体由TsWS类实现。

关于nj4x-ts的网络通信部分,会在下一篇博客中做介绍,在此略过。

但是作者在最后又默认启动专家系统的webserive,不知道为什么,现在没有计划研究专家系统,以后有可能。

//部署专家系统webservice
deployEaWs(true);

总之,启动后的样子:

这个界面就不介绍了,懂的人自然懂。

TS类的分析

TS类要单独拿出来分析,因为,以上的启动过程都是表面上的,程序启动真正干活的是TS类。

TS类中的各个静态变量,可以自己看。

首先,TS类有三个静态块:

第一个我不知道是干嘛用的,目的不明确:

static {
    try {
        //不知道为什么要外部程序,不知道是干什么的,跑起来之后看看hostname是个什么鬼
        //但是这个是简化外部当前JVM进程的执行。
        ExternalProcess p = new ExternalProcess("hostname");
        p.run();
        TS.hostname = p.getOut().trim();
    } catch (Exception e) {
        TS.LOGGER.error("hostname cmd error", e);
        TS.hostname = System.getenv("COMPUTERNAME");
    }

}

第二个初始化JFX的HOME路径:

static {
    //貌似是初始化JFX的HOME路径
    JFX_HOME = System.getProperty("home", System.getProperty("user.hom
    System.setProperty("home", JFX_HOME);
    String tmout = System.getenv("JFX_TERM_IDLE_TMOUT_SECONDS");
    if (tmout == null) {
        JFX_TERM_IDLE_TMOUT_SECONDS = 3600 * 6;
    } else {
        JFX_TERM_IDLE_TMOUT_SECONDS = Long.parseLong(tmout);
    }
}

第三个非常重要,其作用是初始化程序运行的各种路径文件:

static {
    //mkdir函数,如果存在就返回false
    new File(getTargetTermDir()).mkdirs();
    //
    new File(JFX_HOME_CONFIG).mkdirs();
    new File(JFX_HOME_SRV_DIR).mkdirs();
    new File(JFX_HOME_EA_DIR).mkdirs();
    new File(JFX_HOME_EXPERTS_DIR).mkdirs();
    new File(JFX_HOME_INDICATORS_DIR).mkdirs();
    new File(JFX_HOME_CHR_DIR).mkdirs();
    //
    TerminalClient terminalClient = null;
    try {
        terminalClient = new TerminalClient("127.0.0.1", Integer.parseInt(System.getProperty("port", "7788")));
        TS.P_GUI_ONLY = true;
        if (!TS.P_USE_MSTSC) {
            String mode = terminalClient.ask(ClientWorkerThread.GETMODE).toLowerCase();
            if (mode.contains("mstsc=true")) {
                TS.P_USE_MSTSC = true;
            }
        }
    } catch (IOException e) {
        //ignore, seems this is 1st TS instance or port is used by another app
    } finally {
        if (terminalClient != null) {
            try {
                terminalClient.close();
            } catch (IOException ignore) {
            }
        }
    }
    //
    LOGGING_CONFIG_XML = JFX_HOME_CONFIG + File.separatorChar + (P_GUI_ONLY ? "gui_" : "") + "logging.xml";
    if (!new File(LOGGING_CONFIG_XML).exists()) {
        try {
            String loggingXml = ResourceReader.getClassResourceReader(TerminalServer.class, true).getProperty("logging.xml");
            if (P_GUI_ONLY) {
                loggingXml = loggingXml.replace("jfx_term.log", "gui_jfx_term.log");
            }
            writeFile(LOGGING_CONFIG_XML, loggingXml.replace("./jfx_term/", JFX_HOME.replace('\\', '/') + "/").getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //
    if (!new File(JMX_CONFIG_XML).exists()) {
        try {
            writeFile(JMX_CONFIG_XML, ResourceReader.getClassResourceReader(TerminalServer.class).getProperty("mbean_config.xml").getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //
    DOMConfigurator.configureAndWatch(LOGGING_CONFIG_XML);
    LOGGER = Logger.getLogger(TS.class);
    //
    String termDirLn = System.getenv("SystemDrive") + "\\." + System.getProperty("port", "7788");
    File tmpDirLnk = new File(termDirLn);
    if (tmpDirLnk.exists() && !P_GUI_ONLY) {
        String linkDir = getLinkDir(tmpDirLnk);
        if (linkDir == null || !new File(getTargetTermDir()).equals(new File(linkDir))) {
            tmpDirLnk.delete();
        }
    }
    if (!tmpDirLnk.exists()) {
        tmpDirLnk.delete();
        ExternalProcess mklink = new ExternalProcess("cmd", "/C", "mklink", "/J", termDirLn, getTargetTermDir());
        try {
            mklink.run();
            TERM_DIR = tmpDirLnk.exists() ? termDirLn : null;
        } catch (Exception e) {
            e.printStackTrace();
        }
    } else {
        TERM_DIR = termDirLn;
    }
    //
    tsConfig = new TSConfig();
}

要注意几个路径:

C:\ProgramData\nj4x\bin目前看来只包含PSUtils_x64.dll文件。

C:\Users\Micheal\jfx_term文件夹比较复杂,总之就是包含了jfx所有的配置文件,如图:

chr文件夹是空的,不知道干嘛的。

config文件夹中,是一些配置文件,logging.xmllog4j的配置文件。配置方式都不一样,战斗民族;mbean-config.xml文件是网络配置文件,下次博客再讲;ts_config.xml文件里面就有一句话,不知道什么意思:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>Properties removed at Thu Nov 17 16:28:14 CST 2016</comment>
</properties>

log文件夹中是日志。

srv文件夹中是各个交易商的配置文件,这个和交易商有关:

zero_term文件夹中存放着一份mt4的程序。没有提到的文件夹就是空的。

C:\Users\Micheal\.jfx_terminals这个文件夹中存放的是mt4的程序,每一个账号,每一个交易商的不同服务器都会创建一份新的程序,应该是配置文件不同。

TS.scheduledExecutorService分析。

作者在Ts类中定义了一个scheduledExecutorService

/**
     * The constant scheduledExecutorService.
     */
    public static ScheduledExecutorService scheduledExecutorService
            = java.util.concurrent.Executors.newScheduledThreadPool(64);

据我统计,有10个地方用到了这个scheduledExecutorService,也就是至少有10个定时任务。

1.MT4程序连接监控,监控有没有网络连接到MT4程序。这个里面有两个,不知道为什么里面还有一个,这两个任务是一样的:

if (mt4Module.isCheckRequired && mt4Module.checkFuture == null) {
    mt4Module.checkFuture = scheduledExecutorService.schedule(new Runnable() {
        @Override
        public void run() {
            Mt4Module mt4Module = incomingConnectionModule.get(tp.strategy);
            if (mt4Module.isCheckRequired) {
                try {
                    String status = tp.checkTerminal(clientWorker);
                    if (status.startsWith("OK")) {
                        mt4Module.checkFuture = scheduledExecutorService.schedule(this, 15, TimeUnit
                    } else {
                        mt4Module.checkFuture = null;
                        incomingConnectionError.put(tp.strategy, status);
                    }
                } catch (NoSrvConnection e) {
                    String m = "No connection to server: " + e;
                    TS.LOGGER.error(m, e);
                    incomingConnectionError.put(tp.strategy, m);
                } catch (SrvFileNotFound e) {
                    String m = "SRV file not found: " + e;
                    TS.LOGGER.error(m, e);
                    incomingConnectionError.put(tp.strategy, m);
                } catch (MaxNumberOfTerminalsReached e) {
                    String m = "Reached max number of terminals: " + e;
                    TS.LOGGER.error(m, e);
                    incomingConnectionError.put(tp.strategy, m);
                } catch (InvalidUserNamePassword e) {
                    String m = "Invalid user name or password: " + e;
                    TS.LOGGER.error(m, e);
                    incomingConnectionError.put(tp.strategy, m);
                } catch (TerminalInstallationIsRequired e) {
                    String m = e.getMessage();
                    TS.LOGGER.error(m, e);
                    incomingConnectionError.put(tp.strategy, m);
                } catch (Throwable e) {
                    e.printStackTrace();
                    String m = "Unexpected error: " + e;
                    TS.LOGGER.error(m, e);
                    incomingConnectionError.put(tp.strategy, m);
                }
            }
        }
    }, 15, TimeUnit.SECONDS);

2.磁盘监控,从这个就能看出对磁盘的操作

//这个磁盘监控每60s进行一次6
spaceMonitoringJob =    scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {}
},5, 60, TimeUnit.SECONDS);

3.监控新的session

TS.scheduledExecutorService.submit(newSessionCreator);

4.监控终端变化

TS.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    public void run() {
        if (Log4JUtil.isConfigured() && TS.LOGGER.isTraceEnabled()) {
            TS.LOGGER.trace("Timer: update session's load level");
        }
        if (TS.P_USE_MSTSC) {
            try {
                loadExistingSessions();
            } catch (Throwable e) {
                TS.LOGGER.error("Error loading current sessions", e);
            }
        }
        updateTerminals();
    }
}, 30, 60, TimeUnit.SECONDS);

4.监控google网盘设置变化

TS.scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        int nextSyncInSeconds = nextSyncInSeconds();
        if (nextSyncInSeconds > 0) {
            TS.scheduledExecutorService.schedule(this, 1/*Math.min(nextSyncInSeconds, 5)*/, TimeUnit.SECONDS);
            return;
        }
        try {
            checking = true;
            boolean somethingDone = false;
            for (DownloadSetup ds : downloads.values()) {
                somethingDone |= ds.run();
            }
            if (somethingDone) {
                TS.LOGGER.info("Google Drive: All Downloads Complete!");
            }
            checking = false;
        } finally {
            lastSyncTime = System.currentTimeMillis();
            TS.scheduledExecutorService.schedule(this, 1/*Math.max(nextSyncInSeconds(), 5)*/, TimeUnit.SECONDS);
        }
    }

5.日志压缩任务

6.断线终端的关闭任务

ScheduledFuture schedule = TS.scheduledExecutorService.schedule(new Runnable() {
    public void run() {
        if (Log4JUtil.isConfigured() && TS.LOGGER.isInfoEnabled()) {
            TS.LOGGER.info("Timer: Stop terminal " + processName);
        }
        ts.killProcess(processName, true);
    }
}, TS.JFX_TERM_IDLE_TMOUT_SECONDS, TimeUnit.SECONDS);
TS.terminations.put(processName, schedule);

7.更新终端状态任务

TS.scheduledExecutorService.schedule(new Runnable() {
    public void run() {
        ts.updateTerminals();
    }
}, 2, TimeUnit.SECONDS);

8.监控GUI中的网盘设置项变化

TS.scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    if (!text.equals(updateInterval.getText())) {
                        return;
                    }
                    if (text.replace("0", "").length() > 0) {
                        try {
                            if (Integer.parseInt(text) <= 0) {
                                JOptionPane.showMessageDialog(null,
                                        "Error: Please enter number bigger than 0", "Error Message",
                                        JOptionPane.ERROR_MESSAGE);
                            } else {
                                TS.setConfigurationValue("cloud_update_interval", text);
                                TS.LOGGER.info("Google Drive update interval set to " + text + " sec");
                            }
                        } catch (NumberFormatException e) {
                            JOptionPane.showMessageDialog(null,
                                    "Error: Please enter valid number >0", "Error Message",
                                    JOptionPane.ERROR_MESSAGE);
                        }
                    }
                } catch (HeadlessException e) {
                    TS.LOGGER.error("At GUI", e);
                }
            }
        });
    }
}, KEY_PRESSED_ACTION_DELAY, TimeUnit.MILLISECONDS);

9.貌似是界面的刷新

TS.scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        SwingUtilities.invokeLater(x);
    }
}, 1, TimeUnit.SECONDS);

10.监控正在进行连接操作的终端

TS.scheduledExecutorService.schedule(new Runnable() {
    public void run() {
          将正在连接的终端列表下这个map
        final HashMap<String, TerminalParams> tp = new HashMap<>();
        synchronized (connectionsInProgress) {
            for (TerminalParams p : connectionsInProgress) {
                tp.put(p.getTerminalDirPathName(), p);
            }
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (connectionsInProgressTable) {
                        DefaultTableModel model = (DefaultTableModel) connectionsInProgressTable.getModel();
                        Vector dataVector = model.getDataVector();
                        int sz = dataVector.size();
                        boolean allInPlace = sz > 0;
                        for (int row = 0; row < sz; row++) {
                            String path = (String) model.getValueAt(row, model.findColumn(PATH_COLUMN));
                            TerminalParams terminalParams = tp.get(path);
                            if (terminalParams == null) {
                                allInPlace = false;
                                break;
                            } else {
                                model.setValueAt(
                                        //                                            "<html><b>" + ((System.curre
                                          设置连接的时间
                                        new Integer((int) ((System.currentTimeMillis() - terminalParams.start.getT
                                        row, model.findColumn(DURATION_COLUMN)
                                );
                            }
                        }
                          设置好时间之后开始显示
                        if (!allInPlace) {
                            dataVector.clear();
                            // new Object[]{"Broker", "Account", "Path", "Start Time", "Duration (s)"}
                            for (Map.Entry<String, TerminalParams> p : tp.entrySet()) {
                                Vector row = new Vector();
                                TerminalParams terminalParams = p.getValue();
                                row.add(terminalParams.getSrv());
                                row.add(terminalParams.getUser());
                                row.add(new SimpleDateFormat("MMM d, HH:mm:ss").format(terminalParams.start));
                                row.add(
                                        //"<html><b>" + ((System.currentTimeMillis() - terminalParams.start.getTim
                                        new Integer((int) ((System.currentTimeMillis() - terminalParams.start.getT
                                );
                                row.add(terminalParams.getTerminalDirPathName());
                                //
                                dataVector.add(row);
                            }
                            model.fireTableDataChanged();
                        }
                        //这个才是真真的显示
                        SwingUtilities.invokeLater(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    TitledBorder border = (TitledBorder) connectionsInProgressPanel.getBorder();
                                    border.setTitle(tp.size() > 0
                                            ? (tp.size() == 1 ? "1 Connection In Progress" : "" + tp.size() + " Co
                                            : "Connections In Progress"
                                    );
                                    connectionsInProgressPanel.repaint();
                                } catch (Exception e) {
                                    TS.LOGGER.error("At GUI", e);
                                }
                            }
                        });
                    }
                } catch (Exception e) {
                    TS.LOGGER.error("At GUI", e);
                }
            }
        });
        //不知道这句话是干什么用的,为什么要this,this指的是这个new runable类,不知道加不加有什么区别
        TS.scheduledExecutorService.schedule(this, 1, TimeUnit.SECONDS);
    }
}, 1, TimeUnit.SECONDS);

11.监控日志显示列表的变化

//监听日志限制输入框的输入变化
TS.scheduledExecutorService.schedule(new Runnable() {
    @Override
    public void run() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    if (!text.equals(limitRowsField.getText())) {
                        return;
                    }
                    if (text.replace("0", "").length() > 0) {
                        try {
                            if (Integer.parseInt(text) <= 0) {
                                JOptionPane.showMessageDialog(null,
                                        "Error: Please enter number bigger than 0", "Error Message",
                                        JOptionPane.ERROR_MESSAGE);
                            } else {
                                applyNewRowsLimit();
                            }
                        } catch (NumberFormatException e) {
                            JOptionPane.showMessageDialog(null,
                                    "Error: Please enter valid number bigger than 0", "Error Message",
                                    JOptionPane.ERROR_MESSAGE);
                        }
                    }
                } catch (HeadlessException e) {
                    TS.LOGGER.error("At GUI", e);
                }
            }
        });
    }
}, KEY_PRESSED_ACTION_DELAY, TimeUnit.MILLISECONDS);

此外,还有其他的任务,后面会提到

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值