2013-08-22 36 views
9

EDIT - Sử dụng định dạng nhị phân tăng cườngAPNS SSL hoạt động thất bại với mã 1

Hóa ra tôi đã không sử dụng các định dạng nhị phân tăng cường vì vậy tôi đã thay đổi mã của tôi.

<?php 

$message = $_POST['message']; 
$passphrase = $_POST['pass']; 

//Connect to db 


if ($db_found) { 

// Create the payload body 
$body['aps'] = array(
    'alert' => $message, 
    'sound' => 'default' 
); 

$streamContext = stream_context_create(); 
stream_context_set_option($streamContext, 'ssl', 'local_cert', 'x.pem'); 
stream_context_set_option($streamContext, 'ssl', 'passphrase', $passphrase); 

$fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $error, $errorString, 15, STREAM_CLIENT_CONNECT, $streamContext); 
stream_set_blocking ($fp, 0); 

if (!$fp) 
    exit("Failed to connect: $err $errstr" . PHP_EOL); 

echo 'Connected to APNS for Push Notification' . PHP_EOL; 

// Keep push alive (waiting for delivery) for 90 days 
$apple_expiry = time() + (90 * 24 * 60 * 60); 



$tokenResult = //SQL QUERY TO GET TOKENS 

while($row = mysql_fetch_array($tokenResult)) { 
    $apple_identifier = $row["id"]; 
    $deviceToken = $row['device_id']; 
    $payload = json_encode($body); 

    // Enhanced Notification 
    $msg = pack("C", 1) . pack("N", $apple_identifier) . pack("N", $apple_expiry) . pack("n", 32) . pack('H*', str_replace(' ', '', $deviceToken)) . pack("n", strlen($payload)) . $payload; 

    // SEND PUSH 
    fwrite($fp, $msg); 

    // We can check if an error has been returned while we are sending, but we also need to 
    // check once more after we are done sending in case there was a delay with error response. 
    checkAppleErrorResponse($fp); 
} 

// Workaround to check if there were any errors during the last seconds of sending. 
// Pause for half a second. 
// Note I tested this with up to a 5 minute pause, and the error message was still available to be retrieved 
usleep(500000); 

checkAppleErrorResponse($fp); 

echo 'Completed'; 

fclose($fp); 


// SIMPLE BINARY FORMAT 
/*for($i = 0; $i<count($deviceToken); $i++) { 

    // Encode the payload as JSON 
    $payload = json_encode($body); 

    // Build the binary notification 
    $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken[$i]) . pack('n', strlen($payload)) . $payload; 

    // Send it to the server 
    $result = fwrite($fp, $msg, strlen($msg)); 

    $bodyError .= 'result: '.$result.', devicetoken: '.$deviceToken[$i].''; 

    if (!$result) { 
     $errCounter = $errCounter + 1; 
     echo 'Message not delivered' . PHP_EOL; 
    } 
    else 
     echo 'Message successfully delivered' . PHP_EOL; 
}*/ 


// Close the connection to the server 
//fclose($fp); 


//Insert message into database 

mysql_close($db_handle); 

} 

else { 

    print "Database niet gevonden "; 
    mysql_close($db_handle); 
} 

