KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Хэл Фултон - Программирование на языке Ruby

Хэл Фултон - Программирование на языке Ruby

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Хэл Фултон, "Программирование на языке Ruby" бесплатно, без регистрации.
Перейти на страницу:

Предупреждаю, что ради простоты показанная ниже программа ничего не знает о шахматах. Логика игры просто заглушена, чтобы можно было сосредоточиться на сетевых аспектах.

Для установления соединения между клиентом и сервером будем использовать протокол TCP. Можно было бы остановиться и на UDP, но этот протокол ненадежен, и нам пришлось бы использовать тайм-ауты, как в одном из примеров выше.

Клиент может передать два поля: свое имя и имя желательного противника. Для идентификации противника условимся записывать его имя в виде user:hostname; мы употребили двоеточие вместо напрашивающегося знака @, чтобы не вызывать ассоциаций с электронным адресом, каковым эта строка не является.

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

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

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

Поскольку клиенты посылают запросы и ответы попеременно, причем сеанс связи включает много таких обменов, будем пользоваться протоколом 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 сохраняет информацию о клиенте. Если запись о таком клиенте уже существует, то каждому клиенту посылается сообщение о том, где находится другой партнер. Этим обязанности сервера исчерпываются.

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

Листинг 18.2. Шахматный клиент

require "socket"

require "timeout"


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

ChessServerPort = 12000

PeerPort = 12001


WHITE, BLACK = 0, 1

Colors = %w[White Black]


def draw_board(board)

 puts <<-EOF

+------------------------------+

| Заглушка! Шахматная доска... |

+------------------------------+

 EOF

end


def analyze_move(who, move, num, board)

 # Заглушка - черные всегда выигрывают на четвертом ходу.

 if who == BLACK and num == 4

  move << " Мат!"

 end

 true # Еще одна заглушка - любой ход считается допустимым.

end


def my_move(who, lastmove, num, board, sock)

 ok = false

 until ok do

  print "nВаш ход: "

  move = STDIN.gets.chomp

  ok = analyze_move(who, move, num, board)

  puts "Недопустимый ход" if not ok

 end

  sock.puts move

 move

end


def other_move(who, move, num, board, sock)

 move = sock.gets.chomp

 puts "nПротивник: #{move}"

 move

end


if ARGV[0]

 myself = ARGV[0]

else

 print "Ваше имя? "

 myself = STDIN.gets.chomp

end


if ARGV[1]

 opponent_id = ARGV[1]

else

 print "Ваш противник? "

 opponent_id = STDIN.gets.chomp

end


opponent = opponent_id.split(":")[0] # Удалить имя хоста.

# Обратиться к серверу


socket = TCPSocket.new(ChessServer, ChessServerPort)


response = nil


socket.puts "login # {myself} #{opponent_id}"

socket.flush

response = socket.gets.chomp


name, ipname, color = response.split ":"

color = color.to_i


if color == BLACK   # Цвет фигур другого игрока,

 puts "nУстанавливается соединение..."

 server = TCPServer.new(PeerPort)

 session = server.accept

 str = nil

 begin

  timeout(30) do

   str = session.gets.chomp

   if str != "ready"

    raise "Ошибка протокола: получено сообщение о готовности #{str}."

   end

  end

 rescue TimeoutError

  raise "He получено сообщение о готовности от противника."

 end


 puts "Ваш противник #{opponent}... у вас белые.n"


 who = WHITE

 move = nil

 board = nil        # В этом примере не используется.

 num = 0

 draw_board(board)  # Нарисовать начальное положение для белых.

 loop do

  num += 1

  move = my_move(who, move, num, board, session)

  draw_board(board)

  case move

   when "resign"

    puts "nВы сдались. #{opponent} выиграл."

    break

  when /Checkmate/

    puts "nВы поставили мат #{opponent}!"

    draw_board(board)

    break

  end

  move = other_move(who, move, num, board, session)

  draw_board(board)

  case move

   when "resign"

    puts "n#{opponent} сдался... вы выиграли!"

    break

   when /Checkmate/

    puts "n#{opponent} поставил вам мат."

    break

  end

 end

else                # Мы играем черными,

 puts "nУстанавливается соединение..."

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*