В нашем примере все запросы обслуживаются в порядке поступления однопоточным сервером. Когда приходит запрос от клиента, мы возвращаем строку, содержащую текущее время. Ниже приведен код сервера:
require "socket"
PORT = 12321
HOST = ARGV[0] || 'localhost'
server = UDPSocket.open # Применяется протокол UDP...
server.bind nil, PORT
loop do
text, sender = server.recvfrom(1)
server.send(Time.new.to_s + "\n", 0, sender[3], sender[1])
end
А это код клиента:
require "socket"
require "timeout"
PORT = 12321
HOST = ARGV[0] || 'localhost'
socket = UDPSocket.new
socket.connect(HOST, PORT)
socket.send("", 0)
timeout(10) do
time = socket.gets
puts time
end
Чтобы сделать запрос, клиент посылает пустой пакет. Поскольку протокол UDP ненадежен, то, не получив ответа в течение некоторого времени, мы завершаем работу по тайм-ауту.
В следующем примере такой же сервер реализован на базе протокола TCP. Он прослушивает порт 12321; запросы к этому порту можно посылать с помощью программы telnet (или клиента, код которого приведен ниже).
require "socket"
PORT = 12321
server = TCPServer.new(PORT)
while (session = server.accept)
session.puts Time.new
session.close
end
Обратите внимание, как просто использовать класс TCPServer
. Вот TCP-версия клиента:
require "socket"
PORT = 12321
HOST = ARGV[0] || "localhost"
session = TCPSocket.new(HOST, PORT)
time = session.gets
session.close
puts time
18.1.2. Реализация многопоточного сервера
Некоторые серверы должны обслуживать очень интенсивный поток запросов. В таком случае эффективнее обрабатывать каждый запрос в отдельном потоке.
Ниже показана реализация сервера текущего времени, с которым мы познакомились в предыдущем разделе. Он работает по протоколу TCP и создает новый поток для каждого запроса.
require "socket"
PORT = 12321
server = TCPServer.new(PORT)
while (session = server.accept)
Thread.new(session) do |my_session|
my_session.puts Time.new
my_session.close
end
end
Многопоточность позволяет достичь высокого параллелизма. Вызывать метод join
не нужно, поскольку сервер исполняет бесконечный цикл, пока его не остановят вручную.
Код клиента, конечно, остался тем же самым. С точки зрения клиента, поведение сервера не изменилось (разве что он стал более надежным).
18.1.3. Пример: сервер для игры в шахматы по сети
Не всегда нашей конечной целью является взаимодействие с самим сервером. Иногда сервер — всего лишь средство для соединения клиентов друг с другом. В качестве примера можно привести файлообменные сети, столь популярные в 2001 году. Другой пример — серверы для мгновенной передачи сообщений, например ICQ, и разного рода игровые серверы.
Давайте напишем скелет шахматного сервера. Мы не имеем в виду программу, которая будет играть в шахматы с клиентом. Нет, наша задача — связать клиентов так, чтобы они могли затем играть без вмешательства сервера.
Предупреждаю, что ради простоты показанная ниже программа ничего не знает о шахматах. Логика игры просто заглушена, чтобы можно было сосредоточиться на сетевых аспектах.
Для установления соединения между клиентом и сервером будем использовать протокол TCP. Можно было бы остановиться и на UDP, но этот протокол ненадежен, и нам пришлось бы использовать тайм-ауты, как в одном из примеров выше.
Клиент может передать два поля: свое имя и имя желательного противника. Для идентификации противника условимся записывать его имя в виде user:hostname
; мы употребили двоеточие вместо напрашивающегося знака @
, чтобы не вызывать ассоциаций с электронным адресом, каковым эта строка не является.
Когда от клиента приходит запрос, сервер сохраняет сведения о клиенте у себя в списке. Если поступили запросы от обоих клиентов, сервер посылает каждому из них сообщение; теперь у каждого клиента достаточно информации для установления связи с противником.
Есть еще вопрос о выборе цвета фигур. Оба партнера должны как-то договориться о том, кто каким цветом будет играть. Для простоты предположим, что цвет назначает сервер. Первый обратившийся клиент будет играть белыми (и, стало быть, ходить первым), второй — черными.
Уточним: компьютеры, которые первоначально были клиентами, начиная с этого момента общаются друг с другом напрямую; следовательно, один из них становится сервером. Но на эту семантическую тонкость я не буду обращать внимания.
Читать дальше
Конец ознакомительного отрывка
Купить книгу