码迷,mamicode.com
首页 > 数据库 > 详细

Oracle Listener

时间:2018-04-24 21:54:18      阅读:229      评论:0      收藏:0      [点我收藏+]

标签:程序   getopts   数据库服务器   认证   try   created   prot   values   bash   

一、监听器功能

1)监听客户端请求:监听器作为独立进程运行在数据库服务器上,监听特定网络端口(默认1521)服务请求。

2)为客户端请求分配oracle Server Process:监听器不直接处理客户端发送过来的SQL命令,而是作为代理,将分配一个服务进程与客户端建立通信连接,由server process处理SQL命令并返回结果。

3)注册服务:通过注册过程实现监听器与数据库实例之间了联系,注册的过程就是告知监听器数据库实例名称和服务名,目前Oracle版本中,提供动态注册和静态注册两种方式。

4)故障转移failover: Failover是RAC容错的一个重要方面功能,当数据库实例发生崩溃时,监听器可以自动将请求转移到其它可用实例上。

5)负载均衡衡:在RAC架构中,当一个客户请求到来时,Oracle会根据当前RAC集群环境中所有实例的负载情况,避开负载较高的实例,将请求转移到负载较低的实例进行处理。

 

二、注册过程

注册的作用就是实现数据库实例名和服务名注册到运行的监听器程序中。

Figure 2-1 Example listener.ora File

LISTENER=
  (DESCRIPTION=
    (ADDRESS_LIST=
      (ADDRESS=(PROTOCOL=tcp)(HOST=sale-server)(PORT=1521))
      (ADDRESS=(PROTOCOL=ipc)(KEY=extproc))))-- 静态注册部分
SID_LIST_LISTENER=
  (SID_LIST=
    (SID_DESC=
      (GLOBAL_DBNAME=sales.us.acme.com)
      (ORACLE_HOME=/oracle9i)
      (SID_NAME=sales))
    (SID_DESC=
      (SID_NAME=plsextproc)
      (ORACLE_HOME=/oracle9i)
      (PROGRAM=extproc)))

2.1) 默认是动态注册的时候,只有PLSExtProc项目。SID_LIST里用来配置当前监听器静态注册的服务项目,其中,通过一个或多个SID_DESC进行配置。静态配置项目中,通过GLOBAL_NAME配置服务项目,通过SID_NAME指定数据库实例的名称,通过ORACLE_HOME配置Oracle数据库软件安装的基本目录。

2.2)动态注册由PMON后台进程周期(2分钟)发起注册。

-- 手工进行注册
alter system set LOCAL_LISTENER=(ADDRESS = (PROTOCOL = TCP)(HOST = 10.150.10.150)(PORT = 1521));
alter system register;

 

三、监听器信息

通过lsnrctl监听器控制程序与监听器进行交互和控制操作获取相对应信息。

技术分享图片

技术分享图片

注:

-- 这在10g及以后版本不是必要的安全检查条件,若想要取消此安全设置,可以在listener.ora文件中设置上LOCAL_OS_AUTHENTICATION_[listener name]=OFF
set
password

  监听器密码设置在ORACLE 9i版本中有效,在10g版本中,即使设置密码后,依然可以不用输入密码停止监听服务,因为In Oracle 10, the TNSListener is secure out of the box and there should not be a need to set a listener password as in older versions of the Oracle listener。
  Oracle10g以后,设置Listener密码已经不是安全检查的必要条件了,因为默认在10g里面除了启动监听的用户之外,其它用户都无法停止Listener(还有另外一些lsnrctl的命令也同样被禁止了,比如trace, reload等),即使Listener没有设置密码。在默认情况下,启动Listener或者使用lsnrctl status命令查看监听状态,可以看到:Security ON: Password OR Local OS Authentication这表明Listener的安全机制使用了Password方式或者Local OS Authentication方式,在这种状态下,即使是设置了监听密码,对于启动监听的user来说,也仍然是不需要任何密码就可以停止监听的。

3.1)set 命令操作配置

参数

描叙

set password

 

set rawmode

设置rawmode

set displaymode

把lsnrctl工具的显示模式设置成RAW、COMPACT、NORMAL或VERBOSE

set trc_file

设置监听跟踪文件的名称

set trc_directory

设置监听器跟踪目录的名称

