Ruby, SSLSockets и расширенный формат сообщений Apple APN - PullRequest
4 голосов
/ 19 июля 2010

Я пытаюсь реализовать поддержку расширенного формата сообщений push-уведомлений Apple в моем приложении Rails, и у меня возникают некоторые неприятные проблемы. Я явно не понимаю сокеты так, как я думал.

Моя основная проблема заключается в том, что если я отправлю все сообщения правильно, мой код зависнет, потому что socket.read будет блокироваться, пока я не получу сообщение. Apple не возвращает ничего , если ваши сообщения выглядят нормально, поэтому моя программа блокируется.

Вот некоторый псевдокод, как у меня это работает:

cert = File.read(options[:cert])
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey::RSA.new(cert, options[:passphrase])
ctx.cert = OpenSSL::X509::Certificate.new(cert)

sock = TCPSocket.new(options[:host], options[:port])
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
ssl.sync = true
ssl.connect

messages.each do |message|
  ssl.write(message.to_apn)
end

if read_buffer = ssl.read(6)
  process_error_response(read_buffer)
end

Очевидно, есть ряд проблем с этим:

  1. Если я отправляю сообщения на большое количество устройств, а сообщение об ошибке отправляется на полпути в процессе обработки, то я на самом деле не увижу ошибку до тех пор, пока не попытаюсь отправить на все устройства.
  2. Как упоминалось ранее, если все сообщения были приемлемы для Apple, мое приложение будет зависать в вызове чтения сокета.

Один из способов, который я попытался решить, - это чтение из сокета в отдельном потоке:

Thread.new() {
  while data = ssl.read(6)
    process_error_response(data)
  end
}

messages.each do |message|
  ssl.write(message.to_apn)
end

ssl.close
sock.close

Кажется, это не работает. Кажется, что данные никогда не читаются из сокета. Вероятно, это неправильное понимание того, как должны работать сокеты.

Другое решение, о котором я подумал, - это неблокирующий вызов чтения ... но не похоже, чтобы у Ruby был неблокирующий вызов чтения по SSLSocket до 1.9 ... который я, к сожалению, пока не могу использовать.

Может ли кто-нибудь с лучшим пониманием программирования сокетов указать мне правильное направление?

Ответы [ 4 ]

3 голосов
/ 18 августа 2010

кулачок верен: традиционный способ справиться с этой ситуацией - IO.select

if IO.select([ssl], nil, nil, 5)
  read_buffer = ssl.read(6)
  process_error_response(read_buffer)
end

. В течение 5 секунд будет проверяться ssl на «читабельность», и будет возвращаться ssl, если он читается илиnil в противном случае.

1 голос
/ 18 августа 2010

Можете ли вы использовать IO.select? Это позволяет вам указать время ожидания, чтобы вы могли ограничить количество времени, которое вы блокируете. Подробности смотрите в спецификации: http://github.com/rubyspec/rubyspec/blob/master/core/io/select_spec.rb

0 голосов
/ 14 апреля 2014

Есть простой способ.После того, как вы напишите свои сообщения, попробуйте прочитать в неблокирующем режиме:

ssl.connect
ssl.sync = true # then ssl.write() flushes immediately
ssl.write(your_packed_frame)
sleep(0.5)      # so APN have time to answer
begin
  error_packet = ssl.read_nonblock(6) # Read one packet: 6 bytes
  # If we are here, there IS an error_packet which we need to process
rescue  IO::WaitReadable
  # There is no (yet) 6 bytes from APN, probably everything is fine
end

Я использую его с MRI 2.1, но он должен работать и с более ранними версиями.

0 голосов
/ 13 августа 2010

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

messages.each do |message|
  begin
    // Write message to APNS
    ssl.write(message.to_apn)
  rescue
    // Write failed (disconnected), read response
    response = ssl.read(6)
    // Unpack the binary response and print it out
    command, errorCode, identifier = response.unpack('CCN');
    puts "Command: #{command} Code: #{errorCode} Identifier: #{identifier}"
    // Before reconnecting, the problem (assuming incorrect token) must be solved
    break
  end
end

Кажется, это работает, и, поскольку у меня постоянное соединение, я могу без проблемЗаново подключитесь в коде rescue и начните заново.

Однако есть некоторые проблемы.Основная проблема, которую я ищу, - это разъединения, вызванные отправкой неверных токенов устройства (например, из сборок разработчика).Если у меня есть 100 токенов устройств, на которые я отправляю сообщение, и где-то посередине есть неверный токен, мой код позволяет мне узнать, какой это был (при условии, что я предоставил хорошие идентификаторы).Затем я могу удалить неисправный токен и отправить сообщение всем устройствам, которые появились после неисправного (поскольку сообщение не было отправлено им).Но если неправильный токен находится где-то в конце 100, rescue не произойдет, пока я не отправлю сообщения в следующий раз.

Проблема заключается в том, что код на самом деле не является реальнымвремя.Если бы я отправил, скажем, 10 сообщений на 10 неправильных токенов с этим кодом, все было бы просто замечательно, цикл прошел бы, и о проблемах не сообщалось.Кажется, что write() не ждет, пока все прояснится, и цикл проходит до того, как соединение будет разорвано.В следующий раз, когда цикл будет запущен, команда write() завершится неудачно (так как мы фактически были отключены с прошлого раза), и мы получим ошибку.

Если есть альтернативный способ ответить насбой соединения, это может решить проблему.

...