4 ## Copyright (C) 2004 NABEYA Kenichi (aka nanami@2ch)
5 ## Copyright (C) 2007-2008 Daigo Moriwaki (daigo at debian dot org)
7 ## This program is free software; you can redistribute it and/or modify
8 ## it under the terms of the GNU General Public License as published by
9 ## the Free Software Foundation; either version 2 of the License, or
10 ## (at your option) any later version.
12 ## This program is distributed in the hope that it will be useful,
13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ## GNU General Public License for more details.
17 ## You should have received a copy of the GNU General Public License
18 ## along with this program; if not, write to the Free Software
19 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 TOP_DIR = File.expand_path(File.dirname(__FILE__))
22 $:.unshift File.dirname(__FILE__)
23 require 'shogi_server'
25 #################################################
31 def gets_safe(socket, timeout=nil)
32 if r = select([socket], nil, nil, timeout)
33 return r[0].first.gets
37 rescue Exception => ex
38 log_error("gets_safe: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
45 shogi-server - server for CSA server protocol
48 shogi-server [OPTIONS] event_name port_number
51 server for CSA server protocol
55 specify filename for logging process ID
57 run as a daemon. Log files will be put in dir.
59 log network messages for each player. Log files
60 will be put in the dir.
62 file name to record Floodgate game history
63 default: './floodgate_history.yaml'
66 GPL versoin 2 or later
71 #{ShogiServer::Release}
74 #{ShogiServer::Revision}
99 def parse_command_line
101 parser = GetoptLong.new(
102 ["--daemon", GetoptLong::REQUIRED_ARGUMENT],
103 ["--pid-file", GetoptLong::REQUIRED_ARGUMENT],
104 ["--player-log-dir", GetoptLong::REQUIRED_ARGUMENT],
105 ["--floodgate-history", GetoptLong::REQUIRED_ARGUMENT])
108 parser.each_option do |name, arg|
110 options[name] = arg.dup
114 raise parser.error_message
119 def write_pid_file(file)
120 open(file, "w") do |fh|
125 def mutex_watchdog(mutex, sec)
133 queue.push(Object.new)
136 log_error("mutex watchdog timeout: %d sec" % [sec])
144 def login_loop(client)
147 while r = select([client], nil, nil, ShogiServer::Login_Time) do
148 break unless str = r[0].first.gets
149 $mutex.lock # guards LEAGUE
153 if (ShogiServer::Login::good_login?(str))
154 player = ShogiServer::Player::new(str, client, eol)
155 login = ShogiServer::Login::factory(str, player)
156 if (current_player = LEAGUE.find(player.name))
157 if (current_player.password == player.password &&
158 current_player.status != "game")
159 log_message(sprintf("user %s login forcely", player.name))
162 login.incorrect_duplicated_player(str)
170 client.write("LOGIN:incorrect" + eol)
171 client.write("type 'LOGIN name password' or 'LOGIN name password x1'" + eol) if (str.split.length >= 4)
177 return [player, login]
180 def setup_logger(log_file)
181 logger = Logger.new(log_file, 'daily')
182 logger.formatter = ShogiServer::Formatter.new
183 logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
184 logger.datetime_format = "%Y-%m-%d %H:%M:%S"
188 def setup_watchdog_for_giant_lock
192 mutex_watchdog($mutex, 10)
197 return Thread.start do
199 floodgate = ShogiServer::League::Floodgate.new(LEAGUE)
200 log_message("Flooddgate reloaded. The next match will start at %s." %
201 [floodgate.next_time])
205 diff = floodgate.next_time - Time.now
213 next_time = floodgate.next_time
214 $mutex.synchronize do
215 log_message("Reloading source...")
218 floodgate = ShogiServer::League::Floodgate.new(LEAGUE, next_time)
219 log_message("Floodgate: The next match will start at %s." %
220 [floodgate.next_time])
221 rescue Exception => ex
223 log_error("[in Floodgate's thread] #{ex} #{ex.backtrace}")
231 $options = parse_command_line
232 if (ARGV.length != 2)
236 if $options["player-log-dir"]
237 $options["player-log-dir"] = File.expand_path($options["player-log-dir"])
239 if $options["player-log-dir"] &&
240 !File.directory?($options["player-log-dir"])
244 if $options["pid-file"]
245 $options["pid-file"] = File.expand_path($options["pid-file"])
247 $options["floodgate-history"] ||= File.join(File.dirname(__FILE__), "floodgate_history.yaml")
248 $options["floodgate-history"] = File.expand_path($options["floodgate-history"])
250 LEAGUE.event = ARGV.shift
253 dir = $options["daemon"]
254 dir = File.expand_path(dir) if dir
255 if dir && ! File.exist?(dir)
259 log_file = dir ? File.join(dir, "shogi-server.log") : STDOUT
260 $logger = setup_logger(log_file)
262 LEAGUE.dir = dir || TOP_DIR
266 config[:ServerType] = WEBrick::Daemon if $options["daemon"]
267 config[:Logger] = $logger
271 config[:StartCallback] = Proc.new do
273 if $options["pid-file"]
274 write_pid_file($options["pid-file"])
276 setup_watchdog_for_giant_lock
277 LEAGUE.setup_players_database
278 fg_thread = setup_floodgate
281 config[:StopCallback] = Proc.new do
282 if $options["pid-file"]
283 FileUtils.rm($options["pid-file"], :force => true)
288 server = WEBrick::GenericServer.new(config)
289 ["INT", "TERM"].each do |signal|
292 fg_thread.kill if fg_thread
298 $stderr.puts("server started as a deamon [Revision: #{ShogiServer::Revision}]") if $options["daemon"]
299 log_message("server started [Revision: #{ShogiServer::Revision}]")
301 server.start do |client|
302 # client.sync = true # this is already set in WEBrick
303 client.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
304 # Keepalive time can be set by /proc/sys/net/ipv4/tcp_keepalive_time
305 player, login = login_loop(client) # loop
308 log_message(sprintf("user %s login", player.name))
310 player.setup_logger($options["player-log-dir"]) if $options["player-log-dir"]
311 player.run(login.csa_1st_str) # loop
315 player.game.kill(player)
317 player.finish # socket has been closed
318 LEAGUE.delete(player)
319 log_message(sprintf("user %s logout", player.name))
330 TCPSocket.do_not_reverse_lookup = true
331 Thread.abort_on_exception = $DEBUG ? true : false
334 LEAGUE = ShogiServer::League.new(TOP_DIR)
336 rescue Exception => ex
338 log_error("main: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}")
340 $stderr.puts "main: #{ex.class}: #{ex.message}\n\t#{ex.backtrace[0]}"