set trc_level

把跟踪级别设置为OFF、USER、ADMIN、SUPPORT模式

set log_file

显示或设置日志文件

set log_directory

设置日志目录位置

set log_status

设置是否为该监听器打开日志特性

set current_listener

设置当前监听器为指定监听器

set inbound_connect_timeout

设置参数指定的时间,在几秒钟内为客户完成网络连接已经建立后,其连接请求的监听

set startup_waittime

设置监听器等待响应lsnrctl 命令行工具中的一条STATUS命令的时间长度

set save_config_on_stop

在退出lsnrctl工具时保存对listener.ora文件的修改

set dynamic_registration

使用的DYNAMIC_REGISTRATION_listener_name的参数启用或禁用动态注册。当设置为on,听者接受动态登记;设置为关闭时,听者拒绝动态注册。静态注册不受影响

set enable_global_dynamic_endpoint

 

四、监听器日志解析

1)日志格式:TIMESTAMP * CONNECT DATA [* PROTOCOL INFO] * EVENT [* SID] * RETURN CODE, 使用*进行内容分割。

 技术分享图片

2)监听日志截断,防止日志文件过大,一般不能超过2GB,超过会导致LISTENER监听器无法处理新的连接或是给写入、查看带来的一些性能问题。

Step 1:首先停止监听服务进程(tnslsnr)记录日志。

Step 2:将监听日志文件(listener.log)复制一份,以listener.log.yyyymmdd格式命名

Step 3:将监听日志文件(listener.log)清空。(如:mv命令等)

Step 4:开启监听服务进程(tnslsnr)记录日志

技术分享图片
#!/bin/bash

# 00 00 * * * /home/oracle/_cron/cls_oracle/cls_oracle.sh -d 31 > /home/oracle/_cron/cls_oracle/cls_oracle.sh.log 2>&1

# Script used to cleanup any Oracle environment.

#

# Cleans:      audit_log_dest

#              background_dump_dest

#              core_dump_dest

#              user_dump_dest

#

# Rotates:     Alert Logs

#              Listener Logs

#

# Scheduling:  00 00 * * * /home/oracle/_cron/cls_oracle/cls_oracle.sh -d 31 > /home/oracle/_cron/cls_oracle/cls_oracle.log 2>&1

#

# Created By:  Tommy Wang  2012-09-10

#

# History:

#

RM="rm -f"

RMDIR="rm -rf"

LS="ls -l"

MV="mv"

TOUCH="touch"

TESTTOUCH="echo touch"

TESTMV="echo mv"

TESTRM=$LS

TESTRMDIR=$LS

SUCCESS=0

FAILURE=1

TEST=0

HOSTNAME=`hostname`

ORAENV="oraenv"

TODAY=`date +%Y%m%d`

ORIGPATH=/usr/local/bin:$PATH

ORIGLD=$LD_LIBRARY_PATH

export PATH=$ORIGPATH

# Usage function.

f_usage(){

  echo "Usage: `basename $0` -d DAYS [-a DAYS] [-b DAYS] [-c DAYS] [-n DAYS] [-r DAYS] [-u DAYS] [-t] [-h]"

  echo "       -d = Mandatory default number of days to keep log files that are not explicitly passed as parameters."

  echo "       -a = Optional number of days to keep audit logs."

  echo "       -b = Optional number of days to keep background dumps."

  echo "       -c = Optional number of days to keep core dumps."

  echo "       -n = Optional number of days to keep network log files."

  echo "       -r = Optional number of days to keep clusterware log files."

  echo "       -u = Optional number of days to keep user dumps."

  echo "       -h = Optional help mode."

  echo "       -t = Optional test mode. Does not delete any files."

}

