我正在尝试连接到 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 正在发送错误响应,但我没有正确读取它,或者我没有正确设置套接字。我尝试了以下技术:-
- 每个套接字使用一个单独的线程来尝试每 5 毫秒左右读取一次响应。
- 在写入失败后使用阻塞读取。
- 远程断开后使用最终读取。
我已经在 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