Sockets 从半开插座读取

Sockets 从半开插座读取,sockets,tcp,apple-push-notifications,Sockets,Tcp,Apple Push Notifications,我正在尝试连接苹果推送通知服务,该服务使用一个简单的二进制协议,通过受TLS(或SSL)保护的TCP。协议指出,当遇到错误时(大约有10个定义良好的错误条件),APN将发送回错误响应,然后关闭连接。这会导致套接字半关闭,因为远程对等方关闭了该套接字。我可以看到这是一个优雅的关机,因为APNS使用tcpdump发送FIN和RST 在所有错误条件中,我可以在发送验证之前处理大多数错误。此操作失败的情况是,向无效的设备令牌发送通知时,由于令牌的格式可能不正确,因此无法轻松处理该令牌。令牌是不透明的32

我正在尝试连接苹果推送通知服务,该服务使用一个简单的二进制协议,通过受TLS(或SSL)保护的TCP。协议指出,当遇到错误时(大约有10个定义良好的错误条件),APN将发送回错误响应,然后关闭连接。这会导致套接字半关闭,因为远程对等方关闭了该套接字。我可以看到这是一个优雅的关机,因为APNS使用tcpdump发送FIN和RST

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

反正

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

a.  open socket
b.  try writing
c.  if write failed, read the error response
不幸的是,这似乎不起作用。我想APNS正在发送一个错误响应,我没有正确读取它,或者我没有正确设置套接字。我尝试了以下技巧:-

  • 每个套接字使用一个单独的线程,尝试每隔5毫秒左右读取响应(如果有)
  • 写入失败后使用阻塞读取
  • 远程断开后使用最终读数
  • 我已经在Windows上的C++.NET4.5和Linux上的Java1.7上尝试过这一点。在这两种情况下,我似乎从未得到错误响应,并且套接字指示没有可读取的数据

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

    我知道我的设置方式是正确的,因为如果我使用了有效的令牌和有效的通知,这些令牌就会被传递

    作为对其中一条评论的回应,我使用了增强的通知格式,因此APNS应该会做出回应

    这是我的C#代码:-

    X509证书=
    新的X509证书(@“Foo.cer”、“密码”);
    X509CertificateCollection集合=新的X509CertificateCollection();
    收藏。添加(证书);
    插座插座=
    新套接字(AddressFamily.InterNetwork、SocketType.Stream、ProtocolType.Tcp);
    socket.Connect(“gateway.sandbox.push.apple.com”,2195);
    网络流=
    新的网络流(socket、System.IO.FileAccess.ReadWrite、false);
    stream.ReadTimeout=1000;
    stream.WriteTimeout=1000;
    SSL流=
    新SSL流(流,真,
    新的RemoteCertificateValidationCallback(ValidateServerCertificate),null);
    sslStream.authenticatesClient(“gateway.sandbox.push.apple.com”,集合,
    SslProtocols.Default,false);
    sslStream.ReadTimeout=10000;
    sslStream.WriteTimeout=1000;
    //Task rdr=Task.Factory.StartNew(this.reader);
    //rdr用于在每次读取之间并行读取5ms的套接字。
    //现在没有使用,但另一种选择是尝试过的。
    Random r=新随机(DateTime.Now.Second);
    字节[]缓冲区=新字节[32];
    r、 下字节(缓冲区);
    字节[]resp=新字节[6];
    字符串标记=toHex(缓冲区);
    TimeSpan t=(DateTime.UtcNow-新的日期时间(1970,1,1));
    int timestamp=(int)t.TotalSeconds;
    尝试
    {
    对于(int i=0;i<1000;++i)
    {
    //生成通知;格式在APNS文档中发布。
    var not=new ApplicationNotificationBuilder().withToken(缓冲区)。withPayload(
    @{“aps”:{“警报”:“foo”,“声音”:“default”,“badge”:1}}')。过期(
    timestamp).withIdentifier(i+1).build();
    sslStream.Write(缓冲区);
    sslStream.Flush();
    Console.Out.WriteLine(“发送消息”#“+i);
    int rd=SSL流读取(分别为0,6);
    如果(rd>0)
    {
    Console.Out.WriteLine(“找到响应:+rd”);
    打破
    }
    //不管我们发送的速度有多快或多慢
    睡眠(500);
    }
    }
    捕获(例外情况除外)
    {
    Console.Out.WriteLine(“未能写入…”);
    int rd=SSL流读取(分别为0,6);
    如果(rd>0)
    {
    Console.Out.WriteLine(“找到响应:+rd”);
    }
    }
    //rdr.Wait();更改为非无限超时以允许错误读取器终止
    
    我在Java中实现了APN的服务器端,在可靠地读取错误响应时遇到了问题(这意味着永远不会错过任何错误响应),但我确实设法获得了错误响应

    你可以看到这一点,尽管它没有足够的答案

    如果您从未设法读取错误响应,那么您的代码肯定有问题

  • 使用单独的线程进行阅读对我来说很有效,但不是100%可靠
  • 使用写失败后的阻塞读取——这是苹果公司建议的做法,但并不总是有效。您可能发送100条消息,第一条消息具有无效令牌,并且只有在第100条消息之后,您才会收到写入失败。此时,从套接字读取错误响应有时为时已晚
  • 我不知道你说的是什么意思
  • 如果要保证错误响应的读取工作正常,应在每次写入后尝试读取,并留出足够的超时时间。当然,这对于在生产中使用是不实际的(因为它非常慢),但是您可以使用它来验证读取和解析错误响应的代码是否正确。您还可以使用它来迭代您拥有的所有设备令牌,并查找所有无效的令牌,以便清理数据库


    你没有发布任何代码,所以我不知道你用什么二进制格式向APNS发送消息。如果您使用的是简单格式(以0字节开始,没有消息ID),您将无法从Apple获得任何响应。

    发布一些代码。任何待发送的数据在FIN之前到达。如果你看不懂的话
    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