#!/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 "$@"
|
|
|