if [ $# -lt 1 ]; then

  f_usage

  exit $FAILURE

fi

# Function used to check the validity of days.

f_checkdays(){

  if [ $1 -lt 1 ]; then

    echo "ERROR: Number of days is invalid."

    exit $FAILURE

  fi

  if [ $? -ne 0 ]; then

    echo "ERROR: Number of days is invalid."

    exit $FAILURE

  fi

}

# Function used to cut log files.

f_cutlog(){

  # Set name of log file.

  LOG_FILE=$1

  CUT_FILE=${LOG_FILE}.${TODAY}

  FILESIZE=`ls -l $LOG_FILE | awk {print $5}`

  # Cut the log file if it has not been cut today.

  if [ -f $CUT_FILE ]; then

    echo "Log Already Cut Today: $CUT_FILE"

  elif [ ! -f $LOG_FILE ]; then

    echo "Log File Does Not Exist: $LOG_FILE"

  elif [ $FILESIZE -eq 0 ]; then

    echo "Log File Has Zero Size: $LOG_FILE"

  else

    # Cut file.

    echo "Cutting Log File: $LOG_FILE"

    $MV $LOG_FILE $CUT_FILE

    $TOUCH $LOG_FILE

  fi

}

# Function used to delete log files.

f_deletelog(){

  # Set name of log file.

  CLEAN_LOG=$1

  # Set time limit and confirm it is valid.

  CLEAN_DAYS=$2

  f_checkdays $CLEAN_DAYS



  # Delete old log files if they exist.

  find $CLEAN_LOG.[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9] -type f -mtime +$CLEAN_DAYS -exec $RM {} \; 2>/dev/null

}



# Function used to get database parameter values.

f_getparameter(){

  if [ -z "$1" ]; then

    return

  fi

  PARAMETER=$1

  sqlplus -s /nolog <<EOF | awk -F= "/^a=/ {print \$2}"

set head off pagesize 0 feedback off linesize 200

whenever sqlerror exit 1

conn / as sysdba

select a=||value from v\$parameter where name = $PARAMETER;

EOF

}

# Function to get unique list of directories.

f_getuniq(){

  if [ -z "$1" ]; then

    return

  fi

  ARRCNT=0

  MATCH=N

  x=0

  for e in `echo $1`; do

    if [ ${#ARRAY[*]} -gt 0 ]; then

      # See if the array element is a duplicate.

      while [ $x -lt  ${#ARRAY[*]} ]; do

        if [ "$e" = "${ARRAY[$x]}" ]; then

          MATCH=Y

        fi

      done

    fi

    if [ "$MATCH" = "N" ]; then

      ARRAY[$ARRCNT]=$e

      ARRCNT=`expr $ARRCNT+1`

    fi

    x=`expr $x + 1`

  done

  echo ${ARRAY[*]}

}

# Parse the command line options.

while getopts a:b:c:d:n:r:u:th OPT; do

  case $OPT in

    a) ADAYS=$OPTARG

       ;;

    b) BDAYS=$OPTARG

       ;;

    c) CDAYS=$OPTARG

       ;;

    d) DDAYS=$OPTARG

       ;;

    n) NDAYS=$OPTARG

       ;;

    r) RDAYS=$OPTARG

       ;;

    u) UDAYS=$OPTARG

       ;;

    t) TEST=1

       ;;

    h) f_usage

       exit 0

       ;;

    *) f_usage

       exit 2

       ;;

  esac

done

shift $(($OPTIND - 1))

# Ensure the default number of days is passed.

if [ -z "$DDAYS" ]; then

  echo "ERROR: The default days parameter is mandatory."

  f_usage

  exit $FAILURE

fi

f_checkdays $DDAYS

echo "`basename $0` Started `date`."

# Use test mode if specified.

if [ $TEST -eq 1 ]

then

  RM=$TESTRM

  RMDIR=$TESTRMDIR

  MV=$TESTMV

  TOUCH=$TESTTOUCH

  echo "Running in TEST mode."

fi

# Set the number of days to the default if not explicitly set.

ADAYS=${ADAYS:-$DDAYS}; echo "Keeping audit logs for $ADAYS days."; f_checkdays $ADAYS

BDAYS=${BDAYS:-$DDAYS}; echo "Keeping background logs for $BDAYS days."; f_checkdays $BDAYS

CDAYS=${CDAYS:-$DDAYS}; echo "Keeping core dumps for $CDAYS days."; f_checkdays $CDAYS

NDAYS=${NDAYS:-$DDAYS}; echo "Keeping network logs for $NDAYS days."; f_checkdays $NDAYS

RDAYS=${RDAYS:-$DDAYS}; echo "Keeping clusterware logs for $RDAYS days."; f_checkdays $RDAYS

UDAYS=${UDAYS:-$DDAYS}; echo "Keeping user logs for $UDAYS days."; f_checkdays $UDAYS