// FUNCTION to check if there is an error response from Apple 
// Returns TRUE if there was and FALSE if there was not 
function checkAppleErrorResponse($fp) { 

//byte1=always 8, byte2=StatusCode, bytes3,4,5,6=identifier(rowID). 
// Should return nothing if OK. 

//NOTE: Make sure you set stream_set_blocking($fp, 0) or else fread will pause your script and wait 
// forever when there is no response to be sent. 

$apple_error_response = fread($fp, 6); 

if ($apple_error_response) { 

    // unpack the error response (first byte 'command" should always be 8) 
    $error_response = unpack('Ccommand/Cstatus_code/Nidentifier', $apple_error_response); 

    if ($error_response['status_code'] == '0') { 
    $error_response['status_code'] = '0-No errors encountered'; 

    } else if ($error_response['status_code'] == '1') { 
    $error_response['status_code'] = '1-Processing error'; 

    } else if ($error_response['status_code'] == '2') { 
    $error_response['status_code'] = '2-Missing device token'; 

    } else if ($error_response['status_code'] == '3') { 
    $error_response['status_code'] = '3-Missing topic'; 

    } else if ($error_response['status_code'] == '4') { 
    $error_response['status_code'] = '4-Missing payload'; 

    } else if ($error_response['status_code'] == '5') { 
    $error_response['status_code'] = '5-Invalid token size'; 

    } else if ($error_response['status_code'] == '6') { 
    $error_response['status_code'] = '6-Invalid topic size'; 

    } else if ($error_response['status_code'] == '7') { 
    $error_response['status_code'] = '7-Invalid payload size'; 

    } else if ($error_response['status_code'] == '8') { 
    $error_response['status_code'] = '8-Invalid token'; 

    } else if ($error_response['status_code'] == '255') { 
    $error_response['status_code'] = '255-None (unknown)'; 

    } else { 
    $error_response['status_code'] = $error_response['status_code'].'-Not listed'; 

    } 

    echo '<br><b>+ + + + + + ERROR</b> Response Command:<b>' . $error_response['command'] . '</b>&nbsp;&nbsp;&nbsp;Identifier:<b>' . $error_response['identifier'] . '</b>&nbsp;&nbsp;&nbsp;Status:<b>' . $error_response['status_code'] . '</b><br>'; 

    echo 'Identifier is the rowID (index) in the database that caused the problem, and Apple will disconnect you from server. To continue sending Push Notifications, just start at the next rowID after this Identifier.<br>'; 

    return true; 
} 

return false; 
} 

?> 

Trong khi sử dụng mã mới này, tôi vẫn không thể gửi nhiều hơn 300 thông điệp vì lỗi này:

Warning: fwrite() [function.fwrite]: SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry in PATH_TO_SCRIPT.php on line NUMBER 

mã này hoạt động tốt khi gửi chỉ là một vài báo đẩy.

CÂU HỎI THƯỜNG GẶP với định dạng nhị phân đơn giản Vì vậy, tôi đã tích hợp Thông báo đẩy một thời gian dài trước đây và nó hoạt động tốt cho các thư được gửi tới dưới 500 người. Bây giờ tôi đang cố gắng để gửi một thông báo push to hơn 1000 người nhưng sau đó tôi nhận được lỗi bị hỏng

Warning: fwrite() [function.fwrite]: SSL: Broken pipe in PATH_TO.PHP on line x 

Tôi đã đọc các tài liệu táo và tôi biết rằng thẻ không hợp lệ có thể gây ra các ổ cắm để ngắt kết nối. Một số giải pháp trực tuyến giới thiệu về ngắt kết nối và kết nối lại phát hiện như thế này:

Your server needs to detect disconnections and reconnect if necessary. Nothing is 
"instant" when networking is involved; there's always some latency and code needs to take 
that into account. Also, consider using the enhanced binary interface so you can check the 
return response and know why the connection was dropped. The connection can also be 
dropped as a result of TCP keep-alive, which is outside of Apple's control. 

Tôi cũng đang chạy một dịch vụ Phản hồi đó phát hiện thẻ không hợp lệ (Người dùng muốn Đẩy Notifications nhưng xóa ứng dụng) và chỉ hoạt động tốt. Tập lệnh php đó lặp lại ID đã xóa và tôi có thể xác nhận rằng các mã thông báo đó đã bị xóa khỏi cơ sở dữ liệu MySQL của chúng tôi.

Làm cách nào để có thể phát hiện thấy ngắt kết nối hoặc đường ống bị hỏng và phản ứng với điều đó để thông báo đẩy của tôi có thể tiếp cận hơn 1000 người?

Hiện tại tôi đang sử dụng tập lệnh push.php đơn giản này.

<?php 

$message = $_POST['message']; 
$passphrase = $_POST['pass']; 

//Connect to database stuff 

