PHP Swoole long connection FAQ

Connection failure
Example
Among them, the common errors reported by Redis are:

Configuration item: timeout
Error while reading line from the server
Redis can configure that if the client does not send data to the redis server after several seconds, the connection will be close d.

Common MySQL errors:

Configuration item: wait & Interactive timeout
Error message: has gone away
Like Redis server, MySQL will regularly clean up useless connections.

How to solve
1. Reconnection during use

2. Send heartbeat regularly to maintain connection

Reconnection during use
The advantage is simple, and the disadvantage is the problem of short connection.

Send heartbeat regularly to maintain connection
Recommend.

How to maintain long connections

tcp_keepalive implemented in tcp protocol

 

The underlying operating system provides a set of tcp keepalive configurations:

 1 tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
 2 The number of seconds a connection needs to be idle before TCP
 3 begins sending out keep-alive probes. Keep-alives are sent only
 4 when the SO_KEEPALIVE socket option is enabled. The default
 5 value is 7200 seconds (2 hours). An idle connection is
 6 terminated after approximately an additional 11 minutes (9
 7 probes an interval of 75 seconds apart) when keep-alive is
 8 enabled.
 9  
10 Note that underlying connection tracking mechanisms and
11 application timeouts may be much shorter.
12  
13 tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
14 The number of seconds between TCP keep-alive probes.
15  
16 tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
17 The maximum number of TCP keep-alive probes to send before
18 giving up and killing the connection if no response is obtained
19 from the other end.
20 8

The bottom layer of Swoole has opened up these configurations, for example:

 1 ?php
 2  
 3 $server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS);
 4  
 5 $server->set([
 6 'worker_num' => 1,
 7 'open_tcp_keepalive' => 1,
 8 'tcp_keepidle' => 4, // Corresponding tcp_keepalive_time
 9 'tcp_keepinterval' => 1, // Corresponding tcp_keepalive_intvl
10 'tcp_keepcount' => 5, // Corresponding tcp_keepalive_probes
11 ]);

Among them:

1 'open_tcp_keepalive' => 1, // Main switch for opening tcp_keepalive
2 'tcp_keepidle' => 4, // 4s Test without data transmission
3 // The detection strategy is as follows:
4 'tcp_keepinterval' => 1, // 1s Detect once, i.e. every 1 s Send a package to the client (the client may then return a ack If the server receives this ack The connection is alive.)
5 'tcp_keepcount' => 5, // The number of probes. After more than 5 times, the client has not returned ack Bag, then close This connection

 

Let's have a real-world test experience. The server script is as follows:

 1 <?php
 2  
 3 $server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS);
 4  
 5 $server->set([
 6 'worker_num' => 1,
 7 'open_tcp_keepalive' => 1, // open tcp_keepalive
 8 'tcp_keepidle' => 4, // 4s Test without data transmission
 9 'tcp_keepinterval' => 1, // 1s Detect once
10 'tcp_keepcount' => 5, // Number of detections, no packet returned after more than 5 detections close This connection
11 ]);
12  
13 $server->on('connect', function ($server, $fd) {
14 var_dump("Client: Connect $fd");
15 });
16  
17 $server->on('receive', function ($server, $fd, $reactor_id, $data) {
18 var_dump($data);
19 });
20  
21 $server->on('close', function ($server, $fd) {
22 var_dump("close fd $fd");
23 });
24  
25 $server->start();

Let's start this server:

1 ~/codeDir/phpCode/hyperf-skeleton # php server.php

Then through tcpdump to grab packets:

~/codeDir/phpCode/hyperf-skeleton # tcpdump -i lo port 6666
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes

We're listening to port 6666 on the lo at this time.

Then we use the client to connect it:

1 ~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

At this time, the server will print out a message:

~/codeDir/phpCode/hyperf-skeleton # php server.php
string(17) "Client: Connect 1"

The output information of tcpdump is as follows:

