源战役
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

501 lines
16 KiB

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