if ($db_found) { 
     $streamContext = stream_context_create(); 
     stream_context_set_option($streamContext, 'ssl', 'local_cert', 'x.pem'); 
     stream_context_set_option($streamContext, 'ssl', 'passphrase', $passphrase); 

     $fp = stream_socket_client('ssl://gateway.push.apple.com:2195', $error, $errorString, 15, STREAM_CLIENT_CONNECT, $streamContext); 

if (!$fp) 
    exit("Failed to connect: $err $errstr" . PHP_EOL); 

echo 'Connected to APNS for Push Notification' . PHP_EOL; 

$deviceToken[] = //GET ALL TOKENS FROM DATABASE AND STORE IN ARRAY 

for($i = 0; $i<count($deviceToken); $i++) { 
    // Create the payload body 
    $body['aps'] = array(
    'alert' => $message, 
    'sound' => 'default' 
    ); 

    // Encode the payload as JSON 
    $payload = json_encode($body); 

    // Build the binary notification 
    $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken[$i]) . pack('n', strlen($payload)) . $payload; 

    // Send it to the server 
    $result = fwrite($fp, $msg, strlen($msg)); 

    $bodyError .= 'result: '.$result.', devicetoken: '.$deviceToken[$i].''; 

    if (!$result) { 
     $errCounter = $errCounter + 1; 
     echo 'Message not delivered' . PHP_EOL; 
    } 
    else 
     echo 'Message successfully delivered' . PHP_EOL; 
} 


echo $bodyError; 

// Close the connection to the server 
fclose($fp); 


//CODE TO SAVE MESSAGE TO DATABSE HERE 

if (!mysql_query($SQL,$db_handle)) { 
    die('Error: ' . mysql_error()); 
} 

} 
else { 
    print "Database niet gevonden "; 
    mysql_close($db_handle); 
} 


?> 

Cũng trả về fwrite 0 byte bằng văn bản khi xảy ra lỗi ống SLL bị hỏng.

Tôi cũng phải đề cập rằng tôi không có PHP hoặc nhà phát triển web nhưng là nhà phát triển ứng dụng nên kỹ năng php của tôi không tốt.

+0