1 01:48:40.178439 IP localhost.33933 > localhost.6666: Flags [S], seq 43162537, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 0,nop,wscale 7], length 0
2 01:48:40.178484 IP localhost.6666 > localhost.33933: Flags [S.], seq 1327460565, ack 43162538, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 9833698,nop,wscale 7], length 0
3 01:48:40.178519 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9833698 ecr 9833698], length 0
4 01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
5 01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
6 01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
7 01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
8 01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
9 // Other outputs are omitted

We will find that at the beginning, we will print the package with three handshakes:

01:48:40.178439 IP localhost.33933 > localhost.6666: Flags [S], seq 43162537, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 0,nop,wscale 7], length 0
01:48:40.178484 IP localhost.6666 > localhost.33933: Flags [S.], seq 1327460565, ack 43162538, win 43690, options [mss 65495,sackOK,TS val 9833698 ecr 9833698,nop,wscale 7], length 0
01:48:40.178519 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9833698 ecr 9833698], length 0

Then, after 4 seconds, there is no output of any package.

After that, a group will be printed every 1s or so:

1 01:52:54.359341 IP localhost.6666 > localhost.43101: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9858736], length 0
2 01:52:54.359377 IP localhost.43101 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9855887], length 0

In fact, this is our configuration strategy:

1 'tcp_keepinterval' => 1, // 1s Detect once
2 'tcp_keepcount' => 5, // Number of detections, no packet returned after more than 5 detections close This connection

Because the underlying of our operating system will automatically back ack to the client, this connection will not be closed after 5 probes. The underlying operating system will continuously send such a group of packets:

1 01:52:54.359341 IP localhost.6666 > localhost.43101: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9858736], length 0
2 01:52:54.359377 IP localhost.43101 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9855887], length 0

If we want to close this connection after testing 5 probes, we can disable the package of port 6666:

1 ~/codeDir/phpCode/hyperf-skeleton # iptables -A INPUT -p tcp --dport 6666 -j DROP

This will disable all packets coming in from port 6666. Naturally, the server will not receive the ack packets sent from the client side.

Then the server will print the close after 5 seconds (the server actively calls the close method and sends the FIN package to the client):

1 ~/codeDir/phpCode/hyperf-skeleton # php server.php
2 string(17) "Client: Connect 1"
3 string(10) "close fd 1"

Let's restore the iptables rule:

1 ~/codeDir/phpCode # iptables -D INPUT -p tcp -m tcp --dport 6666 -j DROP

That is, the rules we set are deleted.

The function of heartbeat is realized by tcp ﹣ keepalive, which has the advantage of simplicity. It can be completed without writing code, and the heartbeat packet sent is small. The disadvantage is that it depends on the network environment of the system, so it is necessary to ensure that the server and the client have realized such functions, and the client needs to cooperate in sending heartbeat packets. A more serious disadvantage is that if the client and the server are not directly connected, but connected through a proxy, for example, socks5 proxy, which only forwards the packets in the application layer, and does not forward the more underlying tcp detection packets, the heartbeat function will fail.

So, Swoole provides other solutions, a set of configurations to detect dead connections.

1 'heartbeat_check_interval' => 1, // 1s Detect once
2 'heartbeat_idle_time' => 5, // 5s No packets sent close This connection

heartbeat implemented by swoole

Let's test it:

 1 <?php
 2  
 3 $server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS);
 4  
 5 $server->set([
 6 'worker_num' => 1,
 7 'heartbeat_check_interval' => 1, // 1s Detect once
 8 'heartbeat_idle_time' => 5, // 5s No packets sent close This connection
 9 ]);
10  
11 $server->on('connect', function ($server, $fd) {
12 var_dump("Client: Connect $fd");
13 });
14  
15 $server->on('receive', function ($server, $fd, $reactor_id, $data) {
16 var_dump($data);
17 });
18  
19 $server->on('close', function ($server, $fd) {
20 var_dump("close fd $fd");
21 });
22  
23 $server->start();

Then start the server:

1 ~/codeDir/phpCode/hyperf-skeleton # php server.php

Then start tcpdump:

 

1 ~/codeDir/phpCode # tcpdump -i lo port 6666
2 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
3 listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes

 


Then start the client:

1 ~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666

 


At this time, the server Prints:

