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

发布于 2024-09-10 13:53:02 字数 1224 浏览 3 评论 0原文

我正在尝试在我的 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 在 SSLSocket 上似乎没有非阻塞读取调用,直到 1.9...不幸的是我现在无法使用它。

对套接字编程有更好了解的人可以指出我正确的方向吗?

I'm trying to implement support for Apple's enhanced Push Notification message format in my Rails app, and am having some frustrating problems. I clearly don't understand sockets as much as I thought I did.

My main problem is that if I send all messages correctly, my code hangs, because socket.read will block until I receive a message. Apple doesn't return anything if your messages looked OK, so my program locks up.

Here is some pseudocode for how I have this working:

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

Obviously, there are a number of problems with this:

  1. If I'm sending messages to a large number of devices, and the failure message is sent half way through processing, then I'm not going to actually see the error until I've already tried to send to all devices.
  2. As mentioned earlier, if all messages were acceptable to Apple, my app will hang on the socket read call.

One way I've tried to solve this is by to reading from the socket in a separate thread:

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

This doesn't seem to work. Data never seems to be read from the socket. This is probably a misunderstanding I have about how sockets are supposed to work.

The other solution I have thought of is having a non-blocking read call... but it doesn't seem like Ruby has a non blocking read call on SSLSocket until 1.9... which I unfortunately cannot use right now.

Could someone with a better understanding of socket programming please point me in the right direction?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

伤感在游骋 2024-09-17 13:53:02

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

cam is correct: the traditional way to handle this situation is with IO.select

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

This will check ssl for "readability" for 5 seconds and return ssl if it's readable or nil otherwise.

爱的故事 2024-09-17 13:53:02

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

Can you use IO.select? It lets you specify a timeout, so you could at limit the amount of time you block. See the spec for details: http://github.com/rubyspec/rubyspec/blob/master/core/io/select_spec.rb

时光病人 2024-09-17 13:53:02

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

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 末尾的某个位置,则直到我下次发送消息时才会发生救援

问题似乎在于代码并不是真正实时的。如果我使用此代码向 10 个不正确的令牌发送 10 条消息,一切都会很好,循环将完成并且不会报告任何问题。看起来 write() 不会等待一切都清除,并且在连接终止之前循环运行。下次运行循环时,write() 命令会失败(因为自上次以来我们实际上已断开连接),并且会收到错误消息。

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

I'm interested in this too, this is another approach, unfortunately with it's own flaws.

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

This seems to work, and since I'm keeping a persistent connection, I can without problems reconnect in the rescue code and start over again.

There are some issues though. The main problem I'm looking to solve is disconnects caused by sending in incorrect device tokens (for example from development builds). If I have 100 device tokens that I send a message to, and somewhere in the middle there is an incorrect token, my code lets me know which one it was (assuming I supplied good identifiers). I can then remove the faulty token, and send the message to all devices that appeared after the faulty one (since the message didn't get sent to them). But if the incorrect token is somewhere in the end of the 100, the rescue doesn't happen until the next time I send messages.

The problem seams to be that the code isn't really in real time. If I were to send in, say, 10 messages to 10 incorrect tokens with this code, everything would be just fine, the loop will go through and no problems will be reported. It seems that write() doesn't wait for everything to clear up, and the loops runs through before the connection is terminated. The next time the loop will be run, the write() command fails (since we've actually been disconnected since the last time) and we would get the error.

If there is an alternative way to respond to the failed connection, this could solve the problem.

星星的軌跡 2024-09-17 13:53:02

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

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

There is a simple way. After you write your messages, try reading in nonblocking mode:

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

I use it with MRI 2.1 but it should work with earlier versions too.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文