Bạn đang sử dụng định dạng nhị phân đơn giản, mà không trả lại câu trả lời. Bạn nên sử dụng định dạng nâng cao để nhận phản hồi lỗi. Xem [câu trả lời này] (http://stackoverflow.com/questions/17724614/using-push-notification-usingf-api/17726433#17726433) để biết thêm chi tiết. – Eran

+0

Cảm ơn vì điều đó. Tôi đang sử dụng định dạng enchanced ngay bây giờ và nó trả về lỗi này khi gửi một lượng lớn thư (không phải trong khi gửi chỉ một vài thư): Cảnh báo: fwrite() [function.fwrite]: Thao tác SSL không thành công với mã 1. OpenSSL Thông báo lỗi: lỗi: 1409F07F: Thường trình SSL: SSL3_WRITE_PENDING: viết xấu thử lại trong PATH_TO_PHP_FILE_ON_LINE –

+0

Nhìn vào [câu hỏi này] (http://stackoverflow.com/questions/2997218/why-am-i-getting-error1409f07fssl-routinesssl3- write-pending-bad-write-retr), nó có giúp ích gì không? – LombaX

Trả lời

3

Khi bạn làm:

fwrite($fp, $msg); 

bạn đang cố gắng ghi vào ổ cắm. Nếu có điều gì sai, fwrite sẽ trả lại false hoặc 0 (tùy thuộc vào phiên bản php) làm giá trị trả lại. Khi nó xảy ra, bạn phải quản lý nó. Bạn có hai khả năng:

  • loại bỏ toàn bộ hoạt động
  • thử lại lần viết gần nhất hoạt động

nếu bạn chọn tùy chọn thứ hai, bạn phải làm một mới fwrite($fp, $msg) với THE $ fp CÙNG và $ msg của hoạt động không hoạt động fwrite(). Nếu bạn thay đổi thông số, lỗi 1409F07F:SSL được trả lại

Hơn nữa, có trường hợp fwrite không thành công khi chỉ viết "một số byte", bạn nên quản lý ngay cả trường hợp này, so sánh giá trị trả về với chiều dài của $ msg . Trong trường hợp này, bạn nên gửi phần còn lại của tin nhắn, nhưng trong một số trường hợp, bạn phải gửi lại toàn bộ tin nhắn (theo số this link).

Có xem xét tham khảo fwrite và nhận xét: Link

+0

Vì vậy, về cơ bản tôi cần kiểm tra độ dài của $ msg với strlen và đầu ra của fwrite và so sánh chúng. Nếu chúng không giống nhau thì hãy viết lại toàn bộ với các giá trị giống nhau không? Nhưng điều gì sẽ xảy ra nếu các mã thông báo không hợp lệ hoặc giống như vậy. Không phải là kịch bản bị mắc kẹt trong vòng lặp vô hạn này? –

+0

Một cái gì đó như thế này, vâng. Để tránh các vòng lặp vô hạn, bạn phải thiết lập số lần thử lại tối đa. Hơn nữa, tôi đề nghị bạn chờ một lát (ngủ?) Trước khi thử lại: nếu lỗi là do bộ đệm đầy, bạn sẽ cho nó cơ hội giải phóng một số không gian. Về retries, trong lý thuyết bạn nên thử lại: với toàn bộ thông điệp, nếu các byte là 0, hoặc với phần còn lại của tin nhắn nếu các byte là nhiều hơn 0, nhưng ít hơn tổng chiều dài. Tuy nhiên, có vẻ như trong một số trường hợp (tùy thuộc vào phía máy chủ), bạn phải gửi toàn bộ tin nhắn ngay cả trong trường hợp cuối cùng này. Hãy thử nó – LombaX

+0

Điều gì sẽ là một số lượng tốt của retries và thời gian ngủ? 5x và ngủ 3s? –

2

tôi không thể cung cấp cho bạn mã PHP thực tế, kể từ khi tôi không biết PHP, nhưng đây là logic bạn nên sử dụng (theo Apple):

Push Notification Throughput and Error Checking

If you're seeing throughput lower than 9,000 notifications per second, your server might benefit from improved error handling logic.

Here's how to check for errors when using the enhanced binary interface. Keep writing until a write fails. If the stream is ready for writing again, resend the notification and keep going. If the stream isn't ready for writing, see if the stream is available for reading.

If it is, read everything available from the stream. If you get zero bytes back, the connection was closed because of an error such as an invalid command byte or other parsing error. If you get six bytes back, that's an error response that you can check for the response code and the ID of the notification that caused the error. You'll need to send every notification following that one again.

Once everything has been sent, do one last check for an error response.

It can take a while for the dropped connection to make its way from APNs back to your server just because of normal latency. It's possible to send over 500 notifications before a write fails because of the connection being dropped. Around 1,700 notifications writes can fail just because the pipe is full, so just retry in that case once the stream is ready for writing again.

Now, here's where the tradeoffs get interesting. You can check for an error response after every write, and you'll catch the error right away. But this causes a huge increase in the time it takes to send a batch of notifications.

Device tokens should almost all be valid if you've captured them correctly and you're sending them to the correct environment. So it makes sense to optimize assuming failures will be rare. You'll get way better performance if you wait for write to fail or the batch to complete before checking for an error response, even counting the time to send the dropped notifications again.

None of this is really specific to APNs, it applies to most socket-level programming.

If your development tool of choice supports multiple threads or interprocess communication, you could have a thread or process waiting for an error response all the time and let the main sending thread or process know when it should give up and retry.

Điều này được lấy từ Lưu ý công nghệ của Apple: Troubleshooting Push Notifications.

EDIT

Tôi không biết làm thế nào bạn phát hiện trong PHP rằng ghi thất bại, nhưng khi nó làm, bạn nên cố gắng để viết thông báo thất bại một lần nữa, và nếu nó không thành công một lần nữa, cố gắng đọc phản hồi lỗi và đóng kết nối.

Nếu bạn quản lý để đọc phản hồi lỗi, bạn sẽ biết thông báo nào không thành công và bạn sẽ biết loại lỗi (lỗi có khả năng nhất là 8 - mã thông báo thiết bị không hợp lệ). Nếu sau khi viết 100 tin nhắn bạn nhận được phản hồi lỗi cho tin nhắn thứ 80, bạn phải gửi lại tin nhắn 81 đến 100, vì Apple chưa bao giờ nhận được chúng. Trong trường hợp của tôi (máy chủ Java), tôi không luôn luôn quản lý để đọc các phản ứng lỗi (đôi khi tôi nhận được một lỗi khi cố gắng đọc phản hồi từ socket). Trong trường hợp đó, tôi chỉ có thể chuyển sang gửi thông báo tiếp theo (và không có cách nào biết được thông báo nào thực sự được Apple nhận). Đó là lý do tại sao điều quan trọng là giữ cho cơ sở dữ liệu của bạn sạch mã thông báo không hợp lệ.

Dù sao, bạn không nên bị kẹt trong vòng lặp vô hạn, vì khi gặp lỗi sau khi gửi thông báo N, bạn sẽ không gửi lại các thông báo N này. Trừ khi bạn quản lý để đọc phản hồi lỗi từ Apple (trong trường hợp bạn biết chính xác những gì cần gửi lại), bạn sẽ chỉ gửi lại thông báo cuối cùng và ngay cả khi thông báo đó xảy ra là thông báo có mã thông báo không hợp lệ, có thể bạn sẽ nhận được lỗi tiếp theo sau khi gửi nhiều thông báo hơn (điều này thật không may, vì nó sẽ dễ dàng hơn trong việc phát hiện các mã thông báo không hợp lệ nếu bạn gặp lỗi ngay lập tức).

Nếu bạn giữ cho cơ sở dữ liệu của bạn sạch (ví dụ như chỉ lưu trữ mã thông báo thiết bị được Apple gửi đến Ứng dụng của bạn và tất cả chúng thuộc cùng một môi trường đẩy - hoặc hộp cát hoặc sản xuất), bạn sẽ không gặp phải bất kỳ mã thông báo thiết bị không hợp lệ.

Mã thông báo thiết bị do dịch vụ phản hồi trả về không phải là mã thông báo không hợp lệ. Chúng là các mã thông báo hợp lệ của các thiết bị đã gỡ cài đặt ứng dụng của bạn. Mã thông báo không hợp lệ chưa bao giờ hợp lệ đối với môi trường đẩy hiện tại và sẽ không bao giờ xảy ra. Cách duy nhất để xác định mã thông báo không hợp lệ là đọc các phản hồi lỗi từ Apple.

EDIT2:

tôi quên đề cập đến nó trước đây. Tôi gặp phải một vấn đề tương tự với bạn khi triển khai phía máy chủ thông báo đẩy trong Java. Tôi không thể nhận được tất cả các phản hồi lỗi do Apple trả lại một cách đáng tin cậy.

Tôi thấy rằng trong Java có một cách để vô hiệu hoá thuật toán của TCP Nagle, gây ra việc đệm nhiều thư trước khi gửi chúng trong một đợt cho Apple.Mặc dù Apple khuyến khích chúng tôi sử dụng thuật toán của Nagle (vì lý do hiệu suất), tôi thấy rằng khi tôi vô hiệu hóa nó và sau đó cố gắng đọc phản hồi từ Apple sau mỗi thư tôi gửi cho họ, tôi quản lý để nhận được 100% các phản hồi lỗi (I xác minh nó bằng cách viết một quá trình mô phỏng máy chủ APNS).

Bằng cách tắt thuật toán của Nagle và gửi từng thông báo, từ từ và không được đọc phản hồi lỗi sau mỗi thư, bạn có thể xác định tất cả mã thông báo không hợp lệ trong DB và xóa chúng. Một khi bạn biết DB của bạn được sạch sẽ, bạn có thể kích hoạt thuật toán của Nagle và tiếp tục gửi thông báo một cách nhanh chóng mà không cần phải đọc các phản hồi lỗi từ Apple. Sau đó, bất cứ khi nào bạn nhận được một lỗi trong khi viết một tin nhắn vào ổ cắm, bạn có thể chỉ cần tạo một ổ cắm mới và thử lại chỉ gửi tin nhắn cuối cùng.

+0

Cảm ơn thông tin. Tuy nhiên, vấn đề của tôi là tôi biết những gì đang xảy ra và tất cả các lý thuyết về chủ đề nhưng cũng không biết PHP. Có rất nhiều ví dụ về apns với php nhưng tất cả đều thất bại trong việc gửi một số lượng lớn các thông báo bởi vì không ai trong số họ kiểm tra lỗi. –

+0

Btw im không bị mắc kẹt trên php hoặc bất cứ điều gì nhưng chỉ nghĩ rằng nó rất dễ dàng để thực hiện. Nếu có một thay thế đơn giản (và miễn phí) có thể sửa lỗi này, tôi cũng sẽ rất vui. –

0

Googling tôi thấy một số điều quan tâm

http://rt.openssl.org/Ticket/Display.html?id=598&user=guest&pass=guest

Khi bình luận vá nói

first check if there is a SSL3_BUFFER still being written out. This will happen with non blocking IO

trả lời của Why am I getting "error:1409F07F:SSL routines:SSL3_WRITE_PENDING: bad write retry" error while attempting an SSL_write? nói:

SSL_Write returns with SSL_ERROR_WANT_WRITE or SSL_ERROR_WANT_READ, you have to repeat the call to SSL_write with the same parameters again, after the condition is satisfied.

Có lẽ bộ đệm ssl vẫn viết khi bạn cố gắng viết, bạn có thể c Nếu bộ đệm không viết, thử lại hoặc hạn chế bộ đệm có thể đủ.

Duplicates:

bổ sung (chỉnh sửa)

Trên tôi cố gắng để nói rằng bạn cần phải tìm ra một cách để xác định xem ổ cắm không viết khi bạn cố gắng viết lại và sau đó viết.

Nếu không có một cách để làm điều đó, hãy thử:

  • Vô hiệu hóa khối non-blocking
  • Rerty write

    while(!fwrite($fp, $msg)) { 
        usleep(400000); //400 msec 
    } 
    

    nếu là thành công, chỉ cần vô hiệu hóa các erors qua error_reporting không bao giờ sử dụng toán tử @.

  • Thiết stream_set_write_buffer() đến 0
+0

Liên kết thứ hai đó không thực sự hợp lý với tôi nhưng có vẻ dễ dàng. $ Đệm chính xác là gì? Một lần nữa tôi thất bại @ php –

+0

Khái niệm về bộ đệm là quá khó để giải thích cho tôi ngay bây giờ và với trình độ tiếng anh của tôi, có lẽ wikipedia hoặc một cái gì đó tương tự có thể giúp bạn tốt hơn với điều đó, nhìn vào các chỉnh sửa, tôi đọc về nó một chút. –

1

Giải pháp của tôi (cho câu hỏi tại bán cũ) đã được rằng tôi đã có một số phát triển môi trường thẻ APN trong cơ sở dữ liệu của tôi cố gắng để gửi đến một sản xuất-môi trường. Một khi tôi đã loại bỏ chúng khỏi cơ sở dữ liệu của tôi phần còn lại làm việc tốt. Thật không may, trong số 7000+ APN, tôi không chắc chắn mã thông báo nào là xấu nên tôi phải xóa tất cả chúng với hy vọng rằng mã thông báo mới sẽ được tạo khi người dùng mở lại ứng dụng. Càng xa càng tốt.

Apple sẽ tạm dừng mọi nỗ lực ngay lập tức khi gửi thông báo đẩy nếu thông báo xuất hiện trên mã thông báo APN sai.

Tôi có cùng một thông báo xuất hiện mà tôi chưa từng thấy trước đây (dưới đây) trên các ứng dụng khác nhau vì vậy tôi rất vui vì tôi đã có thể giải quyết nó.

Warning: fwrite() [function.fwrite]: SSL operation failed with code 1. OpenSSL Error messages: error:1409F07F:SSL routines:SSL3_WRITE_PENDING:bad write retry in PATH_TO_SCRIPT.php on line [NUMBER]

1

Giải pháp là:

$msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload; 
try { 
    $result = fwrite($fp, $msg, strlen($msg)); 
} catch (Exception $ex) { 
    sleep(1); //sleep for 5 seconds 
    $result = fwrite($fp, $msg, strlen($msg)); 
} 
Các vấn đề liên quan