从Half Open Socket读取

时间:2013-11-14 21:13:56

标签: sockets tcp apple-push-notifications

我正在尝试连接到Apple推送通知服务,该服务使用通过TLS(或SSL)保护的TCP上的简单二进制协议。该协议表明,当遇到错误(大约有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. 每个插槽使用一个单独的线程来尝试 - 如果每隔5ms左右读取一次响应。
  2. 写入失败后使用阻塞读取。
  3. 远程断开后使用最终读取。
  4. 我在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
    

1 个答案:

答案 0 :(得分:0)

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

你可以看到这个related question,但它没有足够的答案。

如果您从未设法阅读错误响应,则代码必定存在问题。

  1. 使用单独的线程进行阅读对我有用,但不是100%可靠。
  2. 写入失败后使用阻塞读取 - 这是Apple建议做的,但它并不总是有效。您可能发送100条消息,第一条消息有无效令牌,并且只有在第100条消息之后才会出现写入失败。此时,从套接字读取错误响应有时为时已晚。
  3. 我不确定你的意思。
  4. 如果您希望保证读取错误响应有效,则应在每次写入后尝试读取足够的超时。当然,这对于在生产中使用是不实际的(因为它非常慢),但您可以使用它来验证您的读取和解析错误响应的代码是否正确。您还可以使用它来迭代您拥有的所有设备令牌,并找到所有无效的设备令牌,以便清理您的数据库。

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