#! /usr/bin/env ruby ## $Id$ ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch) ## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org) ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by ## the Free Software Foundation; either version 2 of the License, or ## (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA TOP_DIR = File.expand_path(File.dirname(__FILE__)) $:.unshift File.dirname(__FILE__) require 'shogi_server' ################################################# # MAIN # ShogiServer.reload def gets_safe(socket, timeout=nil) if r = select([socket], nil, nil, timeout) return r[0].first.gets else return :timeout end rescue Exception => ex log_error("gets_safe: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}") return :exception end def usage print < sec # timeout log_error("mutex watchdog timeout: %d sec" % [sec]) queue.clear end end sleep(1) end end def login_loop(client) player = login = nil while r = select([client], nil, nil, ShogiServer::Login_Time) do break unless str = r[0].first.gets $mutex.lock # guards LEAGUE begin str =~ /([\r\n]*)$/ eol = $1 if (ShogiServer::Login::good_login?(str)) player = ShogiServer::Player::new(str, client, eol) login = ShogiServer::Login::factory(str, player) if (current_player = LEAGUE.find(player.name)) if (current_player.password == player.password && current_player.status != "game") log_message(sprintf("user %s login forcely", player.name)) current_player.kill else login.incorrect_duplicated_player(str) player = nil break end end LEAGUE.add(player) break else client.write("LOGIN:incorrect" + eol) client.write("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4) end ensure $mutex.unlock end end # login loop return [player, login] end def setup_logger(log_file) logger = Logger.new(log_file, 'daily') logger.formatter = ShogiServer::Formatter.new logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO logger.datetime_format = "%Y-%m-%d %H:%M:%S" return logger end def setup_watchdog_for_giant_lock $mutex = Mutex::new Thread::start do Thread.pass mutex_watchdog($mutex, 10) end end def setup_floodgate return Thread.start do Thread.pass floodgate = ShogiServer::League::Floodgate.new(LEAGUE) log_message("Flooddgate reloaded. The next match will start at %s." % [floodgate.next_time]) while (true) begin diff = floodgate.next_time - Time.now if diff > 0 sleep(diff/2) next end LEAGUE.reload floodgate.match_game floodgate.charge next_time = floodgate.next_time $mutex.synchronize do log_message("Reloading source...") ShogiServer.reload end floodgate = ShogiServer::League::Floodgate.new(LEAGUE, next_time) log_message("Floodgate will start the next match at %s." % [floodgate.next_time]) rescue Exception => ex # ignore errors log_error("[in Floodgate's thread] #{ex} #{ex.backtrace}") end end end end def main $options = parse_command_line if (ARGV.length != 2) usage exit 2 end if $options["player-log-dir"] $options["player-log-dir"] = File.expand_path($options["player-log-dir"]) end if $options["player-log-dir"] && !File.directory?($options["player-log-dir"]) usage exit 3 end if $options["pid-file"] $options["pid-file"] = File.expand_path($options["pid-file"]) end LEAGUE.event = ARGV.shift port = ARGV.shift dir = $options["daemon"] dir = File.expand_path(dir) if dir if dir && ! File.exist?(dir) FileUtils.mkdir(dir) end log_file = dir ? File.join(dir, "shogi-server.log") : STDOUT $logger = setup_logger(log_file) LEAGUE.dir = dir || TOP_DIR config = {} config[:Port] = port config[:ServerType] = WEBrick::Daemon if $options["daemon"] config[:Logger] = $logger fg_thread = nil config[:StartCallback] = Proc.new do srand if $options["pid-file"] write_pid_file($options["pid-file"]) end setup_watchdog_for_giant_lock LEAGUE.setup_players_database fg_thread = setup_floodgate end config[:StopCallback] = Proc.new do if $options["pid-file"] FileUtils.rm($options["pid-file"], :force => true) end end server = WEBrick::GenericServer.new(config) ["INT", "TERM"].each do |signal| trap(signal) do server.shutdown fg_thread.kill if fg_thread end end trap("HUP") do Dependencies.clear end $stderr.puts("server started as a deamon [Revision: #{ShogiServer::Revision}]") if $options["daemon"] log_message("server started [Revision: #{ShogiServer::Revision}]") server.start do |client| # client.sync = true # this is already set in WEBrick client.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true) # Keepalive time can be set by /proc/sys/net/ipv4/tcp_keepalive_time player, login = login_loop(client) # loop next unless player log_message(sprintf("user %s login", player.name)) login.process player.setup_logger($options["player-log-dir"]) if $options["player-log-dir"] player.run(login.csa_1st_str) # loop $mutex.lock begin if (player.game) player.game.kill(player) end player.finish # socket has been closed LEAGUE.delete(player) log_message(sprintf("user %s logout", player.name)) ensure $mutex.unlock end end end if ($0 == __FILE__) STDOUT.sync = true STDERR.sync = true TCPSocket.do_not_reverse_lookup = true Thread.abort_on_exception = $DEBUG ? true : false begin LEAGUE = ShogiServer::League.new(TOP_DIR) main rescue Exception => ex log_error("main: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}") end end