webMethods IS中负责服务器启动的类有四个:执行入口类com.wm.app.b2b.server.Main、服务器初始化类com.wm.app.b2b.server.Server、以及两个辅助类com.wm.util.UniqueApp、com.wm.util.AppRegistry。
让我们先来看看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.”的错误。