让我们先来看看Main的main以及注释:
public static void main(String argv[]) { int port = 0; String serviceName = null; //处理在启动脚本中传入的几个参数 for(int i = 0; i < argv.length; i++) { if(argv[i].equals("-port") && i < argv.length - 1) port = Integer.valueOf(argv[1 + i++]).intValue(); if(argv[i].equals("-home") && i < argv.length - 1) Config.setProperty("watt.server.homeDir", argv[1 + i++]); if(argv[i].equals("-service") && i < argv.length - 1) serviceName = argv[1 + i++]; } if(port != 0) Config.setProperty("watt.server.port", Integer.toString(port)); UniqueApp ua = null; int uaport = 4321; //获得一个叫uaport的端口参数,默认是4321 String uapstr = System.getProperty("watt.server.uaport"); if(uapstr != null) try { uaport = Integer.parseInt(uapstr); } catch(NumberFormatException _ex) { } //用这个uaport创建一个app ua = new UniqueApp(uaport); try { ua.start(); //调用Server这个类的start方法初始化IS Server.start(argv); ua.quit(); if(Server.restart()) { try { if(serviceName != null) { String homepath = System.getProperty("watt.server.homeDir", "."); String cmd = homepath + File.separator + "bin" + File.separator + "RestartService.exe " + serviceName; Runtime.getRuntime().exec(cmd); } } catch(Exception e) { e.printStackTrace(); } System.exit(42); } else { System.exit(0); } } catch(com.wm.util.UniqueApp.DeniedException _ex) { System.out.println("Server cannot execute. A server with this configuration is already running."); System.exit(0); } }
看到这里大家可能会产生疑问,这个UniqueApp的实例的ua到底有什么用呢,为什么要在Server初始化之前先start它,初始化之后又quit呢?要解答这些问题让我们先看看这个UniqueApp究竟做了什么事:
UniqueApp类的start方法、send方法和runRegistry方法:
public void start() throws DeniedException { if(port_ > lastPort_) debugPrint("UNQ ERROR: port_ (" + port_ + ") > lastPort_ (" + lastPort_ + ")"); for(; port_ <= lastPort_; port_++) { debugPrint("UNQ attempting connection on port " + port_); try { debugPrint("UNQ " + iden_ + " sending initial message"); String response = send("init"); debugPrint("UNQ " + iden_ + " got response " + response); if(response == null) { debugPrint("UNQ connected on port " + port_ + ", but no response"); continue; } if(response.equals("DENIED")) throw new DeniedException(); if(response.equals("OK")) { runHeartbeat(); break; } debugPrint("UNQ connected on port " + port_ + ", but incomprehensible response"); continue; } catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(System.out); runRegistry(); } break; } } public String send(String type) throws IOException { Socket socket = new Socket("localhost", port_); BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); PrintWriter out = new PrintWriter(socket.getOutputStream(), true); String msg = type + ": " + iden_; out.println(msg); out.flush(); return in.readLine(); } public void runRegistry() { debugPrint("UNQ " + iden_ + " starting registry on port " + port_); registry_ = new AppRegistry(iden_, port_); }
这里可以很清楚的看到,UniqueApp是一个socket的client端,它的作用就是用一些特定的原语连接localhost上的uaport端口,如果这个端口没有响应或者响应的不对,它就“new AppRegistry(iden_, port_)”。
AppRegistry是一个实现了Runnable接口的类,它又做了什么呢?请看:
AppRegistry类的构造方法、run方法、openSocket方法以及解析原语的processLineReceived方法:
public AppRegistry(String iden, int port) { info_ = new HashMap(); out_ = null; socket_ = null; info_.put(iden, new Long(0x7fffffffffffffffL)); port_ = port; Thread t = new Thread(this); t.setDaemon(true); t.start(); } public void run() { debugPrint("REG running"); openSocket(); if(socket_ == null) debugPrint("REG no socket"); else while(socket_ != null) processConnection(); debugPrint("REG stopped"); } protected void openSocket() { try { debugPrint("REG running socket server on port " + port_); socket_ = new ServerSocket(port_); socket_.setSoTimeout(3000); } catch(IOException ioe) { if(DEBUG) ioe.printStackTrace(System.out); debugPrint("REG failed to open socket"); socket_ = null; } } protected void processLineReceived(String line) { debugPrint("REG processLineReceived(" + line + ")"); int pos = line != null ? line.indexOf(": ") : -1; if(pos != -1) { String type = line.substring(0, pos); String identifier = line.substring(pos + 2); if(type.equals("init")) handleInit(identifier); else if(type.equals("update")) update(identifier); else if(type.equals("quit")) quit(identifier); else debugPrint("REG unknown type: '" + type + "' in line: '" + line + "'"); } }
由此看出AppRegistry是一个单线程的Socket Server。虽然到了这里我们都知道了UniqueApp和AppRegistry的作用,但是疑惑仍然没有解答,为什么要在服务器启动之前先连接或者启动一个Socket Server呢?
要得到答案,让我们来看看Server初化始化的时候究竟做了些什么:
Server类的start方法:
public static void start(String args[]) { gServer = new Server(args); gServer.start(); try { gServer.join(); } catch(Exception _ex) { } }
这里要说明的是Server类是Thread类的子类,它的run方法如下:
public void run() { try { long start = System.currentTimeMillis(); com.wm.util.Config.setProperty("watt.server", "true"); gRestart = false; gInShutdown = false; gListeners = new Values(); gResources = new Resources(System.getProperty("watt.server.homeDir", "."), true); gConfFile = gResources.getConfigFile("server.cnf"); gResources.getLogJobsInDir(); gResources.getLogJobsOutDir(); gResources.getDatastoreDir(); loadConfiguration(); Scheduler.init(); String tmp = com.wm.util.Config.getProperty("false", "watt.server.control.runMemorySensor"); if(Boolean.valueOf(tmp).booleanValue()) MemorySensor.init(ServerController.getInstance()); ThreadPoolSensor.init(ServerController.getInstance()); checkProperties(); com.wm.util.Config.processCmdLine(args); setupLogging(); JournalLog.init(args); JournalLogger.init(JournalLog.newProducer(), JournalLog.getHandler()); JournalLogger.logCritical(1, 25, Build.getVersion(), Build.getBuild()); if(!LicenseManager.init()) { JournalLogger.logCritical(1, 14); return; } RepositoryManager.init(); JDBCConnectionManager.init(); AuditLogManager.init(); setupIPRules(); UserManager.init(); ACLManager.init(); ThreadManager.init(); StateManager.init(); ListenerAdmin.init(); ServiceManager.init(); InvokeManager.init(); Statistics.init(); NetURLConnection.init(); ContentManager.init(); CacheManager.init(); EventManager.init(); boolean dispatcherCorrectlyInit = false; try { DispatchFacade.init(); dispatcherCorrectlyInit = true; } catch(Exception e) { JournalLogger.logCritical(31, 25, e); } WebContainer.init(); ISMonEvtMgr.create(); PackageManager.init(); DependencyManager dm = NSDependencyManager.current(); if(dm == null || !dm.isEnabled()) JournalLogger.logDebugPlus(3, 15, 25); else JournalLogger.logDebugPlus(3, 14, 25); try { if(dispatcherCorrectlyInit) DispatchFacade.start(); } catch(Exception ce) { JournalLogger.logError(35, 25, ce); } PortManager.init(); MimeTypes.init(); HTTPDispatch.init(); ProxyHTTPDispatch.init(); LBHTTPDispatch.init(); CacheManager.startSweeper(); Document.setHostServices(new ServerXmlHostServices()); try { ClusterManager.init(); } catch(Exception e) { JournalLogger.logDebug(103, 33, e.getMessage()); } try { String jobDir = com.wm.util.Config.getProperty("logs/jobsout", "watt.tx.jobdir"); TContext.init(jobDir); } catch(ServiceException e) { JournalLogger.logDebugPlus(3, 9998, 36, e); } if(Config.isSSLPresent()) try { Class c = Class.forName("com.wm.app.b2b.server.ServerTrustDeciderManager"); TrustDeciderManager tdm = (TrustDeciderManager)c.newInstance(); TrustManager.setManager(tdm); } catch(Throwable _ex) { } if(!ListenerAdmin.isReady() && !gCanListen) { JournalLogger.logCritical(4, 14); return; } Scheduler.scheduleTask("Key Update", new KeyUpdate(), 0L, 0x5265c00L); String sc = com.wm.util.Config.getProperty("true", "watt.server.saveConfigFiles"); if((new Boolean(sc)).booleanValue()) saveConfigFiles(); if(Configurator.isBrokerConfigured()) SyncManager.init(); gRunning = true; long startTimeSeconds = (System.currentTimeMillis() - start) / 1000L; JournalLogger.logCritical(2, 14, Long.toString(startTimeSeconds)); synchronized(this) { try { wait(); } catch(Exception e) { e.printStackTrace(); } } } catch(Exception e) { e.printStackTrace(); } JournalLog.unInit(); }
上面的代码不用多解释,大家可以看到IS启动做了哪些事情,IS分为哪些模块,以及这些模块的加载顺序。
IS启动的操作很多,启动时间也比较长,而且有“PortManager.init()”、“saveConfigFiles()”这样需要独占运行的端口占用和文件操作,如果是在同一个JVM上我们可以用Synchronized,如果是不到的JVM呢?答案揭晓了,UniqueApp的作用是使用端口占用的方式在IS启动阶段实现互斥,即对于同一个启动文件夹下的配置一台机器只能有一个IS在启动,否则会报出“Server cannot execute. A server with this configuration is already running.”的错误。