1 ~/codeDir/phpCode/hyperf-skeleton # php server.php
2 string(17) "Client: Connect 1"

 

Then tcpdump Prints:

 

1 02:48:32.516093 IP localhost.42123 > localhost.6666: Flags [S], seq 1088388248, win 43690, options [mss 65495,sackOK,TS val 10193342 ecr 0,nop,wscale 7], length 0
2 02:48:32.516133 IP localhost.6666 > localhost.42123: Flags [S.], seq 80508236, ack 1088388249, win 43690, options [mss 65495,sackOK,TS val 10193342 ecr 10193342,nop,wscale 7], length 0
3 02:48:32.516156 IP localhost.42123 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 10193342 ecr 10193342], length 0

 

This is the three handshake message.

After 5s, tcpdump will print:

1 02:48:36.985027 IP localhost.6666 > localhost.42123: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 10193789 ecr 10193342], length 0
2 02:48:36.992172 IP localhost.42123 > localhost.6666: Flags [.], ack 2, win 342, options [nop,nop,TS val 10193790 ecr 10193789], length 0

In other words, the server sends FIN packets. Because the client did not send data, Swoole closed the connection.

Then the server will print:

1 ~/codeDir/phpCode/hyperf-skeleton # php server.php
2 string(17) "Client: Connect 1"
3 string(10) "close fd 1"

 

Therefore, heartbeat and tcp keepalive have some differences. tcp keepalive has the function of keeping alive connection, but heartbeat storage is to detect the connection without data, and then close it. It can only be configured on the server side. If it needs to keep alive, it can also let the client side cooperate to send heartbeat.

If we don't want the server to close the connection, we have to send packets continuously in the application layer to keep alive. For example, I send packets continuously in the nc client:

 1 ~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666
 2 ping
 3 ping
 4 ping
 5 ping
 6 ping
 7 ping
 8 ping
 9 ping
10 ping

 

I sent nine ping packets to the server. The output of tcpdump is as follows:

 1 // Package with three handshakes omitted
 2 02:57:53.697363 IP localhost.44195 > localhost.6666: Flags [P.], seq 1:6, ack 1, win 342, options [nop,nop,TS val 10249525 ecr 10249307], length 5
 3 02:57:53.697390 IP localhost.6666 > localhost.44195: Flags [.], ack 6, win 342, options [nop,nop,TS val 10249525 ecr 10249525], length 0
 4 02:57:55.309532 IP localhost.44195 > localhost.6666: Flags [P.], seq 6:11, ack 1, win 342, options [nop,nop,TS val 10249686 ecr 10249525], length 5
 5 02:57:55.309576 IP localhost.6666 > localhost.44195: Flags [.], ack 11, win 342, options [nop,nop,TS val 10249686 ecr 10249686], length 0
 6 02:57:58.395206 IP localhost.44195 > localhost.6666: Flags [P.], seq 11:16, ack 1, win 342, options [nop,nop,TS val 10249994 ecr 10249686], length 5
 7 02:57:58.395239 IP localhost.6666 > localhost.44195: Flags [.], ack 16, win 342, options [nop,nop,TS val 10249994 ecr 10249994], length 0
 8 02:58:01.858094 IP localhost.44195 > localhost.6666: Flags [P.], seq 16:21, ack 1, win 342, options [nop,nop,TS val 10250341 ecr 10249994], length 5
 9 02:58:01.858126 IP localhost.6666 > localhost.44195: Flags [.], ack 21, win 342, options [nop,nop,TS val 10250341 ecr 10250341], length 0
