【问题标题】:Ruby, SSLSockets, and Apple's Enhanced APN message formatRuby、SSLSockets 和 Apple 的增强型 APN 消息格式
【发布时间】:2010-07-19 06:47:39
【问题描述】:

我正在尝试在我的 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 可以接受所有消息,我的应用程序将挂起套接字读取调用。

我尝试解决此问题的一种方法是在单独的线程中读取套接字:

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 直到 1.9 才对 SSLSocket 进行非阻塞读取调用...遗憾的是我现在无法使用。

能否请对套接字编程有更好理解的人指出正确的方向?

【问题讨论】:

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


    【解决方案1】:

    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

    【讨论】:

      【解决方案2】:

      你能用IO.select吗?它允许您指定超时,因此您可以限制阻止的时间量。详见规范:http://github.com/rubyspec/rubyspec/blob/master/core/io/select_spec.rb

      【讨论】:

        【解决方案3】:

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

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

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

        【讨论】:

          【解决方案4】:

          有一个简单的方法。写完消息后,尝试以非阻塞模式阅读:

          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 一起使用,但它也应该与早期版本一起使用。

          【讨论】:

            猜你喜欢
            • 2019-08-26
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-11-11
            • 1970-01-01
            • 2017-09-18
            相关资源
            最近更新 更多