Recently, there was a problem with the message push of the wireless doorbell that my part-time company has successfully crowdfunded. As a result, some users cannot receive the push message, which is really scary. After all, it is embarrassing that the backend service that I have provided by myself affects the company's reputation. , let me briefly introduce our needs: the company develops a wireless doorbell system. If someone presses the doorbell switch outside the door, the doorbell switch will emit a signal, and the receiving gateway in the house will make a sound when receiving the signal. A message will also be pushed to the user's mobile phone. Even if the mobile phone is remote, that is, the owner will know that someone has pressed the doorbell at home when he is not at home. The problem that needs to be solved in the background here is to build a provider for APNS push, because in order to push messages to Apple mobile phones, according to the mechanism designed by Apple, it must be pushed to Apple's PUSH server through its own server, and then pushed to the mobile phone. Each mobile phone corresponds to a deviceToken. The focus of my introduction here is not how to build this platform. There are already quite a lot of tutorials on the domestic Internet. For example, you can refer to: Teach you step by step how to make ios push
Most of the online tutorials are reasonable, but they operate on a mobile phone. What I mean is that they push messages to a mobile phone terminal at a time. In the products designed by our company, the same account can be used on multiple devices. Log in on a mobile phone (in theory, there are countless, because I have no restrictions in the background). The deviceToken corresponding to each mobile phone is different. In addition, the company's products are also designed with a sharing function, that is, the main user can share the device with other users. , and other users may also log in at the same time on different devices. If someone rings the doorbell, a message must be pushed to all logged in users, including shared users, which means that messages must be pushed to many mobile phone terminals in batches. Of course, the example I gave here is not that complicated. All the problems are abstracted into one question: Give you an array to store deviceToken, How does APNS push to multiple users in batches?
First we design a database to store the user's push token (deviceToken). For simplicity, this table has only two fields.
client_id | deviceToken |
1 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX |
Here I am using the CodeIgniter3 framework. We create a new Model to manage user deviceToken data.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
<?php //ios push token management class Apns_model extends CI_Model { public function __construct() {
}
* Create a new push token * create a apns If it already exists, update this deviceToken * $data is an array organized by controller */ public create( $data )
{
function delete $user_id ) {
array ( 'client_id'= >
$user_id
));
}
//Delete push token based on push token
function deletebytoken(
)
{
array(
'deviceToken'= >
$token ));
}
//Query a user’s iso push token
public functionget(
$client_id )
{
$sql = "SELECT deviceToken FROM `tb_apns` WHERE `client_id`='$client_id'" ;
$result = $this ->db->query( $sql );
if ( $result ->num_rows()>0)
{
return $result ->result_array();
}
else
{
return FALSE;
}
}
} |
In the first version of my backend, according to the online tutorials, messages were mostly pushed to one terminal at a time. I made a slight change and stored all the obtained deviceTokens in the $deviceTokens array. The parameter $message needs to be pushed. For messages, use a for loop to sequentially take out a deviceToken from the array to push, and then count, and return true if all pushes are successful. This method seemed to have no flaws, and the test was successful, so I went online directly. (Mainly because I didn’t expect that the company would suddenly release such a product, which would raise the status of the push function very high. I have always thought it was optional).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
function _send_apns( $deviceTokens , $message )
{
// Put your private key's passphrase here:密语
$passphrase = 'xxxxx' ;
////////////////////////////////////////////////////////////////////////////////
$ctx = stream_context_create();
stream_context_set_option( $ctx , 'ssl' , 'local_cert' , 'xxxx.pem' );
stream_context_set_option( $ctx , 'ssl' , 'passphrase' , $passphrase );
// Open a connection to the APNS server
$fp = stream_socket_client(
'ssl://gateway.push.apple.com:2195' , $err ,
$errstr , 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx );
if (! $fp )
exit ( "failed to connect: $ ERR $ ERRSTR" . PHP_EOL);
At re // Create the Payload Body
'aps' ] =
(
C n // Encode the Payload as Json
$payload = json_encode( $body );
$num = count
$deviceTokens );
// statistical number of successful sending
{
~
<div class="line number40 index39 alt1" style="height: 16px;">
<code class="php spaces"> Delete the spaces in deviceToken
//Build the binary notification
$deviceToken ) . pack( 'n',
strlen
(
$payload )) . $payload
;
//Send it to the server
$result = fwrite( $fp , $msg , strlen ( $msg ));
}
// Close the connection to the server
if ( $countOK == $num )
return TRUE;
else
return FALSE;
}
|
It was the above code that caused a series of problems in push later.
The first big problem is: By default, all push tokens are valid. In fact, if the user directly deletes the app or upgrades the app, it may cause the deviceToken in the background database not to be generated. Update, thereby invalidating the push token. But when someone rings the doorbell, the background will still treat it as a valid deviceToken and include it in $deviceTokens. How to clear the expired deviceToken is an issue that must be considered.
After checking the relevant information, I found that the APNS service has The Feedback Service. Domestic blogs have basically ignored this link. There are few information about it. It is more reliable to find an official website on Google. Here is a brief introduction to this service:
When performing APNS remote push, if the push fails due to the user uninstalling the app, the APNS server will record the deviceToken and add it to a list. You can query this list to obtain the expired push token from the database. Clearing these expired tokens will prevent them from being added to the push array during the next push. Connecting to this service is very simple and similar to the push project, except that the address is different. The development environment is <span style="font-family: 宋体, SimSun; font-size: 18px;">feedback.push.apple.com</span>
and the test environment is feedback.sandbox.push.apple. comports are all 2196. The data format returned by the APNS server is:
Timestamp |
A timestamp (as a four-byte |
Token length |
The length of the device token as a two-byte integer value in network order. |
Device token |
The device token in binary format. |
In order to perform this service, I wrote a CI framework controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
<?php defined( 'BASEPATH' ) OR exit ( 'No direct script access allowed' ); class Admin extends CI_Controller { public function __construct() { parent::__construct(); // 加载数据库 $this ->load->database(); $this ->load->model( 'apns_model' );
}
public function apnsfeedback()
{
$ctx = stream_context_create();
$passphrase = 'xxxxx' ;
stream_context_set_option( $ctx , 'ssl' , 'local_cert' , 'xxxxxxx.pem' );
stream_context_set_option( $ctx , 'ssl' , 'passphrase' , $passphrase );
$fp = stream_socket_client( 'ssl://feedback.push.apple.com:2196' , $error , $errorString , 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx );
if (! $fp ) {
echo "Failed to connect feedback server: $err $errstrn" ;
return ;
}
else {
echo "Connection to feedback server OKn" ;
echo "<br>" ;
}
while ( $devcon = fread ( $fp , 38))
{
$arr = unpack( "H*" , $devc
|