предыдущийнесколько статейГоворя о процессе запуска Tomcat, после его запуска в конфигурации по умолчанию вы увидите, что на самом деле в фоновом режиме работает 6 потоков. То есть 1 пользовательский поток, а остальные 5 — это потоки демона (Daemon Thread на рисунке ниже).
Если вы не знакомы с понятием потока демона, повторите его здесь:Так называемый поток демона относится к потоку, который предоставляет общие услуги в фоновом режиме во время работы программы, например, поток сборки мусора. Этот поток не является составной частью программы.Когда все потоки, не являющиеся демонами, заканчиваются, программа завершается, и все потоки демонов в процессе уничтожаются. И наоборот, программа не завершится, пока все еще выполняются какие-либо потоки, не являющиеся демонами.
Между пользовательскими потоками и потоками демона почти нет разницы, единственное отличие состоит в выходе виртуальной машины: если все пользовательские потоки завершили работу, существуют только потоки демона, и виртуальная машина завершится. Поскольку опекуна нет, потоку демона нечего делать, и нет необходимости продолжать выполнение программы. Преобразование потока в поток демона можно выполнить, вызвав метод setDaemon(true) объекта Thread.
Выключение Tomcat использует этот принцип, то есть пока закрывается единственный пользовательский поток, закрывается все приложение.
Чтобы изучить, как закрывается этот пользовательский поток, мы должны сначала начать с того места, где этот поток генерируется. При анализе запуска Tomcat ранее мы начали сorg.apache.catalina.startup.Bootstrap
В качестве входа используется метод main класса, а строки с 453 по 456 класса — это код, который Tomcat выполнит при запуске:
daemon.setAwait(true);
Это предложение, его роль состоит в том, чтобы вызвать через размышлениеorg.apache.catalina.startup.Catalina
Наконец, метод setAwait(true) класса устанавливает для переменной экземпляра await класса Catalina значение true .
Код метода Setawait для класса Catalina:
/**
* Set flag.
*/
public void setAwait(boolean await)
throws Exception {
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Boolean.TYPE;
Object paramValues[] = new Object[1];
paramValues[0] = Boolean.valueOf(await);
Method method =
catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
method.invoke(catalinaDaemon, paramValues);
}
Как и в предыдущем анализе, он будет вызван для запуска Tomcat.org.apache.catalina.startup.Catalina
Стартовый метод класса, посмотрите на код этого метода:
1 /**
2 * Start a new server instance.
3 */
4 public void start() {
5
6 if (getServer() == null) {
7 load();
8 }
9
10 if (getServer() == null) {
11 log.fatal("Cannot start server. Server instance is not configured.");
12 return;
13 }
14
15 long t1 = System.nanoTime();
16
17 // Start the new server
18 try {
19 getServer().start();
20 } catch (LifecycleException e) {
21 log.fatal(sm.getString("catalina.serverStartFail"), e);
22 try {
23 getServer().destroy();
24 } catch (LifecycleException e1) {
25 log.debug("destroy() failed for failed Server ", e1);
26 }
27 return;
28 }
29
30 long t2 = System.nanoTime();
31 if(log.isInfoEnabled()) {
32 log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
33 }
34
35 // Register shutdown hook
36 if (useShutdownHook) {
37 if (shutdownHook == null) {
38 shutdownHook = new CatalinaShutdownHook();
39 }
40 Runtime.getRuntime().addShutdownHook(shutdownHook);
41
42 // If JULI is being used, disable JULI's shutdown hook since
43 // shutdown hooks run in parallel and log messages may be lost
44 // if JULI's hook completes before the CatalinaShutdownHook()
45 LogManager logManager = LogManager.getLogManager();
46 if (logManager instanceof ClassLoaderLogManager) {
47 ((ClassLoaderLogManager) logManager).setUseShutdownHook(
48 false);
49 }
50 }
51
52 if (await) {
53 await();
54 stop();
55 }
56 }
Предыдущий анализ показал, что с помощью вызова метода getServer().start() в строке 19 Tomcat шаг за шагом запускает все компоненты, настроенные в файле конфигурации. Следующий код не анализируется, обратите внимание на последние строки с 52 по 55. Там указано, что переменная экземпляра await класса Catalina установлена в true, поэтому здесь будет выполняться метод await класса Catalina:
/**
* Await and shutdown.
*/
public void await() {
getServer().await();
}
Этот метод представляет собой всего одно предложение, что означает вызовorg.apache.catalina.core.StandardServer
Метод ожидания класса:
1 /**
2 * Wait until a proper shutdown command is received, then return.
3 * This keeps the main thread alive - the thread pool listening for http
4 * connections is daemon threads.
5 */
6 @Override
7 public void await() {
8 // Negative values - don't wait on port - tomcat is embedded or we just don't like ports
9 if( port == -2 ) {
10 // undocumented yet - for embedding apps that are around, alive.
11 return;
12 }
13 if( port==-1 ) {
14 try {
15 awaitThread = Thread.currentThread();
16 while(!stopAwait) {
17 try {
18 Thread.sleep( 10000 );
19 } catch( InterruptedException ex ) {
20 // continue and check the flag
21 }
22 }
23 } finally {
24 awaitThread = null;
25 }
26 return;
27 }
28
29 // Set up a server socket to wait on
30 try {
31 awaitSocket = new ServerSocket(port, 1,
32 InetAddress.getByName(address));
33 } catch (IOException e) {
34 log.error("StandardServer.await: create[" + address
35 + ":" + port
36 + "]: ", e);
37 return;
38 }
39
40 try {
41 awaitThread = Thread.currentThread();
42
43 // Loop waiting for a connection and a valid command
44 while (!stopAwait) {
45 ServerSocket serverSocket = awaitSocket;
46 if (serverSocket == null) {
47 break;
48 }
49
50 // Wait for the next connection
51 Socket socket = null;
52 StringBuilder command = new StringBuilder();
53 try {
54 InputStream stream;
55 try {
56 socket = serverSocket.accept();
57 socket.setSoTimeout(10 * 1000); // Ten seconds
58 stream = socket.getInputStream();
59 } catch (AccessControlException ace) {
60 log.warn("StandardServer.accept security exception: "
61 + ace.getMessage(), ace);
62 continue;
63 } catch (IOException e) {
64 if (stopAwait) {
65 // Wait was aborted with socket.close()
66 break;
67 }
68 log.error("StandardServer.await: accept: ", e);
69 break;
70 }
71
72 // Read a set of characters from the socket
73 int expected = 1024; // Cut off to avoid DoS attack
74 while (expected < shutdown.length()) {
75 if (random == null)
76 random = new Random();
77 expected += (random.nextInt() % 1024);
78 }
79 while (expected > 0) {
80 int ch = -1;
81 try {
82 ch = stream.read();
83 } catch (IOException e) {
84 log.warn("StandardServer.await: read: ", e);
85 ch = -1;
86 }
87 if (ch < 32) // Control character or EOF terminates loop
88 break;
89 command.append((char) ch);
90 expected--;
91 }
92 } finally {
93 // Close the socket now that we are done with it
94 try {
95 if (socket != null) {
96 socket.close();
97 }
98 } catch (IOException e) {
99 // Ignore
100 }
101 }
102
103 // Match against our command string
104 boolean match = command.toString().equals(shutdown);
105 if (match) {
106 log.info(sm.getString("standardServer.shutdownViaPort"));
107 break;
108 } else
109 log.warn("StandardServer.await: Invalid command '"
110 + command.toString() + "' received");
111 }
112 } finally {
113 ServerSocket serverSocket = awaitSocket;
114 awaitThread = null;
115 awaitSocket = null;
116
117 // Close the server socket and return
118 if (serverSocket != null) {
119 try {
120 serverSocket.close();
121 } catch (IOException e) {
122 // Ignore
123 }
124 }
125 }
126 }
Этот код не будет анализироваться один за другим, общий эффект такой же, как и в комментарии перед методом, а именно:Этот метод вернется после ожидания, чтобы получить правильную команду отключения. Это было бы главной резьбой в живых - прослушивание HTTP-соединительной резьбы пул - это нить демона".
Если вы знакомы с программированием Socket в Java, вам будет легко понять этот код, который является адресом по умолчанию (значение адреса определяется адресом переменной экземпляра, значение по умолчанию равноlocalhost
) порт по умолчанию (значение порта определяется переменной порта экземпляра, которая по умолчанию равна8005
) на соединении Socket, когда обнаруживается, что содержимое входного потока отслеживаемого соединения соответствует значению конфигурации по умолчанию (значение по умолчанию равно строкеSHUTDOWN
), чтобы выйти из цикла, и метод возвращается (строки 103–107). В противном случае метод будет продолжать выполняться в цикле.
Обычно основной поток пользователя блокируется (строка 56) до тех пор, пока не будет доступа.localhost:8005
появляется подключение.
Из-за этого основной поток, видимый в начале, всегда выполняется, а поскольку этот поток всегда выполняется, всегда будут существовать другие потоки демона.
После разговора о генерации этого потока, давайте посмотрим на закрытие этого потока.Согласно приведенному выше анализу, этот поток обеспечивает механизм закрытия, то есть до тех пор, пока доступlocalhost:8005
, и отправить контент какSHUTDOWN
строка, вы можете закрыть его.
Это именно то, что делает Tomcat.Вообще говоря, Tomcat выключается, выполняя сценарий shutdown.bat или shutdown.sh.Для этого сценария, пожалуйста, обратитесь к статье об анализе сценария запуска.Механизм аналогичен и в конечном итоге будет выполнен .org.apache.catalina.startup.Bootstrap
Основной метод класса и передача параметров"stop"
, посмотрите на 2-ю картинку в этой статьеorg.apache.catalina.startup.Bootstrap
строка 458 класса, которая затем вызоветorg.apache.catalina.startup.Catalina
Метод класса stopServer:
1 public void stopServer(String[] arguments) {
2
3 if (arguments != null) {
4 arguments(arguments);
5 }
6
7 Server s = getServer();
8 if( s == null ) {
9 // Create and execute our Digester
10 Digester digester = createStopDigester();
11 digester.setClassLoader(Thread.currentThread().getContextClassLoader());
12 File file = configFile();
13 FileInputStream fis = null;
14 try {
15 InputSource is =
16 new InputSource(file.toURI().toURL().toString());
17 fis = new FileInputStream(file);
18 is.setByteStream(fis);
19 digester.push(this);
20 digester.parse(is);
21 } catch (Exception e) {
22 log.error("Catalina.stop: ", e);
23 System.exit(1);
24 } finally {
25 if (fis != null) {
26 try {
27 fis.close();
28 } catch (IOException e) {
29 // Ignore
30 }
31 }
32 }
33 } else {
34 // Server object already present. Must be running as a service
35 try {
36 s.stop();
37 } catch (LifecycleException e) {
38 log.error("Catalina.stop: ", e);
39 }
40 return;
41 }
42
43 // Stop the existing server
44 s = getServer();
45 if (s.getPort()>0) {
46 Socket socket = null;
47 OutputStream stream = null;
48 try {
49 socket = new Socket(s.getAddress(), s.getPort());
50 stream = socket.getOutputStream();
51 String shutdown = s.getShutdown();
52 for (int i = 0; i < shutdown.length(); i++) {
53 stream.write(shutdown.charAt(i));
54 }
55 stream.flush();
56 } catch (ConnectException ce) {
57 log.error(sm.getString("catalina.stopServer.connectException",
58 s.getAddress(),
59 String.valueOf(s.getPort())));
60 log.error("Catalina.stop: ", ce);
61 System.exit(1);
62 } catch (IOException e) {
63 log.error("Catalina.stop: ", e);
64 System.exit(1);
65 } finally {
66 if (stream != null) {
67 try {
68 stream.close();
69 } catch (IOException e) {
70 // Ignore
71 }
72 }
73 if (socket != null) {
74 try {
75 socket.close();
76 } catch (IOException e) {
77 // Ignore
78 }
79 }
80 }
81 } else {
82 log.error(sm.getString("catalina.stopServer"));
83 System.exit(1);
84 }
85 }
Строки с 8 по 41 предназначены для чтения файла конфигурации, вы можете обратиться кПредыдущий анализ статьи Digester,Больше никогда. Начиная со строки 49,localhost:8005
Инициируйте соединение Socket и напишитеSHUTDOWN
нить.
Это закроет единственный пользовательский поток в Tomcat, затем все потоки демона завершатся (гарантировано JVM), а затем все приложение завершит работу.
Механизм отключения Tomcat по умолчанию разобран выше, но он закрывается запуском скрипта, я думаю, что это более хлопотно, поэтому можно ли закрыть Tomcat через метод онлайн-доступа? Конечно, более жестокий геймплей заключается в непосредственном измененииorg.apache.catalina.core.StandardServer
Строка 500 исходного кода будет
boolean match = command.toString().equals(shutdown);
изменить на
boolean match = command.toString().equals(“GET /SHUTDOWN HTTP/1.1”);
Или измените файл server.xml, найдите узел Сервер и измените исходный
<Server port="8005" shutdown="SHUTDOWN">
изменить на
<Server port="8005" shutdown="GET /SHUTDOWN HTTP/1.1">
Это прямо вводится в браузереhttp://localhost:8005/SHUTDOWN
Можно Tomcat закрыть, впринципе? После прочтения вышеуказанной статьи это не должно вызвать затруднений.