Tomcat源码分析——启动与停止服务
前言
熟悉Tomcat的工程师们,肯定都知道Tomcat是如何启动与停止的。对于startup.sh、startup.bat、shutdown.sh、shutdown.bat等脚本或者批处理命令,大家一定知道改如何使用它,但是它们究竟是如何实现的,尤其是shutdown.sh脚本(或者shutdown.bat)究竟是如何和Tomcat进程通信的呢?本文将通过对Tomcat7.0的源码阅读,深入剖析这一过程。
由于在生产环境中,Tomcat一般部署在Linux系统下,所以本文将以startup.sh和shutdown.sh等shell脚本为准,对Tomcat的启动与停止进行分析。
启动过程分析
我们启动Tomcat的命令如下:
- sh startup.sh
所以,将从shell脚本startup.sh开始分析Tomcat的启动过程。startup.sh的脚本代码见代码清单1。
代码清单1
- os400=false
- case "`uname`" in
- OS400*) os400=true;;
- esac
- # resolve links - $ may be a softlink
- PRG="$0"
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`/"$link"
- fi
- done
- PRGDIR=`dirname "$PRG"`
- EXECUTABLE=catalina.sh
- # Check that target executable exists
- if $os400; then
- # -x will Only work on the os400 if the files are:
- # . owned by the user
- # . owned by the PRIMARY group of the user
- # this will not work if the user belongs in secondary groups
- eval
- else
- if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
- echo "Cannot find $PRGDIR/$EXECUTABLE"
- echo "The file is absent or does not have execute permission"
- echo "This file is needed to run this program"
- exit
- fi
- fi
- exec "$PRGDIR"/"$EXECUTABLE" start "$@"
代码清单1中有两个主要的变量,分别是:
- PRGDIR:当前shell脚本所在的路径;
- EXECUTABLE:脚本catalina.sh。
根据最后一行代码:exec "$PRGDIR"/"$EXECUTABLE" start "$@",我们知道执行了shell脚本catalina.sh,并且传递参数start。catalina.sh中接收到start参数后的执行的脚本分支见代码清单2。
代码清单2
- elif [ "$1" = "start" ] ; then
- # 此处省略参数校验的脚本
- shift
- touch "$CATALINA_OUT"
- if [ "$1" = "-security" ] ; then
- if [ $have_tty -eq ]; then
- echo "Using Security Manager"
- fi
- shift
- eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
- -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
- -Djava.security.manager \
- -Djava.security.policy=="\"$CATALINA_BASE/conf/catalina.policy\"" \
- -Dcatalina.base="\"$CATALINA_BASE\"" \
- -Dcatalina.home="\"$CATALINA_HOME\"" \
- -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
- org.apache.catalina.startup.Bootstrap "$@" start \
- >> "$CATALINA_OUT" >& "&"
- else
- eval "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \
- -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
- -Dcatalina.base="\"$CATALINA_BASE\"" \
- -Dcatalina.home="\"$CATALINA_HOME\"" \
- -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
- org.apache.catalina.startup.Bootstrap "$@" start \
- >> "$CATALINA_OUT" >& "&"
- fi
- if [ ! -z "$CATALINA_PID" ]; then
- echo $! > "$CATALINA_PID"
- fi
- echo "Tomcat started."
从代码清单2可以看出,最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数也是start。Bootstrap的main方法的实现见代码清单3。
代码清单3
- /**
- * Main method, used for testing only.
- *
- * @param args Command line arguments to be processed
- */
- public static void main(String args[]) {
- if (daemon == null) {
- // Don't set daemon until init() has completed
- Bootstrap bootstrap = new Bootstrap();
- try {
- bootstrap.init();
- } catch (Throwable t) {
- t.printStackTrace();
- return;
- }
- daemon = bootstrap;
- }
- try {
- String command = "start";
- if (args.length > 0) {
- command = args[args.length - 1];
- }
- if (command.equals("startd")) {
- args[args.length - 1] = "start";
- daemon.load(args);
- daemon.start();
- } else if (command.equals("stopd")) {
- args[args.length - 1] = "stop";
- daemon.stop();
- } else if (command.equals("start")) {
- daemon.setAwait(true);
- daemon.load(args);
- daemon.start();
- } else if (command.equals("stop")) {
- daemon.stopServer(args);
- } else {
- log.warn("Bootstrap: command \"" + command + "\" does not exist.");
- }
- } catch (Throwable t) {
- t.printStackTrace();
- }
- }
从代码清单3可以看出,当传递参数start的时候,command等于start,此时main方法的执行步骤如下:
步骤一 初始化Bootstrap
Bootstrap的init方法(见代码清单4)的执行步骤如下:
- 设置Catalina路径,默认为Tomcat的根目录;
- 初始化Tomcat的类加载器,并设置线程上下文类加载器(具体实现细节,读者可以参考《TOMCAT源码分析——类加载体系》一文);
- 用反射实例化org.apache.catalina.startup.Catalina对象,并且使用反射调用其setParentClassLoader方法,给Catalina对象设置Tomcat类加载体系的顶级加载器(Java自带的三种类加载器除外)。
代码清单4
- /**
- * Initialize daemon.
- */
- public void init()
- throws Exception
- {
- // Set Catalina path
- setCatalinaHome();
- setCatalinaBase();
- initClassLoaders();
- Thread.currentThread().setContextClassLoader(catalinaLoader);
- SecurityClassLoad.securityClassLoad(catalinaLoader);
- // Load our startup class and call its process() method
- if (log.isDebugEnabled())
- log.debug("Loading startup class");
- Class<?> startupClass =
- catalinaLoader.loadClass
- ("org.apache.catalina.startup.Catalina");
- Object startupInstance = startupClass.newInstance();
- // Set the shared extensions class loader
- if (log.isDebugEnabled())
- log.debug("Setting startup class properties");
- String methodName = "setParentClassLoader";
- Class<?> paramTypes[] = new Class[1];
- paramTypes[0] = Class.forName("java.lang.ClassLoader");
- Object paramValues[] = new Object[1];
- paramValues[0] = sharedLoader;
- Method method =
- startupInstance.getClass().getMethod(methodName, paramTypes);
- method.invoke(startupInstance, paramValues);
- catalinaDaemon = startupInstance;
- }
步骤二 加载、解析server.xml配置文件
当传递参数start的时候,会调用Bootstrap的load方法(见代码清单5),其作用是用反射调用catalinaDaemon(类型是Catalina)的load方法加载和解析server.xml配置文件,具体细节已在《TOMCAT源码分析——SERVER.XML文件的加载与解析》一文中详细介绍,有兴趣的朋友可以选择阅读。
代码清单5
- /**
- * Load daemon.
- */
- private void load(String[] arguments)
- throws Exception {
- // Call the load() method
- String methodName = "load";
- Object param[];
- Class<?> paramTypes[];
- if (arguments==null || arguments.length==0) {
- paramTypes = null;
- param = null;
- } else {
- paramTypes = new Class[1];
- paramTypes[0] = arguments.getClass();
- param = new Object[1];
- param[0] = arguments;
- }
- Method method =
- catalinaDaemon.getClass().getMethod(methodName, paramTypes);
- if (log.isDebugEnabled())
- log.debug("Calling startup class " + method);
- method.invoke(catalinaDaemon, param);
- }
步骤三 启动Tomcat
当传递参数start的时候,调用Bootstrap的load方法之后会接着调用start方法(见代码清单6)启动Tomcat,此方法实际是用反射调用了catalinaDaemon(类型是Catalina)的start方法。
代码清单6
- /**
- * Start the Catalina daemon.
- */
- public void start()
- throws Exception {
- if( catalinaDaemon==null ) init();
- Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
- method.invoke(catalinaDaemon, (Object [])null);
- }
Catalina的start方法(见代码清单7)的执行步骤如下:
- 验证Server容器是否已经实例化。如果没有实例化Server容器,还会再次调用Catalina的load方法加载和解析server.xml,这也说明Tomcat只允许Server容器通过配置在server.xml的方式生成,用户也可以自己实现Server接口创建自定义的Server容器以取代默认的StandardServer。
- 启动Server容器,有关容器的启动过程的分析可以参考《TOMCAT源码分析——生命周期管理》一文的内容。
- 设置关闭钩子。这么说可能有些不好理解,那就换个说法。Tomcat本身可能由于所在机器断点,程序bug甚至内存溢出导致进程退出,但是Tomcat可能需要在退出的时候做一些清理工作,比如:内存清理、对象销毁等。这些清理动作需要封装在一个Thread的实现中,然后将此Thread对象作为参数传递给Runtime的addShutdownHook方法即可。
- 最后调用Catalina的await方法循环等待接收Tomcat的shutdown命令。
- 如果Tomcat运行正常且没有收到shutdown命令,是不会向下执行stop方法的,当接收到shutdown命令,Catalina的await方法会退出循环等待,然后顺序执行stop方法停止Tomcat。
代码清单7
- /**
- * Start a new server instance.
- */
- public void start() {
- if (getServer() == null) {
- load();
- }
- if (getServer() == null) {
- log.fatal("Cannot start server. Server instance is not configured.");
- return;
- }
- long t1 = System.nanoTime();
- // Start the new server
- try {
- getServer().start();
- } catch (LifecycleException e) {
- log.error("Catalina.start: ", e);
- }
- long t2 = System.nanoTime();
- if(log.isInfoEnabled())
- log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms");
- try {
- // Register shutdown hook
- if (useShutdownHook) {
- if (shutdownHook == null) {
- shutdownHook = new CatalinaShutdownHook();
- }
- Runtime.getRuntime().addShutdownHook(shutdownHook);
- // If JULI is being used, disable JULI's shutdown hook since
- // shutdown hooks run in parallel and log messages may be lost
- // if JULI's hook completes before the CatalinaShutdownHook()
- LogManager logManager = LogManager.getLogManager();
- if (logManager instanceof ClassLoaderLogManager) {
- ((ClassLoaderLogManager) logManager).setUseShutdownHook(
- false);
- }
- }
- } catch (Throwable t) {
- // This will fail on JDK 1.2. Ignoring, as Tomcat can run
- // fine without the shutdown hook.
- }
- if (await) {
- await();
- stop();
- }
- }
Catalina的await方法(见代码清单8)实际只是代理执行了Server容器的await方法。
代码清单8
- /**
- * Await and shutdown.
- */
- public void await() {
- getServer().await();
- }
以Server的默认实现StandardServer为例,其await方法(见代码清单9)的执行步骤如下:
- 创建socket连接的服务端对象ServerSocket;
- 循环等待接收客户端发出的命令,如果接收到的命令与SHUTDOWN匹配(由于使用了equals,所以shutdown命令必须是大写的),那么退出循环等待。
代码清单9
- public void await() {
- // Negative values - don't wait on port - tomcat is embedded or we just don't like ports gja
- if( port == -2 ) {
- // undocumented yet - for embedding apps that are around, alive.
- return;
- }
- if( port==-1 ) {
- while( true ) {
- try {
- Thread.sleep( 10000 );
- } catch( InterruptedException ex ) {
- }
- if( stopAwait ) return;
- }
- }
- // Set up a server socket to wait on
- ServerSocket serverSocket = null;
- try {
- serverSocket =
- new ServerSocket(port, 1,
- InetAddress.getByName(address));
- } catch (IOException e) {
- log.error("StandardServer.await: create[" + address
- + ":" + port
- + "]: ", e);
- System.exit(1);
- }
- // Loop waiting for a connection and a valid command
- while (true) {
- // Wait for the next connection
- Socket socket = null;
- InputStream stream = null;
- try {
- socket = serverSocket.accept();
- socket.setSoTimeout(10 * 1000); // Ten seconds
- stream = socket.getInputStream();
- } catch (AccessControlException ace) {
- log.warn("StandardServer.accept security exception: "
- + ace.getMessage(), ace);
- continue;
- } catch (IOException e) {
- log.error("StandardServer.await: accept: ", e);
- System.exit(1);
- }
- // Read a set of characters from the socket
- StringBuilder command = new StringBuilder();
- int expected = 1024; // Cut off to avoid DoS attack
- while (expected < shutdown.length()) {
- if (random == null)
- random = new Random();
- expected += (random.nextInt() % 1024);
- }
- while (expected > 0) {
- int ch = -1;
- try {
- ch = stream.read();
- } catch (IOException e) {
- log.warn("StandardServer.await: read: ", e);
- ch = -1;
- }
- if (ch < 32) // Control character or EOF terminates loop
- break;
- command.append((char) ch);
- expected--;
- }
- // Close the socket now that we are done with it
- try {
- socket.close();
- } catch (IOException e) {
- // Ignore
- }
- // Match against our command string
- boolean match = command.toString().equals(shutdown);
- if (match) {
- log.info(sm.getString("standardServer.shutdownViaPort"));
- break;
- } else
- log.warn("StandardServer.await: Invalid command '" +
- command.toString() + "' received");
- }
- // Close the server socket and return
- try {
- serverSocket.close();
- } catch (IOException e) {
- // Ignore
- }
- }
至此,Tomcat启动完毕。很多人可能会问,执行sh shutdown.sh脚本时,是如何与Tomcat进程通信的呢?如果要与Tomcat的ServerSocket通信,socket客户端如何知道服务端的连接地址与端口呢?下面会慢慢说明。
停止过程分析
我们停止Tomcat的命令如下:
- sh shutdown.sh
所以,将从shell脚本shutdown.sh开始分析Tomcat的停止过程。shutdown.sh的脚本代码见代码清单10。
代码清单10
- os400=false
- case "`uname`" in
- OS400*) os400=true;;
- esac
- # resolve links - $0 may be a softlink
- PRG="$0"
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`/"$link"
- fi
- done
- PRGDIR=`dirname "$PRG"`
- EXECUTABLE=catalina.sh
- # Check that target executable exists
- if $os400; then
- # -x will Only work on the os400 if the files are:
- # 1. owned by the user
- # 2. owned by the PRIMARY group of the user
- # this will not work if the user belongs in secondary groups
- eval
- else
- if [ ! -x "$PRGDIR"/"$EXECUTABLE" ]; then
- echo "Cannot find $PRGDIR/$EXECUTABLE"
- echo "The file is absent or does not have execute permission"
- echo "This file is needed to run this program"
- exit 1
- fi
- fi
- exec "$PRGDIR"/"$EXECUTABLE" stop "$@"
代码清单10和代码清单1非常相似,其中也有两个主要的变量,分别是:
- PRGDIR:当前shell脚本所在的路径;
- EXECUTABLE:脚本catalina.sh。
根据最后一行代码:exec "$PRGDIR"/"$EXECUTABLE" stop "$@",我们知道执行了shell脚本catalina.sh,并且传递参数stop。catalina.sh中接收到stop参数后的执行的脚本分支见代码清单11。
代码清单11
- elif [ "$1" = "stop" ] ; then
- #省略参数校验脚本
- eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \
- -Djava.endorsed.dirs="\"$JAVA_ENDORSED_DIRS\"" -classpath "\"$CLASSPATH\"" \
- -Dcatalina.base="\"$CATALINA_BASE\"" \
- -Dcatalina.home="\"$CATALINA_HOME\"" \
- -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \
- org.apache.catalina.startup.Bootstrap "$@" stop
从代码清单11可以看出,最终使用java命令执行了org.apache.catalina.startup.Bootstrap类中的main方法,参数是stop。从代码清单3可以看出,当传递参数stop的时候,command等于stop,此时main方法的执行步骤如下:
步骤一 初始化Bootstrap
已经在启动过程分析中介绍, 不再赘述。
步骤二 停止服务
通过调用Bootstrap的stopServer方法(见代码清单12)停止Tomcat,其实质是用反射调用catalinaDaemon(类型是Catalina)的stopServer方法。
代码清单12
- /**
- * Stop the standalone server.
- */
- public void stopServer(String[] arguments)
- throws Exception {
- Object param[];
- Class<?> paramTypes[];
- if (arguments==null || arguments.length==0) {
- paramTypes = null;
- param = null;
- } else {
- paramTypes = new Class[1];
- paramTypes[0] = arguments.getClass();
- param = new Object[1];
- param[0] = arguments;
- }
- Method method =
- catalinaDaemon.getClass().getMethod("stopServer", paramTypes);
- method.invoke(catalinaDaemon, param);
- }
Catalina的stopServer方法(见代码清单13)的执行步骤如下:
- 创建Digester解析server.xml文件(此处只解析<Server>标签),以构造出Server容器(此时Server容器的子容器没有被实例化);
- 从实例化的Server容器获取Server的socket监听端口和地址,然后创建Socket对象连接启动Tomcat时创建的ServerSocket,最后向ServerSocket发送SHUTDOWN命令。根据代码清单9的内容,ServerSocket循环等待接收到SHUTDOWN命令后,最终调用stop方法停止Tomcat。
代码清单13
- public void stopServer() {
- stopServer(null);
- }
- public void stopServer(String[] arguments) {
- if (arguments != null) {
- arguments(arguments);
- }
- if( getServer() == null ) {
- // Create and execute our Digester
- Digester digester = createStopDigester();
- digester.setClassLoader(Thread.currentThread().getContextClassLoader());
- File file = configFile();
- try {
- InputSource is =
- new InputSource("file://" + file.getAbsolutePath());
- FileInputStream fis = new FileInputStream(file);
- is.setByteStream(fis);
- digester.push(this);
- digester.parse(is);
- fis.close();
- } catch (Exception e) {
- log.error("Catalina.stop: ", e);
- System.exit(1);
- }
- }
- // Stop the existing server
- try {
- if (getServer().getPort()>0) {
- Socket socket = new Socket(getServer().getAddress(),
- getServer().getPort());
- OutputStream stream = socket.getOutputStream();
- String shutdown = getServer().getShutdown();
- for (int i = 0; i < shutdown.length(); i++)
- stream.write(shutdown.charAt(i));
- stream.flush();
- stream.close();
- socket.close();
- } else {
- log.error(sm.getString("catalina.stopServer"));
- System.exit(1);
- }
- } catch (IOException e) {
- log.error("Catalina.stop: ", e);
- System.exit(1);
- }
- }
最后,我们看看Catalina的stop方法(见代码清单14)的实现,其执行步骤如下:
- 将启动过程中添加的关闭钩子移除。Tomcat启动过程辛辛苦苦添加的关闭钩子为什么又要去掉呢?因为关闭钩子是为了在JVM异常退出后,进行资源的回收工作。主动停止Tomcat时调用的stop方法里已经包含了资源回收的内容,所以不再需要这个钩子了。
- 停止Server容器。有关容器的停止内容,请阅读《TOMCAT源码分析——生命周期管理》一文。
代码清单14
- /**
- * Stop an existing server instance.
- */
- public void stop() {
- try {
- // Remove the ShutdownHook first so that server.stop()
- // doesn't get invoked twice
- if (useShutdownHook) {
- Runtime.getRuntime().removeShutdownHook(shutdownHook);
- // If JULI is being used, re-enable JULI's shutdown to ensure
- // log messages are not lost jiaan
- LogManager logManager = LogManager.getLogManager();
- if (logManager instanceof ClassLoaderLogManager) {
- ((ClassLoaderLogManager) logManager).setUseShutdownHook(
- true);
- }
- }
- } catch (Throwable t) {
- // This will fail on JDK 1.2. Ignoring, as Tomcat can run
- // fine without the shutdown hook.
- }
- // Shut down the server
- try {
- getServer().stop();
- } catch (LifecycleException e) {
- log.error("Catalina.stop", e);
- }
- }
总结
通过对Tomcat源码的分析我们了解到Tomcat的启动和停止都离不开org.apache.catalina.startup.Bootstrap。当停止Tomcat时,已经启动的Tomcat作为socket服务端,停止脚本启动的Bootstrap进程作为socket客户端向服务端发送shutdown命令,两个进程通过共享server.xml里Server标签的端口以及地址信息打通了socket的通信。
如需转载,请标明本文作者及出处——作者:jiaan.gja,本文原创首发:博客园,原文链接:http://www.cnblogs.com/jiaan-geng/p/4872550.html
Tomcat源码分析——启动与停止服务的更多相关文章
- TOMCAT源码分析(启动框架)
建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, 是不那么容易掌握TOMCAT的框架的. 所以得实践.实践.再实践. 建议下载一份TOMCAT的源码, 调试通过, 然后单步跟踪其启动 ...
- tomcat 源码分析
Tomcat源码分析——Session管理分析(下) Tomcat源码分析——Session管理分析(上) Tomcat源码分析——请求原理分析(下) Tomcat源码分析——请 ...
- [Tomcat 源码分析系列] (二) : Tomcat 启动脚本-catalina.bat
概述 Tomcat 的三个最重要的启动脚本: startup.bat catalina.bat setclasspath.bat 上一篇咱们分析了 startup.bat 脚本 这一篇咱们来分析 ca ...
- Tomcat源码分析之—具体启动流程分析
从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息 ...
- Tomcat源码分析三:Tomcat启动加载过程(一)的源码解析
Tomcat启动加载过程(一)的源码解析 今天,我将分享用源码的方式讲解Tomcat启动的加载过程,关于Tomcat的架构请参阅<Tomcat源码分析二:先看看Tomcat的整体架构>一文 ...
- Tomcat源码分析
前言: 本文是我阅读了TOMCAT源码后的一些心得. 主要是讲解TOMCAT的系统框架, 以及启动流程.若有错漏之处,敬请批评指教! 建议: 毕竟TOMCAT的框架还是比较复杂的, 单是从文字上理解, ...
- Tomcat源码分析--转
一.架构 下面谈谈我对Tomcat架构的理解 总体架构: 1.面向组件架构 2.基于JMX 3.事件侦听 1)面向组件架构 tomcat代码看似很庞大,但从结构上看却很清晰和简单,它主要由一堆组件组成 ...
- Tomcat源码分析——Session管理分析(上)
前言 对于广大java开发者而已,对于J2EE规范中的Session应该并不陌生,我们可以使用Session管理用户的会话信息,最常见的就是拿Session用来存放用户登录.身份.权限及状态等信息.对 ...
- Tomcat源码分析——请求原理分析(中)
前言 在<TOMCAT源码分析——请求原理分析(上)>一文中已经介绍了关于Tomcat7.0处理请求前作的初始化和准备工作,请读者在阅读本文前确保掌握<TOMCAT源码分析——请求原 ...
随机推荐
- django:multivaluedictkeyerror错误
查了一下,是因为获取前台数据时,用了request.POST[],改用request.POST.get()之后没有这个报错了 细节: request.POST是用来接受从前端表单中传过来的数据,比如用 ...
- 查找 SQL SERVER 所有表记录数
-- 所有表的记录数 SELECT a.name, b.rowsFROM sysobjects AS a INNER JOIN sysindexes AS b ON a.id = b.idWHERE ...
- leetcode 实现strStr()
实现strStr()函数. 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始).如果不存在,则返回 ...
- Let it crash philosophy part II
Designing fault tolerant systems is extremely difficult. You can try to anticipate and reason about ...
- AndroidSDK下载
C:\Windows\System32\drivers\etc\hosts74.125.237.1 dl-ssl.google.com
- struts2 Convention插件好处及使用
现在JAVA开发都流行SSH.而很大部分公司也使用了struts2进行开发..因为struts2提供了很多插件和标签方便使用..在之前开发过程中总发现使用了struts2会出现很多相应的配合文件.如果 ...
- You can't specify target table 'e' for update in FROM clause
UPDATE emp e SET e.salary=e.salary+7 WHERE e.id IN(SELECT e1.id FROM emp e1,dept d WHERE e1.dep_id=d ...
- codeforces 1096 题解
A: 发现最优的方案一定是选 $ l $ 和 $ 2 * l $,题目保证有解,直接输出即可 #include <bits/stdc++.h> #define Fast_cin ios:: ...
- 老调重弹-access注入过主机卫
本文作者:i春秋签约作家——非主流 大家好,我是来自农村的非主流,今天就给在座的各位表演个绝活. 首先打开服务器上安装了主机卫士的网站. 尝试在变量id的值后面插入万恶的单引号,根据报错,我们可以分析 ...
- 如何在CentOS 7上使用vsftpd(FTP)的配置文件介绍
vsftpd.conf - vsftpd的配置文件. 描述 vsftpd.conf可用于控制vsftpd行为的各个方面. 默认情况下,vsftpd在/etc/vsftpd.conf位置查找此文件. 但 ...