亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Tomcat源碼分析——啟動與停止服務

2019-11-14 15:28:26
字體:
來源:轉載
供稿:網友

前言

  熟悉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=falsecase "`uname`" inOS400*) os400=true;;esac# resolve links - $0 may be a softlinkPRG="$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"  fidonePRGDIR=`dirname "$PRG"`EXECUTABLE=catalina.sh# Check that target executable existsif $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  evalelse  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  fifiexec "$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 1 ]; 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" 2>&1 "&"  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" 2>&1 "&"  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)的執行步驟如下:

  1. 設置Catalina路徑,默認為Tomcat的根目錄;
  2. 初始化Tomcat的類加載器,并設置線程上下文類加載器(具體實現細節,讀者可以參考《TOMCAT源碼分析——類加載體系》一文);
  3. 用反射實例化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)的執行步驟如下:

  1. 驗證Server容器是否已經實例化。如果沒有實例化Server容器,還會再次調用Catalina的load方法加載和解析server.xml,這也說明Tomcat只允許Server容器通過配置在server.xml的方式生成,用戶也可以自己實現Server接口創建自定義的Server容器以取代默認的StandardServer。
  2. 啟動Server容器,有關容器的啟動過程的分析可以參考《TOMCAT源碼分析——生命周期管理》一文的內容。
  3. 設置關閉鉤子。這么說可能有些不好理解,那就換個說法。Tomcat本身可能由于所在機器斷點,程序bug甚至內存溢出導致進程退出,但是Tomcat可能需要在退出的時候做一些清理工作,比如:內存清理、對象銷毀等。這些清理動作需要封裝在一個Thread的實現中,然后將此Thread對象作為參數傳遞給Runtime的addShutdownHook方法即可。
  4. 最后調用Catalina的await方法循環等待接收Tomcat的shutdown命令。
  5. 如果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)的執行步驟如下:

  1. 創建socket連接的服務端對象ServerSocket;
  2. 循環等待接收客戶端發出的命令,如果接收到的命令與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=falsecase "`uname`" inOS400*) os400=true;;esac# resolve links - $0 may be a softlinkPRG="$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"  fidonePRGDIR=`dirname "$PRG"`EXECUTABLE=catalina.sh# Check that target executable existsif $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  evalelse  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  fifiexec "$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)的執行步驟如下:

  1. 創建Digester解析server.xml文件(此處只解析<Server>標簽),以構造出Server容器(此時Server容器的子容器沒有被實例化);
  2. 從實例化的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)的實現,其執行步驟如下:

  1. 將啟動過程中添加的關閉鉤子移除。Tomcat啟動過程辛辛苦苦添加的關閉鉤子為什么又要去掉呢?因為關閉鉤子是為了在JVM異常退出后,進行資源的回收工作。主動停止Tomcat時調用的stop方法里已經包含了資源回收的內容,所以不再需要這個鉤子了。
  2. 停止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.49028c.com/jiaan-geng/p/4872550.html

 

 


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲综合大片69999| 日本国产一区二区三区| 欧美久久精品午夜青青大伊人| 国产精品18久久久久久麻辣| 日韩精品视频免费在线观看| 亚洲美女福利视频网站| 国产一区二区三区视频在线观看| 欧美专区日韩视频| 国产suv精品一区二区| 国产精品日韩在线一区| 国产主播精品在线| 国产999在线| 热久久99这里有精品| 欧美激情视频给我| 久久韩国免费视频| 欧美激情女人20p| 欧美色另类天堂2015| 亚洲天堂av在线播放| 韩国19禁主播vip福利视频| 日韩视频精品在线| 国产精品午夜一区二区欲梦| 亚洲最大的av网站| 亚洲午夜国产成人av电影男同| 国产一区二区三区三区在线观看| 欧美激情国产日韩精品一区18| 欧美xxxwww| 亲子乱一区二区三区电影| 欧美日韩一区二区在线| 欧美激情视频三区| 国产精品视频久久久久| 欧美成人精品在线观看| 日本久久久a级免费| 亚洲精品一区二区久| 国模私拍一区二区三区| 精品女同一区二区三区在线播放| 国产精品pans私拍| 久久999免费视频| 成人情趣片在线观看免费| 久久精品美女视频网站| 亚洲国产精品久久久久秋霞不卡| 欧美丰满片xxx777| 亚洲片在线资源| 欧美电影在线观看高清| 在线观看久久久久久| 国产精品pans私拍| 国产精品99久久久久久久久| 美女啪啪无遮挡免费久久网站| 69av在线播放| 精品久久久久久久久国产字幕| 国产精品扒开腿做爽爽爽视频| 日韩欧美中文字幕在线观看| 日韩欧美综合在线视频| 欧美麻豆久久久久久中文| 国产成人精品久久亚洲高清不卡| 欧美日韩在线视频一区| 狠狠躁18三区二区一区| 中日韩美女免费视频网站在线观看| 欧美在线视频免费播放| 久久久久久久亚洲精品| 欧美在线中文字幕| www.亚洲免费视频| 日韩一区二区久久久| 欧美激情第6页| 国产精品久久久久久久7电影| 国产精品成人播放| 久久综合久久美利坚合众国| 国产香蕉一区二区三区在线视频| 国产视频在线观看一区二区| 日韩在线观看免费全集电视剧网站| 精品亚洲一区二区| 亚洲激情成人网| 色偷偷av一区二区三区| 一区二区三区亚洲| 亚洲成人网久久久| 久久影视免费观看| 亚洲国产精品电影| 欧美日韩在线观看视频小说| 国产91在线播放九色快色| 亚洲乱码国产乱码精品精天堂| 在线精品国产成人综合| 国产欧美久久一区二区| 欧美在线欧美在线| 久久视频在线看| 久久不射热爱视频精品| 久久影视电视剧凤归四时歌| 九九热这里只有在线精品视| 日韩欧美一区二区三区| 在线观看国产欧美| 欧美黄色www| 久久久久久久久电影| 国产福利精品在线| 中文字幕亚洲欧美日韩在线不卡| 久久精品91久久久久久再现| 91久久综合亚洲鲁鲁五月天| 欧美成人性色生活仑片| 欧美日韩国产精品一区二区三区四区| 日韩成人中文字幕| 亚洲国产天堂久久综合| 国产精品美女久久久免费| 国产精品嫩草影院久久久| 国内精品久久久久影院 日本资源| 成人国产在线激情| 欧美一级大片在线免费观看| 久久久久国产精品www| 久久免费国产精品1| 日韩视频中文字幕| 日韩精品极品毛片系列视频| 亚洲精品二三区| 欧美精品激情在线| 777国产偷窥盗摄精品视频| 国产一区二区三区在线播放免费观看| 国产精品视频xxx| 国产精品一区av| 中文字幕日韩在线播放| 97在线视频观看| 国产精品男人的天堂| 38少妇精品导航| 国产日韩在线看片| 欧美精品精品精品精品免费| 色中色综合影院手机版在线观看| 精品亚洲va在线va天堂资源站| 亚洲国产免费av| 亚洲深夜福利网站| 亚洲精品久久久久久下一站| 国产精品激情av电影在线观看| 欧美成人精品一区二区| 日韩在线一区二区三区免费视频| 欧美高清视频免费观看| 永久555www成人免费| 亚洲精品视频播放| 亚洲欧美制服另类日韩| 青青草原成人在线视频| 久久久国产精品免费| 久久久久久亚洲| 精品久久久久久电影| 国产精品都在这里| 久久精品2019中文字幕| 国产aⅴ夜夜欢一区二区三区| 最近2019中文字幕mv免费看| 国产视频亚洲精品| 国产欧美在线观看| 欧美一级视频免费在线观看| 精品日韩视频在线观看| 欧美一区二区三区精品电影| 精品国产福利视频| 国产精品视频网| 日韩电影在线观看永久视频免费网站| 2019av中文字幕| 久久久久国产精品免费网站| 91亚洲精品久久久久久久久久久久| 日本电影亚洲天堂| 国产成人精品综合久久久| 九九热精品视频在线播放| 2019中文字幕在线观看| 欧美日本高清一区| 亚洲网在线观看| 国产免费一区二区三区在线能观看| 性欧美在线看片a免费观看| 日本高清不卡在线| 亚洲精品电影网在线观看| 亚洲成人三级在线| 最近的2019中文字幕免费一页| 日本欧美精品在线| 欧美日韩成人在线视频|