цветом будет играть. Для простоты предположим, что цвет назначает сервер. Первый обратившийся клиент будет играть белыми (и, стало быть, ходить первым), второй — черными.

Уточним: компьютеры, которые первоначально были клиентами, начиная с этого момента общаются друг с другом напрямую; следовательно, один из них становится сервером. Но на эту семантическую тонкость я не буду обращать внимания.

Поскольку клиенты посылают запросы и ответы попеременно, причем сеанс связи включает много таких обменов, будем пользоваться протоколом TCP. Следовательно, клиент, который на самом деле играет роль «сервера», создает объект TCPServer, а клиент на другом конце — объект TCPSocket. Будем предполагать, что номер порта для обмена данными заранее известен обоим партнерам (разумеется, У каждого из них свой номер порта).

Мы только что описали простой протокол прикладного уровня. Его можно было бы сделать и более хитроумным.

Сначала рассмотрим код сервера (листинг 18.1). Чтобы его было проще запускать из командной строки, создадим поток, который завершит сервер при нажатии клавиши Enter. Сервер многопоточный — он может одновременно обслуживать нескольких клиентов. Данные о пользователях защищены мьютексом, ведь теоретически несколько потоков могут одновременно попытаться добавить новую запись в список.

Листинг 18.1. Шахматный сервер

require 'thread'

require 'socket'

PORT = 12000

HOST = '96.97.98.99'              # Заменить этот IP-адрес.

# Выход при нажатии клавиши Enter.

waiter = Thread.new do

 puts 'Нажмите Enter для завершения сервера.'

 gets

 exit

end

$mutex = Mutex.new

$list = {}

def match?(p1, p2)

 return false if !$list[p1] or !$list[p2]

 if ($list[p1][0] == p2 and $list[p2][0] == p1)

  true

 else

  false

 end

end

def handle_client(sess, msg, addr, port, ipname)

 $mutex.synchronize do

  cmd, player1, player2 = msg.split

  # Примечание: от клиента мы получаем данные в виде user:hostname,

  # но храним их в виде user:address.

  p1short = player1.dup           # Короткие имена

  p2short = player2.split(':')[0] # (то есть не ':address').

  player1 << ':#{addr}'           # Добавить IP-адрес клиента.

  user2, host2 = player2.split(':')

  host2 = ipname if host2 == nil

  player2 = user2 + ':' + IPSocket.getaddress(host2)

  if cmd != 'login'

   puts 'Ошибка протокола: клиент послал сообщение #{msg}.'

  end

  $list[player1] = [player2, addr, port, ipname, sess]

  if match?(player1, player2)

   # Имена теперь переставлены: если мы попали сюда, значит

   # player2 зарегистрировался первым.

   p1 = $list[player1]

   р2 = $list[player2]

   # ID игрока = name:ipname:color

   # Цвет: 0=белый, 1=черный

   p1id = '#{p1short}:#{p1[3]}:1'

   p2id = '#{p2short}:#{p2[3]}:0'

   sess1 = p1[4]

   sess2 = p2[4]

   sess1.puts '#{p2id}'

   sess2.puts '#{p1id}'

   sess1.close

   sess2.close

  end

 end

end

text = nil

$server = TCPServer.new(HOST, PORT)

while session = $server.accept do

 Thread.new(session) do |sess|

  text = sess.gets

  puts 'Получено: #{text}' # Чтобы знать, что сервер получил.

  domain, port, ipname, ipaddr = sess.peeraddr

  handle_client sess, text, ipaddr, port, ipname

  sleep 1

 end

end

waiter.join                # Выходим, когда была нажата клавиша Enter.

Метод handle_client сохраняет информацию о клиенте. Если запись о таком клиенте уже существует, то каждому клиенту посылается сообщение о том, где находится другой партнер. Этим обязанности сервера исчерпываются.

Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату