Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Программирование на языке Ruby.docx
Скачиваний:
19
Добавлен:
06.09.2019
Размер:
1.74 Mб
Скачать

18.1. Сетевые серверы

Жизнь сервера проходит в ожидании входных сообщений и ответах на них.

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

Но даже это можно организовать разными способами. Сервер может в каждый момент времени обслуживать только один запрос или иметь несколько потоков. Первый подход проще реализовать, зато у второго есть преимущества, когда много клиентов одновременно обращается с запросами.

Можно представить себе сервер, единственное назначение которого состоит в том, чтобы облегчить общение между клиентами. Классические примеры — чат-серверы, игровые серверы и файлообменные сети.

18.1.1. Простой сервер: время дня

Рассмотрим самый простой сервер, который вы только способны представить. Пусть некоторая машина располагает такими точными часами, что ее можно использовать в качестве стандарта времени. Такие серверы, конечно, существуют, но взаимодействуют не по тому тривиальному протоколу, который мы обсудим ниже. (В разделе 18.2.2 приведен пример обращения к подобному серверу по протоколу telnet.)

В нашем примере все запросы обслуживаются в порядке поступления однопоточным сервером. Когда приходит запрос от клиента, мы возвращаем строку, содержащую текущее время. Ниже приведен код сервера:

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 не нужно, поскольку сервер исполняет бесконечный цикл, пока его не остановят вручную.

Код клиента, конечно, остался тем же самым. С точки зрения клиента, поведение сервера не изменилось (разве что он стал более надежным).