# Check for the oratab file.

if [ -f /var/opt/oracle/oratab ]; then

  ORATAB=/var/opt/oracle/oratab

elif [ -f /etc/oratab ]; then

  ORATAB=/etc/oratab

else

  echo "ERROR: Could not find oratab file."

  exit $FAILURE

fi

# Build list of distinct Oracle Home directories.

OH=`egrep -i ":Y|:N" $ORATAB | grep -v "^#" | grep -v "\*" | cut -d":" -f2 | sort | uniq`

# Exit if there are not Oracle Home directories.

if [ -z "$OH" ]; then

  echo "No Oracle Home directories to clean."

  exit $SUCCESS

fi

# Get the list of running databases.

SIDS=`ps -e -o args | grep pmon | grep -v grep | awk -F_ {print $3} | sort`

# Gather information for each running database.

for ORACLE_SID in `echo $SIDS`

do

  # Set the Oracle environment.

  ORAENV_ASK=NO

  export ORACLE_SID

  . $ORAENV

  if [ $? -ne 0 ]; then

    echo "Could not set Oracle environment for $ORACLE_SID."

  else

    export LD_LIBRARY_PATH=$ORACLE_HOME/lib:$ORIGLD

    ORAENV_ASK=YES

    echo "ORACLE_SID: $ORACLE_SID"

    # Get the audit_dump_dest.

    ADUMPDEST=`f_getparameter audit_dump_dest`

    if [ ! -z "$ADUMPDEST" ] && [ -d "$ADUMPDEST" 2>/dev/null ]; then

      echo "  Audit Dump Dest: $ADUMPDEST"

      ADUMPDIRS="$ADUMPDIRS $ADUMPDEST"

    fi

    # Get the background_dump_dest.

    BDUMPDEST=`f_getparameter background_dump_dest`

    echo "  Background Dump Dest: $BDUMPDEST"

    if [ ! -z "$BDUMPDEST" ] && [ -d "$BDUMPDEST" ]; then

      BDUMPDIRS="$BDUMPDIRS $BDUMPDEST"

    fi

    # Get the core_dump_dest.

    CDUMPDEST=`f_getparameter core_dump_dest`

    echo "  Core Dump Dest: $CDUMPDEST"

    if [ ! -z "$CDUMPDEST" ] && [ -d "$CDUMPDEST" ]; then

      CDUMPDIRS="$CDUMPDIRS $CDUMPDEST"

    fi

    # Get the user_dump_dest.

    UDUMPDEST=`f_getparameter user_dump_dest`

    echo "  User Dump Dest: $UDUMPDEST"

    if [ ! -z "$UDUMPDEST" ] && [ -d "$UDUMPDEST" ]; then

      UDUMPDIRS="$UDUMPDIRS $UDUMPDEST"

    fi

  fi

done

# Do cleanup for each Oracle Home.

for ORAHOME in `f_getuniq "$OH"`

do

  # Get the standard audit directory if present.

  if [ -d $ORAHOME/rdbms/audit ]; then

     ADUMPDIRS="$ADUMPDIRS $ORAHOME/rdbms/audit"

  fi

  # Get the Cluster Ready Services Daemon (crsd) log directory if present.

  if [ -d $ORAHOME/log/$HOSTNAME/crsd ]; then

    CRSLOGDIRS="$CRSLOGDIRS $ORAHOME/log/$HOSTNAME/crsd"

  fi

  # Get the  Oracle Cluster Registry (OCR) log directory if present.

  if [ -d $ORAHOME/log/$HOSTNAME/client ]; then

    OCRLOGDIRS="$OCRLOGDIRS $ORAHOME/log/$HOSTNAME/client"

  fi

  # Get the Cluster Synchronization Services (CSS) log directory if present.

  if [ -d $ORAHOME/log/$HOSTNAME/cssd ]; then

    CSSLOGDIRS="$CSSLOGDIRS $ORAHOME/log/$HOSTNAME/cssd"

  fi

  # Get the Event Manager (EVM) log directory if present.

  if [ -d $ORAHOME/log/$HOSTNAME/evmd ]; then

    EVMLOGDIRS="$EVMLOGDIRS $ORAHOME/log/$HOSTNAME/evmd"

  fi

  # Get the RACG log directory if present.

  if [ -d $ORAHOME/log/$HOSTNAME/racg ]; then

    RACGLOGDIRS="$RACGLOGDIRS $ORAHOME/log/$HOSTNAME/racg"

  fi

