我正在尝试在我的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
显然,这有很多问题:
我尝试解决此问题的一种方法是在单独的线程中读取套接字:
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 ...我很遗憾现在无法使用它。
能够更好地理解套接字编程的人能指出我正确的方向吗?
答案 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中使用它,但它也适用于早期版本。