Программирование на языке Ruby - [202]

Шрифт
Интервал

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

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

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


Рекомендуем почитать
Графика DirectX в Delphi

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


Вторая жизнь старых компьютеров

Сейчас во многих школах, институтах и других учебных заведениях можно встретить компьютеры старого парка, уже отслужившие свое как морально, так и физически. На таких компьютерах можно изучать разве что Dos, что далеко от реалий сегодняшнего дня. К тому же у большинства, как правило, жесткий диск уже в нерабочем состоянии. Но и выбросить жалко, а новых никто не дает. Различные спонсоры, меценаты, бывает, подарят компьютер (один) и радуются, как дети. Спасибо, конечно, большое, но проблемы, как вы понимаете, этот компьютер в общем не решает, даже наоборот, усугубляет, работать на старых уже как-то не хочется, теперь просто есть с чем сравнивать.


DirectX 8. Начинаем работу с DirectX Graphics

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


Симуляция частичной специализации

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


Обработка событий в С++

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


Питон — модули, пакеты, классы, экземпляры

Python - объектно-ориентированный язык сверхвысокого уровня. Python, в отличии от Java, не требует исключительно объектной ориентированности, но классы в Python так просто изучить и так удобно использовать, что даже новые и неискушенные пользователи быстро переходят на ОО-подход.