done

# Clean the audit_dump_dest directories.

if [ ! -z "$ADUMPDIRS" ]; then

  for DIR in `f_getuniq "$ADUMPDIRS"`; do

    if [ -d $DIR ]; then

      echo "Cleaning Audit Dump Directory: $DIR"

      find $DIR -type f -name "*.aud" -mtime +$ADAYS -exec $RM {} \; 2>/dev/null

    fi

  done

fi

# Clean the background_dump_dest directories.

if [ ! -z "$BDUMPDIRS" ]; then

  for DIR in `f_getuniq "$BDUMPDIRS"`; do

    if [ -d $DIR ]; then

      echo "Cleaning Background Dump Destination Directory: $DIR"

      # Clean up old trace files.

      find $DIR -type f -name "*.tr[c,m]" -mtime +$BDAYS -exec $RM {} \; 2>/dev/null

      find $DIR -type d -name "cdmp*" -mtime +$BDAYS -exec $RMDIR {} \; 2>/dev/null

    fi



    if [ -d $DIR ]; then

      # Cut the alert log and clean old ones.

      for f in `find $DIR -type f -name "alert\_*.log" ! -name "alert_[0-9A-Z]*.[0-9]*.log" 2>/dev/null`; do

        echo "Alert Log: $f"

        f_cutlog $f

        f_deletelog $f $BDAYS

      done

    fi

  done

fi

# Clean the core_dump_dest directories.

if [ ! -z "$CDUMPDIRS" ]; then

  for DIR in `f_getuniq "$CDUMPDIRS"`; do

    if [ -d $DIR ]; then

      echo "Cleaning Core Dump Destination: $DIR"

      find $DIR -type d -name "core*" -mtime +$CDAYS -exec $RMDIR {} \; 2>/dev/null

    fi

  done

fi

# Clean the user_dump_dest directories.

if [ ! -z "$UDUMPDIRS" ]; then

  for DIR in `f_getuniq "$UDUMPDIRS"`; do

    if [ -d $DIR ]; then

      echo "Cleaning User Dump Destination: $DIR"

      find $DIR -type f -name "*.trc" -mtime +$UDAYS -exec $RM {} \; 2>/dev/null

    fi

  done

fi

# Cluster Ready Services Daemon (crsd) Log Files

for DIR in `f_getuniq "$CRSLOGDIRS $OCRLOGDIRS $CSSLOGDIRS $EVMLOGDIRS $RACGLOGDIRS"`; do

  if [ -d $DIR ]; then

    echo "Cleaning Clusterware Directory: $DIR"

    find $DIR -type f -name "*.log" -mtime +$RDAYS -exec $RM {} \; 2>/dev/null

  fi

done

# Clean Listener Log Files.

# Get the list of running listeners. It is assumed that if the listener is not running, the log file does not need to be cut.

