源战役
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

501 lines
16 KiB

1 개월 전
  1. #!/bin/bash
  2. # 服务器管理控制脚本
  3. # author huangyongxing@yeah.net
  4. # 2016-11-24
  5. # 获取脚本所在目录的父目录(即server目录)的全路径
  6. BASE_DIR=$(dirname $(cd $(dirname $0); pwd))
  7. # 以下参数,由脚本命令行c参数 指定外部配置文件来设定
  8. # 配置文件示例:
  9. # ===================================================
  10. # 游戏运行参数配置
  11. # SCREEN_RUN=TRUE
  12. #
  13. # IS_CROSS_CENTER=FALSE
  14. # PROCESS_MAKE_LIMIT=2
  15. #
  16. # HOST=127.0.0.1
  17. # PORT=9310
  18. # SERVER_ID=1
  19. # NODE_NAME_PREFIX=jh_develop_
  20. # ERL_COOKIE_PREFIX=jianghu_
  21. #
  22. # ---------------------------
  23. # 以下为可选项
  24. # ---------------------------
  25. #
  26. # 默认节点Cookie分跨服和游戏服(如果已指定则不按以下规则生成):
  27. # ERL_COOKIE=${ERL_COOKIE_PREFIX}center
  28. # ERL_COOKIE=${ERL_COOKIE_PREFIX}${SERVER_ID}
  29. #
  30. # APP_CFG=gsrv
  31. #
  32. # ERL=/usr/local/bin/erl
  33. #
  34. # ===================================================
  35. # ===================================================
  36. # 其他说明
  37. # ===================================================
  38. # 默认节点名分跨服和游戏服(不能直接指定节点名):
  39. # ${NODE_NAME_PREFIX}center@${HOST}
  40. # ${NODE_NAME_PREFIX}${SERVER_ID}@${HOST}
  41. #
  42. # PS:节点间通讯端口由gsrv.config/cls.config中配置,不在启动参数中控制
  43. # 注意,下文中使用eval "${ERL}",目的是配合测试服使用,
  44. # 可以通过修改ERL变量实现系统时间的单独设置,这些情况需要先对ERL展开,
  45. # 所以需要在运行前执行eval将字符串转换为相关命令(${ERL}需要带引号才兼容各类情况)
  46. # ===================================================
  47. # 读取脚本参数
  48. while getopts 'c:' OPT; do
  49. case $OPT in
  50. c) CONF_FILE="$OPTARG";;
  51. ?)
  52. fun_usage
  53. exit 1;;
  54. esac
  55. done
  56. shift $(($OPTIND - 1))
  57. if [ -z "$CONF_FILE" ] ; then
  58. CONF_FILE="${BASE_DIR}/script/ctl.conf"
  59. fi
  60. # 如果存在外部配置文件,以上配置以外部配置为准
  61. if [[ "$CONF_FILE" =~ ^/.* ]] ; then
  62. # 绝对路径
  63. if [ -f "$CONF_FILE" ] ; then
  64. source "$CONF_FILE"
  65. fi
  66. else
  67. # 非绝对路径
  68. # 尝试在当前执行路径 及 script目录下查找文件
  69. if [ -f "$CONF_FILE" ] ; then
  70. # 使用./指明路径,避免出现 file not found 问题
  71. source "./$CONF_FILE"
  72. elif [ -f "$BASE_DIR/script/$CONF_FILE" ] ; then
  73. source "$BASE_DIR/script/$CONF_FILE"
  74. fi
  75. fi
  76. # 默认的游戏节点config,游戏服为gsrv.config,跨服为cls.config
  77. if [ -z "${APP_CFG}" ] ; then
  78. if [ "${IS_CROSS_CENTER}" = "TRUE" ] ; then
  79. APP_CFG=cls
  80. else
  81. APP_CFG=gsrv
  82. fi
  83. fi
  84. if [ -z "$ERL_COOKIE" ] ; then
  85. if [ "$IS_CROSS_CENTER" = "TRUE" ] ; then
  86. ERL_COOKIE=${ERL_COOKIE_PREFIX}center
  87. else
  88. ERL_COOKIE=${ERL_COOKIE_PREFIX}${SERVER_ID}
  89. fi
  90. fi
  91. # ===================================================
  92. # 必须/重要的基础默认参数设置
  93. [ -z "$HOST" ] && HOST=127.0.0.1
  94. [ -z "$ERL" ] && ERL="$(which erl 2>/dev/null || echo -n /usr/local/bin/erl)"
  95. [ -z "$SCREEN_RUN" ] && SCREEN_RUN=TRUE
  96. [ -z "$IS_CROSS_CENTER" ] && IS_CROSS_CENTER=FALSE
  97. [ -z "$PROCESS_MAKE_LIMIT" ] && PROCESS_MAKE_LIMIT=4
  98. # ===================================================
  99. # ===================================================
  100. # 参数版本差异管理
  101. ERTS_VER=$(eval "${ERL}" +V 2>&1 | grep -o "[0-9\.]\+")
  102. ERTS_VER_MAIN=${ERTS_VER/.*/}
  103. # 粗略以erts 9分界,省去查erts release版本历史
  104. if [[ "$ERTS_VER_MAIN" < "9" ]] ; then
  105. ERL_OPT_S_SINGLE="+P 204800 +K true +A 100 +spp true -smp disable -hidden +sbwt none"
  106. ERL_OPT_NORMAL="+P 204800 +K true +A 100 +spp true -smp enable -hidden +sbwt none"
  107. else
  108. ERL_OPT_S_SINGLE="+P 204800 +K true +A 100 +spp true +S 1 -hidden +sbwt none"
  109. ERL_OPT_NORMAL="+P 204800 +K true +A 100 +spp true +S 0 -hidden +sbwt none"
  110. fi
  111. # ===================================================
  112. function fun_usage()
  113. {
  114. script_name=`basename $0`
  115. cat << EOF
  116. usage:
  117. 启动/关闭/重启服务器
  118. sh $script_name [-c ConfigFile] <start | stop | restart>
  119. 完整编译代码(erlang工程目录完整编译)
  120. sh $script_name [-c ConfigFile] make [ MakeOptions ]
  121. MakeOption以逗号分隔,例如: "{d,'LANG_VER',\"taiwan\"},load"
  122. 快速编译代码(以ebin目录更新时间来筛选编译内容,如果后台单更配置会影响准确性)
  123. sh $script_name [-c ConfigFile] fast_make [ MakeOptions ]
  124. MakeOption以逗号分隔,例如: "{d,'LANG_VER',\"taiwan\"},load"
  125. 热更服务器
  126. sh $script_name [-c ConfigFile] hot
  127. 执行erl cmd
  128. sh $script_name [-c ConfigFile] do <LINE_NUM | all> <ERL_CMD>
  129. 备注:-c ConfigFile 指定配置文件,便于开发版本在同一目录下运行跨服
  130. EOF
  131. }
  132. function fun_get_node_name()
  133. {
  134. if [ "$IS_CROSS_CENTER" = "TRUE" ] ; then
  135. echo ${NODE_NAME_PREFIX}center@${HOST}
  136. else
  137. echo ${NODE_NAME_PREFIX}${SERVER_ID}@${HOST}
  138. fi
  139. }
  140. # 这种只适应于LINUX系统,WINDOWS下的mingw环境中无法取得对应的cookie
  141. function fun_get_cookie_by_node_name()
  142. {
  143. ps -ef | grep "beam" | grep "\-name $1" | sed -n 's/.*setcookie \([^ ]*\) .*/\1/p'
  144. }
  145. # LINUX系统使用
  146. # fun_kill_node
  147. function fun_kill_node()
  148. {
  149. local pids=""
  150. if [ -z "$1" ] ; then
  151. # 什么都没指定
  152. pids=$(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${ERL_COOKIE}\b" | awk '{printf $2" ";}')
  153. elif [ -z "$2" ] ; then
  154. # 只指定了节点名
  155. pids=$(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${ERL_COOKIE}\b" | grep "\b$1\b" | awk '{printf $2" ";}')
  156. else
  157. # 指定了节点名和cookie
  158. pids=$(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${2}\b" | grep "\b$1\b" | awk '{printf $2" ";}')
  159. fi
  160. if [ -n "$pids" ] ; then
  161. kill -9 "$pids"
  162. fi
  163. }
  164. function fun_create_script()
  165. {
  166. local node_name script_file port erl_option
  167. node_name=$1
  168. script_file=${BASE_DIR}/script/game_server.sh
  169. if [ "$IS_CROSS_CENTER" = "TRUE" ] ; then
  170. script_file=${BASE_DIR}/script/game_center.sh
  171. fi
  172. port=$PORT
  173. erl_option="${ERL_OPT_NORMAL}"
  174. if [ "$SCREEN_RUN" != "TRUE" ] ; then
  175. # local os=$(uname)
  176. # if [[ ! "${os}" =~ ^.*NT.*$ ]] ; then
  177. # erl_option="-noinput ${ERL_OPT_NORMAL}"
  178. # fi
  179. # 非screen运行,统一后台运行,需要加-noinput之类后台运行参数
  180. erl_option="-noinput ${ERL_OPT_NORMAL}"
  181. fi
  182. cat > $script_file <<EOF
  183. #!/bin/bash
  184. cd ${BASE_DIR}/config
  185. ulimit -HSn 204800
  186. ulimit -c 409600
  187. # ulimit -c unlimited
  188. eval "${ERL}" ${erl_option} -name ${node_name} -setcookie ${ERL_COOKIE} -boot start_sasl -config ${APP_CFG} -pa ../ebin -s main start -extra ${HOST} ${port}
  189. EOF
  190. echo $script_file
  191. }
  192. fun_start_node()
  193. {
  194. local node_name=$1
  195. script_file=$(fun_create_script $node_name)
  196. screen_name=${node_name%@*}
  197. fun_run_script $script_file $screen_name
  198. }
  199. function fun_start()
  200. {
  201. node_name=$(fun_get_node_name)
  202. fun_start_node ${node_name}
  203. }
  204. # fun_run_script script_file screen_name
  205. function fun_run_script()
  206. {
  207. chmod +x $1
  208. local logfile=${BASE_DIR}/logs/${screen_name}_`date +"%Y%m%d_%H%M%S.txt"`
  209. if [ "${SCREEN_RUN}" == "TRUE" ] ; then
  210. local screenrcfile="${BASE_DIR}/script/screenrc"
  211. cat > "${screenrcfile}" <<EOF
  212. multiuser on
  213. acladd root
  214. defencoding utf8
  215. deflog on
  216. # defwritelock on
  217. logfile $logfile
  218. EOF
  219. # 以detached模式启动,自动记录日志
  220. screen -U -dmS $2 -c "${screenrcfile}" -s $1
  221. else
  222. # local os=$(uname)
  223. # if [[ "${os}" =~ ^.*NT.*$ ]] ; then
  224. # start sh $1
  225. # else
  226. # sh $1 >$logfile 2>&1 &
  227. # fi
  228. sh $1 >$logfile 2>&1 &
  229. fi
  230. }
  231. function fun_restart()
  232. {
  233. fun_stop "$@"
  234. fun_start "$@"
  235. }
  236. # LINUX系统使用
  237. function fun_get_alive_num()
  238. {
  239. if [ -z "$1" ] ; then
  240. echo $(ps -ef | grep beam | grep -v "\bgrep\b" | grep -c "\b${ERL_COOKIE}\b")
  241. else
  242. line_num=$1
  243. node_name=$(fun_get_node_name ${line_num})
  244. echo $(ps -ef | grep beam | grep -v "\bgrep\b" | grep "\b${ERL_COOKIE}\b" | grep -c "\b${node_name}\b")
  245. fi
  246. }
  247. function fun_stop()
  248. {
  249. cd ${BASE_DIR}/ebin
  250. local cookie node_name stop_node_name
  251. if [ -z "$1" ] ; then
  252. node_name=$(fun_get_node_name)
  253. cookie=${ERL_COOKIE}
  254. else # 指定了节点
  255. node_name=$1
  256. cookie=$(fun_get_cookie_by_node_name "${node_name}")
  257. fi
  258. stop_node_name=${node_name/@/_stop_${RANDOM}@}
  259. eval "${ERL}" -noshell +S 1 -hidden -name ${stop_node_name} -setcookie ${cookie} -pa ../ebin -s main stop_server -extra ${node_name}
  260. fun_kill_node "${node_name}" "${cookie}"
  261. }
  262. # 判断编译版本(非语言版本)
  263. # 主要指DEBUG、DEBUG_CMD、DEBUG_SQL、RELEASE,影响日志打印
  264. # 参数用DEV_SERVER代替DEBUG的原因是写日志的宏名叫DEBUG,为使定义清晰,区别定义
  265. function judge_make_ver() {
  266. local params="$@"
  267. if [ -z "$params" ] ; then
  268. echo -n "RELEASE"
  269. return 0
  270. fi
  271. case "$params" in
  272. *"'DEV_SERVER'"*)
  273. echo -n "DEBUG";;
  274. *"'DEBUG_CMD'"*)
  275. echo -n "DEBUG_CMD";;
  276. *"'DEBUG_SQL'"*)
  277. echo -n "DEBUG_SQL";;
  278. *)
  279. echo -n "RELEASE";;
  280. esac
  281. }
  282. # 拷贝依赖项
  283. function fun_cp_dep() {
  284. cd ${BASE_DIR}
  285. local makeVer needCopy=FALSE
  286. if [ -d "dep/ebin" ] ; then
  287. # 奇怪的现象,在web中调用的时候,$PATH没问题,
  288. # 但是$(which rsync)子进程中,$PATH环境变量丢失,变成null了
  289. # 为什么date、find之类的命令不会有问题?
  290. export PATH=$PATH
  291. local rsync=$(which rsync)
  292. makeVer=$(judge_make_ver "$@")
  293. if [ -e "ebin/game_logger.beam" ] ; then
  294. needCopy=TRUE
  295. else
  296. cmp -s "dep/ebin/${makeVer,,}/game_logger.beam" "ebin/game_logger.beam"
  297. [ "$?" != "0" ] && needCopy=TRUE
  298. fi
  299. if [ -z "$rsync" ] ; then
  300. \cp -a -u -t ebin dep/ebin/*.beam
  301. else
  302. "$rsync" -avhc --partial --include=*.beam --exclude=* dep/ebin/ ebin/
  303. fi
  304. # 版本如有差异则进行复制
  305. [ "$needCopy" == "TRUE" ] && \cp -a "dep/ebin/${makeVer,,}/game_logger.beam" "ebin/"
  306. fi
  307. }
  308. # 如无参数,则按默认规则编译,无指定特殊参数,即makeOptions为[]
  309. # 需要指定编译参数时,示例:
  310. # fun_make "{d,'DEBUG'}", 即 [{d, 'DEBUG'}]
  311. function fun_make()
  312. {
  313. cd ${BASE_DIR}
  314. local make_options="[$1]"
  315. local startTime endTime
  316. startTime=$(date +%s)
  317. echo "开始于 "$(date +"%Y-%m-%d %H:%M:%S" -d @${startTime})
  318. eval "${ERL}" ${ERL_OPT_NORMAL} -noinput -pa ./ebin -eval '"mmake:all(${PROCESS_MAKE_LIMIT}, ${make_options}), erlang:halt()"'
  319. endTime=$(date +%s)
  320. echo "结束于 "$(date +"%Y-%m-%d %H:%M:%S" -d @${endTime})
  321. echo "时间消耗 $((${endTime} - ${startTime})) seconds"
  322. }
  323. function fun_fast_make()
  324. {
  325. cd ${BASE_DIR}
  326. local startTime=$(date +%s)
  327. local startTimeStr=$(date +"%Y-%m-%d %H:%M:%S" -d @${startTime})
  328. echo "开始于 ${startTimeStr}"
  329. local make_options="[$1]"
  330. local makeTimeFile="${BASE_DIR}/script/.maketime"
  331. local newerThenFile="${BASE_DIR}/ebin"
  332. local lastMakeTime=""
  333. if [ -e "${makeTimeFile}" ] ; then
  334. lastMakeTime="$(cat ${makeTimeFile})"
  335. if [[ ! "${lastMakeTime}" =~ ^[0-9]{4,4}(-[0-9]{1,2}){1,2}\ [0-9]{1,2}(:[0-9]{1,2}){1,2}$ ]] ; then
  336. lastMakeTime=""
  337. fi
  338. fi
  339. if [ "${lastMakeTime}" = "" ] ; then
  340. # 以ebin目录的最后修改时间作为上次编译时间
  341. lastMakeTime=$(stat --format="%y" "${newerThenFile}")
  342. lastMakeTime=${lastMakeTime%%.*}
  343. fi
  344. local modules=$(fun_get_update_modules "${lastMakeTime}")
  345. if [ "$modules" != "" ] ; then
  346. 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()"'
  347. else
  348. echo "找不到需要编译的模块"
  349. fi
  350. local endTime=$(date +%s)
  351. echo "结束于 "$(date +"%Y-%m-%d %H:%M:%S" -d @${endTime})
  352. echo "时间消耗 $((${endTime} - ${startTime})) seconds"
  353. }
  354. # join数组,参数1为分隔符,参数2为数组
  355. # 示例: join_array "," ${arr[@]}
  356. function join_array() {
  357. local sep=$1 ret=$2 IFS=
  358. shift 2 || shift $#
  359. echo "${ret}${*/#/$sep}"
  360. }
  361. # 获取可能的更新模块
  362. function fun_get_update_modules()
  363. {
  364. cd ${BASE_DIR}
  365. local lastMakeTime="$1"
  366. declare -A modules=()
  367. declare -A hrls=()
  368. local file ext
  369. # 获取erl和hrl更新文件,并归类记录
  370. #(默认路径中不存在空格,如果存在空格,使用for读取会有问题)
  371. # find -newer "ebin" -name "*.*rl"
  372. for file in $(find -newermt "${lastMakeTime}" -name "*.*rl") ; do
  373. ext=${file##*.}
  374. if [ "${ext}" = "erl" ] ; then
  375. modules[${file/.\//}]=1
  376. elif [ "${ext}" = "hrl" ] ; then
  377. hrls[${file##*/}]=1
  378. fi
  379. done
  380. # 查找受到hrl影响的erl文件,并追加记录到更新模块文件列表
  381. local hrlsRegex=$(join_array '\|' "${!hrls[@]}")
  382. if [ "${hrlsRegex}" != "" ] ; then
  383. for file in $(grep -rl "^\s*\-\s*include\s*(\s*\"\(${hrlsRegex}\)\"\s*)" * --include=*.erl) ; do
  384. modules[$file]=1
  385. done
  386. fi
  387. if [ "${#modules[@]}" -eq 0 ] ; then
  388. echo ""
  389. else
  390. echo "\""$(join_array "\",\"" "${!modules[@]}")"\""
  391. fi
  392. }
  393. function fun_hot()
  394. {
  395. cd ${BASE_DIR}
  396. local cookie node_name hot_node_name
  397. if [ -z "$1" ] ; then
  398. node_name=$(fun_get_node_name)
  399. cookie=${ERL_COOKIE}
  400. else
  401. node_name=$1
  402. cookie=$(fun_get_cookie_by_node_name "${node_name}")
  403. fi
  404. hot_node_name=${node_name/@/_hot_$(date +%s%N)@}
  405. # 添加timer:sleep/1是为了使热更结果尽可能完整打印到这里
  406. eval "${ERL}" -noinput -name ${hot_node_name} -setcookie ${cookie} -pa ../ebin -eval '"rpc:call('\''${node_name}'\'', u, u, [md5]), timer:sleep(3000), erlang:halt()"'
  407. }
  408. function fun_do()
  409. {
  410. cd ${BASE_DIR}/ebin/
  411. local node_name=$1 cookie
  412. if [[ -n "$2" && "$node_name" =~ ^[a-zA-Z0-9_]+@[0-9a-zA-Z.]+$ ]] ; then
  413. # 存在第二个参数且第一个参数是节点名形式,按指定节点执行的方式处理
  414. shift 1
  415. cookie=$(fun_get_cookie_by_node_name "${node_name}")
  416. else
  417. node_name=$(fun_get_node_name)
  418. cookie="${ERL_COOKIE}"
  419. fi
  420. local erl_cmd=$(echo "$@")
  421. if [[ ! "$erl_cmd" =~ ^[^.]*\.\ *$ ]] ; then
  422. echo -e "执行的erlang命令有误,必须是以.号结束,并且只允许有一个.号的完整erlang语句,例如 \"A=123, myfun(A).\""
  423. return 1
  424. fi
  425. local do_node_name=${node_name/@/_do_$(date +%s%N)@}
  426. RPC_CMD="R = rpc:call('${node_name}', util, eval, [\"$erl_cmd\"]), io:format(\"eval result: ~w~n\", [R])."
  427. eval "${ERL}" -noinput -name ${do_node_name} -setcookie ${cookie} -eval '"io:setopts([{encoding, unicode}]), $RPC_CMD"' -eval '"erlang:halt(0)."'
  428. }
  429. function fun_main()
  430. {
  431. local do=$1
  432. shift
  433. if [ -z "$ERL_COOKIE" -a "$do" != "make" ] ; then
  434. echo "ERL COOKIE 未设置!"
  435. return 1
  436. fi
  437. case $do in
  438. fast_make)
  439. fun_cp_dep "$@"
  440. fun_fast_make "$@"
  441. ;;
  442. make)
  443. fun_cp_dep "$@"
  444. fun_make "$@"
  445. ;;
  446. start)
  447. fun_start "$@"
  448. ;;
  449. stop)
  450. fun_stop "$@"
  451. ;;
  452. restart)
  453. fun_restart "$@"
  454. ;;
  455. hot)
  456. fun_hot "$@"
  457. ;;
  458. do)
  459. fun_do "$@"
  460. ;;
  461. *)
  462. fun_usage
  463. ;;
  464. esac
  465. }
  466. fun_main "$@"