4 # Author:: NABEYA Kenichi, Daigo Moriwaki
5 # Homepage:: http://sourceforge.jp/projects/shogi-server/
8 # Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
9 # Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # GNU General Public License for more details.
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 TOP_DIR = File.expand_path(File.dirname(__FILE__))
29 $:.unshift File.dirname(__FILE__)
30 require 'shogi_server'
32 #################################################
38 def gets_safe(socket, timeout=nil)
39 if r = select([socket], nil, nil, timeout)
40 return r[0].first.gets
44 rescue Exception => ex
45 log_error("gets_safe: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
52 shogi-server - server for CSA server protocol
55 shogi-server [OPTIONS] event_name port_number
58 server for CSA server protocol
62 specify filename for logging process ID
64 run as a daemon. Log files will be put in dir.
66 log network messages for each player. Log files
67 will be put in the dir.
69 file name to record Floodgate game history
70 default: './floodgate_history.yaml'
73 GPL versoin 2 or later
78 #{ShogiServer::Release}
81 #{ShogiServer::Revision}
106 def parse_command_line
108 parser = GetoptLong.new(
109 ["--daemon", GetoptLong::REQUIRED_ARGUMENT],
110 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT],
111 ["--player-log-dir", GetoptLong::REQUIRED_ARGUMENT],
112 ["--floodgate-history", GetoptLong::REQUIRED_ARGUMENT])
115 parser.each_option do |name, arg|
117 options[name] = arg.dup
121 raise parser.error_message
126 def write_pid_file(file)
127 open(file, "w") do |fh|
132 def mutex_watchdog(mutex, sec)
140 queue.push(Object.new)
143 log_error("mutex watchdog timeout: %d sec" % [sec])
151 def login_loop(client)
154 while r = select([client], nil, nil, ShogiServer::Login_Time) do
155 break unless str = r[0].first.gets
156 $mutex.lock # guards LEAGUE
160 if (ShogiServer::Login::good_login?(str))
161 player = ShogiServer::Player::new(str, client, eol)
162 login = ShogiServer::Login::factory(str, player)
163 if (current_player = LEAGUE.find(player.name))
164 if (current_player.password == player.password &&
165 current_player.status != "game")
166 log_message(sprintf("user %s login forcely", player.name))
169 login.incorrect_duplicated_player(str)
177 client.write("LOGIN:incorrect" + eol)
178 client.write("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
184 return [player, login]
187 def setup_logger(log_file)
188 logger = Logger.new(log_file, 'daily')
189 logger.formatter = ShogiServer::Formatter.new
190 logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
191 logger.datetime_format = "%Y-%m-%d %H:%M:%S"
195 def setup_watchdog_for_giant_lock
199 mutex_watchdog($mutex, 10)
204 return Thread.start do
206 floodgate = ShogiServer::League::Floodgate.new(LEAGUE)
207 log_message("Flooddgate reloaded. The next match will start at %s." %
208 [floodgate.next_time])
212 diff = floodgate.next_time - Time.now
220 next_time = floodgate.next_time
221 $mutex.synchronize do
222 log_message("Reloading source...")
225 floodgate = ShogiServer::League::Floodgate.new(LEAGUE, next_time)
226 log_message("Floodgate: The next match will start at %s." %
227 [floodgate.next_time])
228 rescue Exception => ex
230 log_error("[in Floodgate's thread] #{ex} #{ex.backtrace}")
238 $options = parse_command_line
239 if (ARGV.length != 2)
243 if $options["player-log-dir"]
244 $options["player-log-dir"] = File.expand_path($options["player-log-dir"])
246 if $options["player-log-dir"] &&
247 !File.directory?($options["player-log-dir"])
251 if $options["pid-file"]
252 $options["pid-file"] = File.expand_path($options["pid-file"])
254 $options["floodgate-history"] ||= File.join(File.dirname(__FILE__), "floodgate_history.yaml")
255 $options["floodgate-history"] = File.expand_path($options["floodgate-history"])
257 LEAGUE.event = ARGV.shift
260 dir = $options["daemon"]
261 dir = File.expand_path(dir) if dir
262 if dir && ! File.exist?(dir)
266 log_file = dir ? File.join(dir, "shogi-server.log") : STDOUT
267 $logger = setup_logger(log_file)
269 LEAGUE.dir = dir || TOP_DIR
273 config[:ServerType] = WEBrick::Daemon if $options["daemon"]
274 config[:Logger] = $logger
278 config[:StartCallback] = Proc.new do
280 if $options["pid-file"]
281 write_pid_file($options["pid-file"])
283 setup_watchdog_for_giant_lock
284 LEAGUE.setup_players_database
285 fg_thread = setup_floodgate
288 config[:StopCallback] = Proc.new do
289 if $options["pid-file"]
290 FileUtils.rm($options["pid-file"], :force => true)
295 server = WEBrick::GenericServer.new(config)
296 ["INT", "TERM"].each do |signal|
299 fg_thread.kill if fg_thread
305 $stderr.puts("server started as a deamon [Revision: #{ShogiServer::Revision}]") if $options["daemon"]
306 log_message("server started [Revision: #{ShogiServer::Revision}]")
308 server.start do |client|
309 # client.sync = true # this is already set in WEBrick
310 client.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
311 # Keepalive time can be set by /proc/sys/net/ipv4/tcp_keepalive_time
312 player, login = login_loop(client) # loop
315 log_message(sprintf("user %s login", player.name))
317 player.setup_logger($options["player-log-dir"]) if $options["player-log-dir"]
318 player.run(login.csa_1st_str) # loop
322 player.game.kill(player)
324 player.finish # socket has been closed
325 LEAGUE.delete(player)
326 log_message(sprintf("user %s logout", player.name))
337 TCPSocket.do_not_reverse_lookup = true
338 Thread.abort_on_exception = $DEBUG ? true : false
341 LEAGUE = ShogiServer::League.new(TOP_DIR)
343 rescue Exception => ex
345 log_error("main: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
347 $stderr.puts "main: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"