|
|
- #!/bin/bash
- # 服务器管理控制脚本
- # author huangyongxing@yeah.net
- # 2016-11-24
-
- # 获取脚本所在目录的父目录(即server目录)的全路径
- BASE_DIR=$(dirname $(cd $(dirname $0); pwd))
-
- # 以下参数,由脚本命令行c参数 指定外部配置文件来设定
-
- # 配置文件示例:
- # ===================================================
- # 游戏运行参数配置
- # SCREEN_RUN=TRUE
- #
- # IS_CROSS_CENTER=FALSE
- # PROCESS_MAKE_LIMIT=2
- #
- # HOST=127.0.0.1
- # PORT=9310
- # SERVER_ID=1
- # NODE_NAME_PREFIX=jh_develop_
- # ERL_COOKIE_PREFIX=jianghu_
- #
- # ---------------------------
- # 以下为可选项
- # ---------------------------
- #
- # 默认节点Cookie分跨服和游戏服(如果已指定则不按以下规则生成):
- # ERL_COOKIE=${ERL_COOKIE_PREFIX}center
- # ERL_COOKIE=${ERL_COOKIE_PREFIX}${SERVER_ID}
- #
- # APP_CFG=gsrv
- #
- # ERL=/usr/local/bin/erl
- #
- # ===================================================
-
- # ===================================================
- # 其他说明
- # ===================================================
- # 默认节点名分跨服和游戏服(不能直接指定节点名):
- # ${NODE_NAME_PREFIX}center@${HOST}
- # ${NODE_NAME_PREFIX}${SERVER_ID}@${HOST}
- #
- # PS:节点间通讯端口由gsrv.config/cls.config中配置,不在启动参数中控制
- # 注意,下文中使用eval "${ERL}",目的是配合测试服使用,
- # 可以通过修改ERL变量实现系统时间的单独设置,这些情况需要先对ERL展开,
- # 所以需要在运行前执行eval将字符串转换为相关命令(${ERL}需要带引号才兼容各类情况)
-
- # ===================================================
-
- # 读取脚本参数
- while getopts 'c:' OPT; do
- case $OPT in
- c) CONF_FILE="$OPTARG";;
- ?)
- fun_usage
- exit 1;;
- esac
- done
- shift $(($OPTIND - 1))
-
- if [ -z "$CONF_FILE" ] ; then
- CONF_FILE="${BASE_DIR}/script/ctl.conf"
- fi
-
- # 如果存在外部配置文件,以上配置以外部配置为准
- if [[ "$CONF_FILE" =~ ^/.* ]] ; then
- # 绝对路径
- if [ -f "$CONF_FILE" ] ; then
- source "$CONF_FILE"
- fi
- else
- # 非绝对路径
- # 尝试在当前执行路径 及 script目录下查找文件
- if [ -f "$CONF_FILE" ] ; then
- # 使用./指明路径,避免出现 file not found 问题
- source "./$CONF_FILE"
- elif [ -f "$BASE_DIR/script/$CONF_FILE" ] ; then
- source "$BASE_DIR/script/$CONF_FILE"
- fi
- fi
-
- # 默认的游戏节点config,游戏服为gsrv.config,跨服为cls.config
- if [ -z "${APP_CFG}" ] ; then
- if [ "${IS_CROSS_CENTER}" = "TRUE" ] ; then
- APP_CFG=cls
- else
- APP_CFG=gsrv
- fi
- fi
-
- if [ -z "$ERL_COOKIE" ] ; then
- if [ "$IS_CROSS_CENTER" = "TRUE" ] ; then
- ERL_COOKIE=${ERL_COOKIE_PREFIX}center
- else
- ERL_COOKIE=${ERL_COOKIE_PREFIX}${SERVER_ID}
- fi
- fi
-
- # ===================================================
- # 必须/重要的基础默认参数设置
- [ -z "$HOST" ] && HOST=127.0.0.1
- [ -z "$ERL" ] && ERL="$(which erl 2>/dev/null || echo -n /usr/local/bin/erl)"
- [ -z "$SCREEN_RUN" ] && SCREEN_RUN=TRUE
- [ -z "$IS_CROSS_CENTER" ] && IS_CROSS_CENTER=FALSE
- [ -z "$PROCESS_MAKE_LIMIT" ] && PROCESS_MAKE_LIMIT=4
- # ===================================================
-
- # ===================================================
- # 参数版本差异管理
- ERTS_VER=$(eval "${ERL}" +V 2>&1 | grep -o "[0-9\.]\+")
- ERTS_VER_MAIN=${ERTS_VER/.*/}
-
- # 粗略以erts 9分界,省去查erts release版本历史
- if [[ "$ERTS_VER_MAIN" < "9" ]] ; then
- ERL_OPT_S_SINGLE="+P 204800 +K true +A 100 +spp true -smp disable -hidden +sbwt none"
- ERL_OPT_NORMAL="+P 204800 +K true +A 100 +spp true -smp enable -hidden +sbwt none"
- else
- ERL_OPT_S_SINGLE="+P 204800 +K true +A 100 +spp true +S 1 -hidden +sbwt none"
- ERL_OPT_NORMAL="+P 204800 +K true +A 100 +spp true +S 0 -hidden +sbwt none"
- fi
- # ===================================================
-
- function fun_usage()
- {
- script_name=`basename $0`
- cat << EOF
- usage:
- 启动/关闭/重启服务器
- sh $script_name [-c ConfigFile] <start | stop | restart>
- 完整编译代码(erlang工程目录完整编译)
- sh $script_name [-c ConfigFile] make [ MakeOptions ]
- MakeOption以逗号分隔,例如: "{d,'LANG_VER',\"taiwan\"},load"
- 快速编译代码(以ebin目录更新时间来筛选编译内容,如果后台单更配置会影响准确性)
- sh $script_name [-c ConfigFile] fast_make [ MakeOptions ]
- MakeOption以逗号分隔,例如: "{d,'LANG_VER',\"taiwan\"},load"
- 热更服务器
- sh $script_name [-c ConfigFile] hot
- 执行erl cmd
- sh $script_name [-c ConfigFile] do <LINE_NUM | all> <ERL_CMD>
- 备注:-c ConfigFile 指定配置文件,便于开发版本在同一目录下运行跨服
- EOF
- }
-
- function fun_get_node_name()
- {
- if [ "$IS_CROSS_CENTER" = "TRUE" ] ; then
- echo ${NODE_NAME_PREFIX}center@${HOST}
- else
- echo ${NODE_NAME_PREFIX}${SERVER_ID}@${HOST}
- fi
- }
-
- # 这种只适应于LINUX系统,WINDOWS下的mingw环境中无法取得对应的cookie
- function fun_get_cookie_by_node_name()
- {
- ps -ef | grep "beam" | grep "\-name $1" | sed -n 's/.*setcookie \([^ ]*\) .*/\1/p'
- }
-
- # LINUX系统使用
- # fun_kill_node
- function fun_kill_node()
- {
- local pids=""
- if [ -z "$1" ] ; then
- # 什么都没指定
- pids=$(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${ERL_COOKIE}\b" | awk '{printf $2" ";}')
- elif [ -z "$2" ] ; then
- # 只指定了节点名
- pids=$(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${ERL_COOKIE}\b" | grep "\b$1\b" | awk '{printf $2" ";}')
- else
- # 指定了节点名和cookie
- pids=$(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${2}\b" | grep "\b$1\b" | awk '{printf $2" ";}')
- fi
- if [ -n "$pids" ] ; then
- kill -9 "$pids"
- fi
- }
-
- function fun_create_script()
- {
- local node_name script_file port erl_option
- node_name=$1
- script_file=${BASE_DIR}/script/game_server.sh
- if [ "$IS_CROSS_CENTER" = "TRUE" ] ; then
- script_file=${BASE_DIR}/script/game_center.sh
- fi
- port=$PORT
- erl_option="${ERL_OPT_NORMAL}"
- if [ "$SCREEN_RUN" != "TRUE" ] ; then
- # local os=$(uname)
- # if [[ ! "${os}" =~ ^.*NT.*$ ]] ; then
- # erl_option="-noinput ${ERL_OPT_NORMAL}"
- # fi
- # 非screen运行,统一后台运行,需要加-noinput之类后台运行参数
- erl_option="-noinput ${ERL_OPT_NORMAL}"
- fi
- cat > $script_file <<EOF
- #!/bin/bash
- cd ${BASE_DIR}/config
- ulimit -HSn 204800
- ulimit -c 409600
- # ulimit -c unlimited
- eval "${ERL}" ${erl_option} -name ${node_name} -setcookie ${ERL_COOKIE} -boot start_sasl -config ${APP_CFG} -pa ../ebin -s main start -extra ${HOST} ${port}
- EOF
- echo $script_file
- }
-
- fun_start_node()
- {
- local node_name=$1
- script_file=$(fun_create_script $node_name)
- screen_name=${node_name%@*}
- fun_run_script $script_file $screen_name
- }
-
- function fun_start()
- {
- node_name=$(fun_get_node_name)
- fun_start_node ${node_name}
- }
-
- # fun_run_script script_file screen_name
- function fun_run_script()
- {
- chmod +x $1
- local logfile=${BASE_DIR}/logs/${screen_name}_`date +"%Y%m%d_%H%M%S.txt"`
- if [ "${SCREEN_RUN}" == "TRUE" ] ; then
- local screenrcfile="${BASE_DIR}/script/screenrc"
- cat > "${screenrcfile}" <<EOF
- multiuser on
- acladd root
- defencoding utf8
- deflog on
- # defwritelock on
- logfile $logfile
- EOF
- # 以detached模式启动,自动记录日志
- screen -U -dmS $2 -c "${screenrcfile}" -s $1
- else
- # local os=$(uname)
- # if [[ "${os}" =~ ^.*NT.*$ ]] ; then
- # start sh $1
- # else
- # sh $1 >$logfile 2>&1 &
- # fi
- sh $1 >$logfile 2>&1 &
- fi
- }
-
- function fun_restart()
- {
- fun_stop "$@"
- fun_start "$@"
- }
-
- # LINUX系统使用
- function fun_get_alive_num()
- {
- if [ -z "$1" ] ; then
- echo $(ps -ef | grep beam | grep -v "\bgrep\b" | grep -c "\b${ERL_COOKIE}\b")
- else
- line_num=$1
- node_name=$(fun_get_node_name ${line_num})
- echo $(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${ERL_COOKIE}\b" | grep -c "\b${node_name}\b")
- fi
- }
-
- function fun_stop()
- {
- cd ${BASE_DIR}/ebin
- local cookie node_name stop_node_name
- if [ -z "$1" ] ; then
- node_name=$(fun_get_node_name)
- cookie=${ERL_COOKIE}
- else # 指定了节点
- node_name=$1
- cookie=$(fun_get_cookie_by_node_name "${node_name}")
- fi
- stop_node_name=${node_name/@/_stop_${RANDOM}@}
- eval "${ERL}" -noshell +S 1 -hidden -name ${stop_node_name} -setcookie ${cookie} -pa ../ebin -s main stop_server -extra ${node_name}
- fun_kill_node "${node_name}" "${cookie}"
- }
-
- # 判断编译版本(非语言版本)
- # 主要指DEBUG、DEBUG_CMD、DEBUG_SQL、RELEASE,影响日志打印
- # 参数用DEV_SERVER代替DEBUG的原因是写日志的宏名叫DEBUG,为使定义清晰,区别定义
- function judge_make_ver() {
- local params="$@"
- if [ -z "$params" ] ; then
- echo -n "RELEASE"
- return 0
- fi
- case "$params" in
- *"'DEV_SERVER'"*)
- echo -n "DEBUG";;
- *"'DEBUG_CMD'"*)
- echo -n "DEBUG_CMD";;
- *"'DEBUG_SQL'"*)
- echo -n "DEBUG_SQL";;
- *)
- echo -n "RELEASE";;
- esac
- }
-
- # 拷贝依赖项
- function fun_cp_dep() {
- cd ${BASE_DIR}
- local makeVer needCopy=FALSE
- if [ -d "dep/ebin" ] ; then
- # 奇怪的现象,在web中调用的时候,$PATH没问题,
- # 但是$(which rsync)子进程中,$PATH环境变量丢失,变成null了
- # 为什么date、find之类的命令不会有问题?
- export PATH=$PATH
- local rsync=$(which rsync)
- makeVer=$(judge_make_ver "$@")
- if [ -e "ebin/game_logger.beam" ] ; then
- needCopy=TRUE
- else
- cmp -s "dep/ebin/${makeVer,,}/game_logger.beam" "ebin/game_logger.beam"
- [ "$?" != "0" ] && needCopy=TRUE
- fi
- if [ -z "$rsync" ] ; then
- \cp -a -u -t ebin dep/ebin/*.beam
- else
- "$rsync" -avhc --partial --include=*.beam --exclude=* dep/ebin/ ebin/
- fi
- # 版本如有差异则进行复制
- [ "$needCopy" == "TRUE" ] && \cp -a "dep/ebin/${makeVer,,}/game_logger.beam" "ebin/"
- fi
- }
-
- # 如无参数,则按默认规则编译,无指定特殊参数,即makeOptions为[]
- # 需要指定编译参数时,示例:
- # fun_make "{d,'DEBUG'}", 即 [{d, 'DEBUG'}]
- function fun_make()
- {
- cd ${BASE_DIR}
- local make_options="[$1]"
- local startTime endTime
- startTime=$(date +%s)
- echo "开始于 "$(date +"%Y-%m-%d %H:%M:%S" -d @${startTime})
- eval "${ERL}" ${ERL_OPT_NORMAL} -noinput -pa ./ebin -eval '"mmake:all(${PROCESS_MAKE_LIMIT}, ${make_options}), erlang:halt()"'
- endTime=$(date +%s)
- echo "结束于 "$(date +"%Y-%m-%d %H:%M:%S" -d @${endTime})
- echo "时间消耗 $((${endTime} - ${startTime})) seconds"
- }
-
- function fun_fast_make()
- {
- cd ${BASE_DIR}
- local startTime=$(date +%s)
- local startTimeStr=$(date +"%Y-%m-%d %H:%M:%S" -d @${startTime})
- echo "开始于 ${startTimeStr}"
- local make_options="[$1]"
- local makeTimeFile="${BASE_DIR}/script/.maketime"
- local newerThenFile="${BASE_DIR}/ebin"
- local lastMakeTime=""
- if [ -e "${makeTimeFile}" ] ; then
- lastMakeTime="$(cat ${makeTimeFile})"
- if [[ ! "${lastMakeTime}" =~ ^[0-9]{4,4}(-[0-9]{1,2}){1,2}\ [0-9]{1,2}(:[0-9]{1,2}){1,2}$ ]] ; then
- lastMakeTime=""
- fi
- fi
- if [ "${lastMakeTime}" = "" ] ; then
- # 以ebin目录的最后修改时间作为上次编译时间
- lastMakeTime=$(stat --format="%y" "${newerThenFile}")
- lastMakeTime=${lastMakeTime%%.*}
- fi
- local modules=$(fun_get_update_modules "${lastMakeTime}")
- if [ "$modules" != "" ] ; then
- eval "${ERL}" ${ERL_OPT_NORMAL} -noinput -pa ./ebin -eval '"case mmake:files(${PROCESS_MAKE_LIMIT}, [${modules}], ${make_options}) of up_to_date -> file:write_file(\"${makeTimeFile}\", unicode:characters_to_binary(\"${startTimeStr}\")); _Error -> ignore end, erlang:halt()"'
- else
- echo "找不到需要编译的模块"
- fi
- local endTime=$(date +%s)
- echo "结束于 "$(date +"%Y-%m-%d %H:%M:%S" -d @${endTime})
- echo "时间消耗 $((${endTime} - ${startTime})) seconds"
- }
-
- # join数组,参数1为分隔符,参数2为数组
- # 示例: join_array "," ${arr[@]}
- function join_array() {
- local sep=$1 ret=$2 IFS=
- shift 2 || shift $#
- echo "${ret}${*/#/$sep}"
- }
-
- # 获取可能的更新模块
- function fun_get_update_modules()
- {
- cd ${BASE_DIR}
- local lastMakeTime="$1"
- declare -A modules=()
- declare -A hrls=()
- local file ext
- # 获取erl和hrl更新文件,并归类记录
- #(默认路径中不存在空格,如果存在空格,使用for读取会有问题)
- # find -newer "ebin" -name "*.*rl"
- for file in $(find -newermt "${lastMakeTime}" -name "*.*rl") ; do
- ext=${file##*.}
- if [ "${ext}" = "erl" ] ; then
- modules[${file/.\//}]=1
- elif [ "${ext}" = "hrl" ] ; then
- hrls[${file##*/}]=1
- fi
- done
- # 查找受到hrl影响的erl文件,并追加记录到更新模块文件列表
- local hrlsRegex=$(join_array '\|' "${!hrls[@]}")
- if [ "${hrlsRegex}" != "" ] ; then
- for file in $(grep -rl "^\s*\-\s*include\s*(\s*\"\(${hrlsRegex}\)\"\s*)" * --include=*.erl) ; do
- modules[$file]=1
- done
- fi
- if [ "${#modules[@]}" -eq 0 ] ; then
- echo ""
- else
- echo "\""$(join_array "\",\"" "${!modules[@]}")"\""
- fi
- }
-
- function fun_hot()
- {
- cd ${BASE_DIR}
- local cookie node_name hot_node_name
- if [ -z "$1" ] ; then
- node_name=$(fun_get_node_name)
- cookie=${ERL_COOKIE}
- else
- node_name=$1
- cookie=$(fun_get_cookie_by_node_name "${node_name}")
- fi
- hot_node_name=${node_name/@/_hot_$(date +%s%N)@}
- # 添加timer:sleep/1是为了使热更结果尽可能完整打印到这里
- eval "${ERL}" -noinput -name ${hot_node_name} -setcookie ${cookie} -pa ../ebin -eval '"rpc:call('\''${node_name}'\'', u, u, [md5]), timer:sleep(3000), erlang:halt()"'
- }
-
- function fun_do()
- {
- cd ${BASE_DIR}/ebin/
- local node_name=$1 cookie
- if [[ -n "$2" && "$node_name" =~ ^[a-zA-Z0-9_]+@[0-9a-zA-Z.]+$ ]] ; then
- # 存在第二个参数且第一个参数是节点名形式,按指定节点执行的方式处理
- shift 1
- cookie=$(fun_get_cookie_by_node_name "${node_name}")
- else
- node_name=$(fun_get_node_name)
- cookie="${ERL_COOKIE}"
- fi
- local erl_cmd=$(echo "$@")
- if [[ ! "$erl_cmd" =~ ^[^.]*\.\ *$ ]] ; then
- echo -e "执行的erlang命令有误,必须是以.号结束,并且只允许有一个.号的完整erlang语句,例如 \"A=123, myfun(A).\""
- return 1
- fi
- local do_node_name=${node_name/@/_do_$(date +%s%N)@}
- RPC_CMD="R = rpc:call('${node_name}', util, eval, [\"$erl_cmd\"]), io:format(\"eval result: ~w~n\", [R])."
- eval "${ERL}" -noinput -name ${do_node_name} -setcookie ${cookie} -eval '"io:setopts([{encoding, unicode}]), $RPC_CMD"' -eval '"erlang:halt(0)."'
- }
-
- function fun_main()
- {
- local do=$1
- shift
- if [ -z "$ERL_COOKIE" -a "$do" != "make" ] ; then
- echo "ERL COOKIE 未设置!"
- return 1
- fi
- case $do in
- fast_make)
- fun_cp_dep "$@"
- fun_fast_make "$@"
- ;;
- make)
- fun_cp_dep "$@"
- fun_make "$@"
- ;;
- start)
- fun_start "$@"
- ;;
- stop)
- fun_stop "$@"
- ;;
- restart)
- fun_restart "$@"
- ;;
- hot)
- fun_hot "$@"
- ;;
- do)
- fun_do "$@"
- ;;
- *)
- fun_usage
- ;;
- esac
- }
-
- fun_main "$@"
-
|