Ruby,SSLSockets和Apple的增强型APN消息格式

时间:2010-07-19 06:47:39

标签: ruby-on-rails ruby sockets push-notification apple-push-notifications

我正在尝试在我的Rails应用程序中实现对Apple增强的推送通知消息格式的支持,并且遇到了一些令人沮丧的问题。我显然不像我想的那样理解插座。

我的主要问题是,如果我正确发送所有消息,我的代码会挂起,因为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接受,我的应用程序将挂起套接字读取呼叫。
  3. 我尝试解决此问题的一种方法是在单独的线程中读取套接字:

    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 个答案:

答案 0 :(得分:3)

cam是正确的:处理这种情况的传统方法是使用IO.select

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

这将检查ssl“可读性”5秒钟,如果可读,则返回ssl,否则返回nil

答案 1 :(得分:1)

您可以使用IO.select吗?它允许您指定超时,因此您可以限制阻止的时间。有关详细信息,请参阅规范:http://github.com/rubyspec/rubyspec/blob/master/core/io/select_spec.rb

答案 2 :(得分:0)

我也对此感兴趣,这是另一种方法,遗憾的是它有自己的缺陷。

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()命令失败(因为我们自上次以来实际上已经断开连接),我们就会收到错误。

如果有另一种方法来响应失败的连接,这可以解决问题。

答案 3 :(得分:0)

有一种简单的方法。编写邮件后,请尝试以非阻止模式阅读:

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中使用它,但它也适用于早期版本。