10 02:58:04.132584 IP localhost.44195 > localhost.6666: Flags [P.], seq 21:26, ack 1, win 342, options [nop,nop,TS val 10250568 ecr 10250341], length 5
11 02:58:04.132609 IP localhost.6666 > localhost.44195: Flags [.], ack 26, win 342, options [nop,nop,TS val 10250568 ecr 10250568], length 0
12 02:58:05.895704 IP localhost.44195 > localhost.6666: Flags [P.], seq 26:31, ack 1, win 342, options [nop,nop,TS val 10250744 ecr 10250568], length 5
13 02:58:05.895728 IP localhost.6666 > localhost.44195: Flags [.], ack 31, win 342, options [nop,nop,TS val 10250744 ecr 10250744], length 0
14 02:58:07.150265 IP localhost.44195 > localhost.6666: Flags [P.], seq 31:36, ack 1, win 342, options [nop,nop,TS val 10250870 ecr 10250744], length 5
15 02:58:07.150288 IP localhost.6666 > localhost.44195: Flags [.], ack 36, win 342, options [nop,nop,TS val 10250870 ecr 10250870], length 0
16 02:58:08.349124 IP localhost.44195 > localhost.6666: Flags [P.], seq 36:41, ack 1, win 342, options [nop,nop,TS val 10250990 ecr 10250870], length 5
17 02:58:08.349156 IP localhost.6666 > localhost.44195: Flags [.], ack 41, win 342, options [nop,nop,TS val 10250990 ecr 10250990], length 0
18 02:58:09.906223 IP localhost.44195 > localhost.6666: Flags [P.], seq 41:46, ack 1, win 342, options [nop,nop,TS val 10251145 ecr 10250990], length 5
19 02:58:09.906247 IP localhost.6666 > localhost.44195: Flags [.], ack 46, win 342, options [nop,nop,TS val 10251145 ecr 10251145], length 0

 

There are 9 groups of packets sent. (Flags [P.] here means Push)

At this time, the server has not yet close d the connection, realizing the function of the client to keep the connection alive. Then we stop sending ping. After five seconds, tcpdump will output a set of:

02:58:14.811761 IP localhost.6666 > localhost.44195: Flags [F.], seq 1, ack 46, win 342, options [nop,nop,TS val 10251636 ecr 10251145], length 0
02:58:14.816420 IP localhost.44195 > localhost.6666: Flags [.], ack 2, win 342, options [nop,nop,TS val 10251637 ecr 10251636], length 0
The server sent the FIN packet, indicating that the server lost the connection. The output of the server is as follows:

 1 ~/codeDir/phpCode/hyperf-skeleton # php server.php
 2 string(17) "Client: Connect 1"
 3 string(5) "ping
 4 "
 5 string(5) "ping
 6 "
 7 string(5) "ping
 8 "
 9 string(5) "ping
10 "
11 string(5) "ping
12 "
13 string(5) "ping
14 "
15 string(5) "ping
16 "
17 string(5) "ping
18 "
19 string(5) "ping
20 "
21 string(10) "close fd 1"

 


Then we close the connection by ctrl + c on the client side:

 1 ~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666
 2 ping
 3 ping
 4 ping
 5 ping
 6 ping
 7 ping
 8 ping
 9 ping
10 ping
11 ^Cpunt!
12 
13 ~/codeDir/phpCode/hyperf-skeleton #

 

At this time, the output of tcpdump is as follows:

1 03:03:02.257667 IP localhost.44195 > localhost.6666: Flags [F.], seq 46, ack 2, win 342, options [nop,nop,TS val 10280414 ecr 10251636], length 0
2 03:03:02.257734 IP localhost.6666 > localhost.44195: Flags [R], seq 2678621620, win 0, length 0

 

Application layer heartbeat

1. Develop ping/pong protocol (mysql and other self-contained ping protocols)
2. The client can send ping heartbeat packets flexibly
3. Server onReceive check availability reply
For example:

 1 $server->on('receive', function (\Swoole\Server $server, $fd, $reactor_id, $data)
 2 {
 3 if ($data == 'ping')
 4 {
 5 checkDB();
 6 checkServiceA();
 7 checkRedis();
 8 $server->send('pong');
 9 }
10 });

 

conclusion
1. tcp keepalive is the simplest, but it has compatibility problems and is not flexible enough
2. The keepalive provided by swoole is the most practical, but it needs the cooperation of the client with moderate complexity
3. keepalive in the application layer is the most flexible but troublesome

Keywords: PHP Redis MySQL Linux

Added by Doom87 on Mon, 11 Nov 2019 11:47:31 +0200