2

我正在尝试连接到 Apple 推送通知服务,该服务在 TCP 上使用简单的二进制协议,并受 TLS(或 SSL)保护。该协议表明,当遇到错误时(大约有 10 个明确定义的错误条件),APNS 将发送回错误响应,然后关闭连接。这会导致套接字半关闭,因为远程对等方关闭了套接字。我可以看到它正常关闭,因为 APNS 使用 tcpdump 发送了 FIN 和 RST。

在所有错误情况中,我可以在发送验证之前处理大多数情况。失败的情况是当通知被发送到无效的设备令牌时,由于令牌可能格式错误,因此无法轻松处理。令牌是不透明的 32 字节值,由 APNS 提供给设备,然后向我注册。我无法知道它在提交给我的服务时是否有效。据推测,APNS 以某种方式对令牌进行校验和,以便他们可以快速对令牌进行快速验证。

反正,

我做了我认为正确的事情:-

a.  open socket
b.  try writing
c.  if write failed, read the error response

不幸的是,这似乎不起作用。我认为 APNS 正在发送错误响应,但我没有正确读取它,或者我没有正确设置套接字。我尝试了以下技术:-

  1. 每个套接字使用一个单独的线程来尝试每 5 毫秒左右读取一次响应。
  2. 在写入失败后使用阻塞读取。
  3. 远程断开后使用最终读取。

我已经在 Windows 上使用 C# + .NET 4.5 和 Linux 上使用 Java 1.7 进行了尝试。在任何一种情况下,我似乎都没有收到错误响应,并且套接字表明没有数据可供读取。

这些操作系统和/或框架是否支持半封闭套接字?似乎没有任何迹象表明这两种方式。

我知道我设置的方式可以正常工作,因为如果我使用带有有效通知的有效令牌,那些确实会交付。

作为对其中一条评论的回应,我使用了增强的通知格式,因此应该从 APNS 收到响应。

这是我的 C# 代码:-

X509Certificate certificate = 
new X509Certificate(@"Foo.cer", "password");
X509CertificateCollection collection = new X509CertificateCollection();
collection.Add(certificate);

Socket socket = 
   new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect("gateway.sandbox.push.apple.com", 2195);

NetworkStream stream = 
   new NetworkStream(socket, System.IO.FileAccess.ReadWrite, false);
stream.ReadTimeout = 1000;
stream.WriteTimeout = 1000;

sslStream = 
  new SslStream(stream, true, 
     new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
sslStream.AuthenticateAsClient("gateway.sandbox.push.apple.com", collection,
                               SslProtocols.Default, false);
sslStream.ReadTimeout = 10000;
sslStream.WriteTimeout = 1000;

// Task rdr = Task.Factory.StartNew(this.reader);
// rdr is used for parallel read of socket sleeping 5ms between each read.
// Not used now but another alternative that was tried.

Random r = new Random(DateTime.Now.Second);
byte[] buffer = new byte[32];
r.NextBytes(buffer);
byte[] resp = new byte[6];

String erroneousToken = toHex(buffer);

TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
int timestamp  = (int) t.TotalSeconds;

try
{
    for (int i = 0; i < 1000; ++i)
    {
        // build the notification; format is published in APNS docs.
        var not = new ApplicationNotificationBuilder().withToken(buffer).withPayload(
             @'{"aps": {"alert":"foo","sound":"default","badge":1}}').withExpiration(
                timestamp).withIdentifier(i+1).build();

        sslStream.Write(buffer);
        sslStream.Flush();
        Console.Out.WriteLine("Sent message # " + i);

        int rd = sslStream.Read(resp, 0, 6);

        if (rd > 0)
        {
            Console.Out.WriteLine("Found response: " + rd);
            break;
        }

        // doesn't really matter how fast or how slow we send
        Thread.Sleep(500);
    }
}
catch (Exception ex)
{
    Console.Out.WriteLine("Failed to write ...");

    int rd = sslStream.Read(resp, 0, 6);

    if (rd > 0)
    {
        Console.Out.WriteLine("Found response: " + rd); ;
    }
}

// rdr.Wait(); change to non-infinite timeout to allow error reader to terminate
4

1 回答 1

0

我在 Java 中为 APNS 实现了服务器端,并且在可靠地读取错误响应时遇到了问题(这意味着 - 永远不会错过任何错误响应),但我确实设法获得了错误响应。

你可以看到这个相关的问题,尽管它没有足够的答案。

如果您从未设法阅读错误响应,则您的代码一定有问题。

  1. 使用单独的线程阅读对我有用,尽管不是 100% 可靠。
  2. 在写入失败后使用阻塞读取 - 这是 Apple 建议做的,但它并不总是有效。您可能发送了 100 条消息,而第一条消息的令牌无效,并且只有在第 100 条消息之后您才会收到写入失败。此时读取套接字的错误响应有时为时已晚。
  3. 我不确定你的意思。

如果你想保证读取错误响应会正常工作,你应该在每次写入后尝试读取,并有足够的超时时间。当然,这对于在生产中使用是不实际的(因为它非常慢),但是您可以使用它来验证您读取和解析错误响应的代码是否正确。您还可以使用它来遍历您拥有的所有设备令牌,并找到所有无效的设备令牌,以清理您的数据库。

您没有发布任何代码,所以我不知道您使用什么二进制格式向 APNS 发送消息。如果您使用的是简单格式(以 0 字节开头且没有消息 ID),您将不会收到 Apple 的任何回复。

于 2013-11-14T21:36:26.967 回答