ps -e -o args | grep tnslsnr | grep -v grep | while read LSNR; do

  # Derive the lsnrctl path from the tnslsnr process path.

  TNSLSNR=`echo $LSNR | awk {print $1}`

  ORACLE_PATH=`dirname $TNSLSNR`

  ORACLE_HOME=`dirname $ORACLE_PATH`

  PATH=$ORACLE_PATH:$ORIGPATH

  LD_LIBRARY_PATH=$ORACLE_HOME/lib:$ORIGLD

  LSNRCTL=$ORACLE_PATH/lsnrctl

  echo "Listener Control Command: $LSNRCTL"



  # Derive the listener name from the running process.

  LSNRNAME=`echo $LSNR | awk {print $2} | tr "[:upper:]" "[:lower:]"`

  echo "Listener Name: $LSNRNAME"



  # Get the listener version.

  LSNRVER=`$LSNRCTL version | grep "LSNRCTL" | grep "Version" | awk {print $5} | awk -F. {print $1}`

  echo "Listener Version: $LSNRVER"

  # Get the TNS_ADMIN variable.

  echo "Initial TNS_ADMIN: $TNS_ADMIN"

  unset TNS_ADMIN

  TNS_ADMIN=`$LSNRCTL status $LSNRNAME | grep "Listener Parameter File" | awk {print $4}`

  if [ ! -z $TNS_ADMIN ]; then

    export TNS_ADMIN=`dirname $TNS_ADMIN`

  else

    export TNS_ADMIN=$ORACLE_HOME/network/admin

  fi

  echo "Network Admin Directory: $TNS_ADMIN"

  # If the listener is 11g, get the diagnostic dest, etc...

  if [ $LSNRVER -ge 11 ]; then



    # Get the listener log file directory.

    LSNRDIAG=`$LSNRCTL<<EOF | grep log_directory | awk {print $6}

set current_listener $LSNRNAME

show log_directory

EOF`

    echo "Listener Diagnostic Directory: $LSNRDIAG"

    # Get the listener trace file name.

    LSNRLOG=`lsnrctl<<EOF | grep trc_directory | awk {print $6"/"$1".log"}

set current_listener $LSNRNAME

show trc_directory

EOF`

    echo "Listener Log File: $LSNRLOG"

  # If 10g or lower, do not use diagnostic dest.

  else

    # Get the listener log file location.

    LSNRLOG=`$LSNRCTL status $LSNRNAME | grep "Listener Log File" | awk {print $4}`

  fi

  # See if the listener is logging.

  if [ -z "$LSNRLOG" ]; then

    echo "Listener Logging is OFF. Not rotating the listener log."

  # See if the listener log exists.

  elif  [ ! -r "$LSNRLOG" ]; then

    echo "Listener Log Does Not Exist: $LSNRLOG"

  # See if the listener log has been cut today.

  elif [ -f $LSNRLOG.$TODAY ]; then

    echo "Listener Log Already Cut Today: $LSNRLOG.$TODAY"

  # Cut the listener log if the previous two conditions were not met.

  else

    # Remove old 11g+ listener log XML files.

    if [ ! -z "$LSNRDIAG" ] && [ -d "$LSNRDIAG" ]; then

      echo "Cleaning Listener Diagnostic Dest: $LSNRDIAG"

      find $LSNRDIAG -type f -name "log\_[0-9]*.xml" -mtime +$NDAYS -exec $RM {} \; 2>/dev/null

    fi



    # Disable logging.

    $LSNRCTL <<EOF

set current_listener $LSNRNAME

set log_status off

EOF

    # Cut the listener log file.

    f_cutlog $LSNRLOG

    # Enable logging.

    $LSNRCTL <<EOF

set current_listener $LSNRNAME

set log_status on

EOF

    # Delete old listener logs.

    f_deletelog $LSNRLOG $NDAYS

  fi

done

echo "`basename $0` Finished `date`."

exit
View Code

 

五、sqlplus连接数据库实例方式,其它类似

5.1) sqlplus / as sysdba
    操作系统认证,不需要数据库服务器启动listener,也不需要数据库服务器处于可用状态。比如我们想要启动数据库就可以用这种方式进入
    sqlplus,然后通过startup命令来启动。
5.2) sqlplus username/password
    连接本机数据库,不需要数据库服务器的listener进程,但是由于需要用户名密码的认证,因此需要数据库服务器处于可用状态才行。
5.3) sqlplus usernaem/password@orcl
    通过网络连接,这是需要数据库服务器的listener处于监听状态。此时建立一个连接的大致步骤如下 
  a. 查询sqlnet.ora,看看名称的解析方式,默认是TNSNAME  
  b. 查询tnsnames.ora文件,从里边找orcl的记录,并且找到数据库服务器的主机名或者IP,端口和service_name  
  c. 如果服务器listener进程没有问题的话,建立与listener进程的连接。  
  d. 根据不同的服务器模式如专用服务器模式或者共享服务器模式,listener采取接下去的动作。默认是专用服务器模式,没有问题的话客户端
            就连接上了数据库的server process。
  e. 这时连接已经建立,可以操作数据库了。
5.4)sqlplus username/password@//host:port/sid
  用sqlplus远程连接oracle命令(例:sqlplus risenet/1@//192.168.130.99:1521/risenet)

 

Oracle Listener

标签:程序   getopts   数据库服务器   认证   try   created   prot   values   bash   

原文地址:https://www.cnblogs.com/wandering-mind/p/8933352.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!