> PHP 프레임워크 > Swoole > PHP Swoole 긴 연결의 일반적인 문제 요약

PHP Swoole 긴 연결의 일반적인 문제 요약

藏色散人
풀어 주다: 2020-01-26 13:48:34
앞으로
2836명이 탐색했습니다.

PHP Swoole 긴 연결의 일반적인 문제 요약

연결 실패 문제

그 중 일반적인 Redis 오류는 다음과 같습니다.

구성 항목: timeouttimeout

报错信息:Error while reading line from the server

Redis可以配置如果客户端经过多少秒还不给Redis服务器发送数据,那么就会把连接close掉。

推荐学习: swoole教程

MySQL常见的报错:

配置项:wait_timeout & interactive_timeout

报错信息:has gone away

和Redis服务器一样,MySQL也会定时的去清理掉没用的连接。

如何解决

1、用的时候进行重连

2、定时发送心跳维持连接

用的时候进行重连

优点是简单,缺点是面临短连接的问题。

定时发送心跳维持连接

推荐。

如何维持长连接

tcp协议中实现的tcp_keepalive

 

操作系统底层提供了一组tcp的keepalive

오류 메시지: Error while 서버에서 라인 읽기

클라이언트가 몇 초 동안 Redis 서버에 데이터를 보내지 않으면 연결을 닫도록 Redis를 구성할 수 있습니다.

추천 학습: swoole 튜토리얼

MySQL 일반적인 오류:

구성 항목: wait_timeout & Interactive_timeout

오류 메시지: 가 사라졌습니다

Redis 서버와 마찬가지로 MySQL도 쓸모 없는 연결을 정기적으로 정리합니다.

해결 방법

1. 사용 중 재접속

2. 정기적으로 하트비트를 보내 연결을 유지합니다

사용 중 재접속

장점은 간단하지만 단점은 짧은 연결 문제.

연결을 유지하려면 정기적으로 하트비트를 보내세요

권장됩니다.

긴 연결을 유지하는 방법

tcp 프로토콜에 구현된 tcp_keepalive

운영 체제의 맨 아래 계층은 tcp의 keepalive 구성 세트를 제공합니다.

tcp_keepalive_time (integer; default: 7200; since Linux 2.2)
The number of seconds a connection needs to be idle before TCP
begins sending out keep-alive probes. Keep-alives are sent only
when the SO_KEEPALIVE socket option is enabled. The default
value is 7200 seconds (2 hours). An idle connection is
terminated after approximately an additional 11 minutes (9
probes an interval of 75 seconds apart) when keep-alive is
enabled.
 
Note that underlying connection tracking mechanisms and
application timeouts may be much shorter.
 
tcp_keepalive_intvl (integer; default: 75; since Linux 2.4)
The number of seconds between TCP keep-alive probes.
 
tcp_keepalive_probes (integer; default: 9; since Linux 2.2)
The maximum number of TCP keep-alive probes to send before
giving up and killing the connection if no response is obtained
from the other end.
8
로그인 후 복사

Swoole 하단 레이어 이러한 구성은 열려 있습니다. 예:

?php
 
$server = new \Swoole\Server('127.0.0.1', 6666, SWOOLE_PROCESS);
 
$server->set([
'worker_num' => 1,
'open_tcp_keepalive' => 1,
'tcp_keepidle' => 4, // 对应tcp_keepalive_time
'tcp_keepinterval' => 1, // 对应tcp_keepalive_intvl
'tcp_keepcount' => 5, // 对应tcp_keepalive_probes
]);
로그인 후 복사

그중:

'open_tcp_keepalive' => 1, // 总开关,用来开启tcp_keepalive
'tcp_keepidle' => 4, // 4s没有数据传输就进行检测
// 检测的策略如下:
'tcp_keepinterval' => 1, // 1s探测一次,即每隔1s给客户端发一个包(然后客户端可能会回一个ack的包,如果服务端收到了这个ack包,那么说明这个连接是活着的)
'tcp_keepcount' => 5, // 探测的次数,超过5次后客户端还没有回ack包,那么close此连接
로그인 후 복사

실제로 테스트해 보겠습니다. 서버 스크립트는 다음과 같습니다.

<?php
 
$server = new \Swoole\Server(&#39;127.0.0.1&#39;, 6666, SWOOLE_PROCESS);
 
$server->set([
&#39;worker_num&#39; => 1,
&#39;open_tcp_keepalive&#39; => 1, // 开启tcp_keepalive
&#39;tcp_keepidle&#39; => 4, // 4s没有数据传输就进行检测
&#39;tcp_keepinterval&#39; => 1, // 1s探测一次
&#39;tcp_keepcount&#39; => 5, // 探测的次数,超过5次后还没有回包close此连接
]);
 
$server->on(&#39;connect&#39;, function ($server, $fd) {
var_dump("Client: Connect $fd");
});
 
$server->on(&#39;receive&#39;, function ($server, $fd, $reactor_id, $data) {
var_dump($data);
});
 
$server->on(&#39;close&#39;, function ($server, $fd) {
var_dump("close fd $fd");
});
 
$server->start();
로그인 후 복사

서버를 시작합니다.

 ~/codeDir/phpCode/hyperf-skeleton # php server.php
로그인 후 복사

그런 다음 캡처합니다. tcpdump를 통한 패킷:

~/codeDir/phpCode/hyperf-skeleton # tcpdump -i lo port 6666

tcpdump: 자세한 출력이 억제됨, 전체 프로토콜 디코딩을 위해 -v 또는 -vv 사용

lo에서 듣기, link- 유형 EN10MB(이더넷), 캡처 크기 262144바이트

현재 lo의 포트 6666에서 데이터 패킷을 수신하고 있습니다.

그런 다음 클라이언트를 사용하여 연결합니다.

~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666
로그인 후 복사
이때 서버는 다음 메시지를 인쇄합니다.

~/codeDir/phpCode/hyperf-skeleton # php server.php
string(17) "Client: Connect 1"
로그인 후 복사

tcpdump의 출력 정보는 다음과 같습니다.

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
01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
01:48:44.229951 IP localhost.33933 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
01:48:44.229926 IP localhost.6666 > localhost.33933: Flags [.], ack 1, win 342, options [nop,nop,TS val 9834104 ecr 9833698], length 0
// 省略了其他的输出
로그인 후 복사

처음에서 이를 찾을 수 있습니다. 3방향 핸드셰이크 패키지가 인쇄됩니다:

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
로그인 후 복사

그런 다음 패킷 출력 없이 4초 동안 유지되었습니다.

그 후, 그룹은 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
 01:52:54.359377 IP localhost.43101 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 9859144 ecr 9855887], length 0
로그인 후 복사
로그인 후 복사

사실 이것이 우리가 구성한 전략입니다:

 &#39;tcp_keepinterval&#39; => 1, // 1s探测一次
 &#39;tcp_keepcount&#39; => 5, // 探测的次数,超过5次后还没有回包close此连接
로그인 후 복사
운영 체제의 맨 아래 계층이 자동으로 클라이언트에 ack를 반환하므로 다음과 같습니다. 5 감지 후 연결이 닫히지 않습니다. 운영 체제의 하위 계층은 다음과 같은 패킷 그룹을 지속적으로 보냅니다.

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

5번 테스트한 후 연결을 닫으려면 포트 6666에서 패킷을 비활성화할 수 있습니다.

 ~/codeDir/phpCode/hyperf-skeleton # iptables -A INPUT -p tcp --dport 6666 -j DROP
로그인 후 복사

이렇게 하면 들어오는 모든 패킷이 제거됩니다. 포트 6666에서 비활성화되면 당연히 서버는 클라이언트에서 보낸 ack 패킷을 수신할 수 없습니다.

그런 다음 서버는 5초 후에 close를 인쇄합니다(서버는 적극적으로 close 메소드를 호출하고 FIN 패킷을 클라이언트에 보냅니다):

 ~/codeDir/phpCode/hyperf-skeleton # php server.php
 string(17) "Client: Connect 1"
 string(10) "close fd 1"
로그인 후 복사

iptables 규칙을 복원합시다:

 ~/codeDir/phpCode # iptables -D INPUT -p tcp -m tcp --dport 6666 -j DROP
로그인 후 복사

즉, 우리가 설정한 규칙이 삭제되었습니다. .

하트비트 기능은 tcp_keepalive를 통해 구현됩니다. 장점은 코드를 작성하지 않고도 이 기능을 완료할 수 있고 전송되는 하트비트 패킷이 작다는 것입니다. 단점은 시스템의 네트워크 환경에 따라 다르다는 점입니다. 서버와 클라이언트 모두 이러한 기능을 구현하는지 확인해야 하며 클라이언트는 하트비트 패킷 전송에 협력해야 합니다.

또 다른 심각한 단점은 클라이언트와 서버가 직접 연결되지 않고 양말5 프록시와 같은 프록시를 통해 연결된 경우 애플리케이션 계층 패킷만 전달하고 하위 수준 TCP 감지 패킷은 전달하지 않는다는 것입니다. 그러면 하트비트 기능이 유효하지 않게 됩니다.

그래서 Swoole은 끊어진 연결을 감지하기 위한 구성 집합인 다른 솔루션을 제공합니다. R

 &#39;heartbeat_check_interval&#39; => 1, // 1s探测一次
 &#39;heartbeat_idle_time&#39; => 5, // 5s未发送数据包就close此连接
로그인 후 복사
EoSWOOLE에 의해 구현된 HeartBeat

를 테스트해 보겠습니다:

<?php
 
$server = new \Swoole\Server(&#39;127.0.0.1&#39;, 6666, SWOOLE_PROCESS);
 
$server->set([
&#39;worker_num&#39; => 1,
&#39;heartbeat_check_interval&#39; => 1, // 1s探测一次
&#39;heartbeat_idle_time&#39; => 5, // 5s未发送数据包就close此连接
]);
 
$server->on(&#39;connect&#39;, function ($server, $fd) {
var_dump("Client: Connect $fd");
});
 
$server->on(&#39;receive&#39;, function ($server, $fd, $reactor_id, $data) {
var_dump($data);
});
 
$server->on(&#39;close&#39;, function ($server, $fd) {
var_dump("close fd $fd");
});
 
$server->start();
로그인 후 복사
그리고 서버를 시작합니다:

~/coder/pHPCode/Hyperf-SKELETON # php server.php

그리고 TCPDUMP를 시작합니다:

Then 클라이언트 시작: 🎜🎜🎜 ~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666🎜🎜🎜 🎜🎜이 때 서버는 다음을 인쇄합니다: 🎜
 ~/codeDir/phpCode # 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
로그인 후 복사
🎜 🎜🎜그런 다음 tcpdump는 다음을 인쇄합니다: 🎜 🎜 🎜
~/codeDir/phpCode/hyperf-skeleton # php server.php
 string(17) "Client: Connect 1"
로그인 후 복사
🎜이것 3방향 핸드셰이크 정보입니다. 🎜🎜그런 다음 5초 후에 tcpdump는 다음을 인쇄합니다: 🎜🎜🎜 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], 길이 0🎜🎜 02:48:36.992172 IP localhost.42123 > localhost.6666: 플래그 [.], ack 2, win 342, 옵션 [nop,nop,TS val 10193790 ecr 1019378 9 ], 길이 0🎜🎜🎜 즉, 서버가 FIN 패킷을 보냈습니다. 클라이언트가 데이터를 보내지 않았기 때문에 Swoole은 연결을 종료했습니다. 🎜🎜그런 다음 서버는 다음을 인쇄합니다. 🎜
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
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
02:48:32.516156 IP localhost.42123 > localhost.6666: Flags [.], ack 1, win 342, options [nop,nop,TS val 10193342 ecr 10193342], length 0
로그인 후 복사
🎜 🎜🎜따라서 하트비트와 tcp keepalive에는 특정 차이점이 있습니다. Tcp keepalive에는 연결을 유지하는 기능이 있지만 하트비트는 데이터가 없는 연결만 감지한 다음 닫습니다. 서버 측에서만 구성할 수 있습니다. 이를 활성 상태로 유지해야 하는 경우 클라이언트에게 하트비트 전송에 협조하도록 요청할 수도 있습니다. 🎜

如果我们不想让服务端close掉连接,那么就得在应用层里面不断的发送数据包来进行保活,例如我在nc客户端里面不断的发送包:

~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666
ping
ping
ping
ping
ping
ping
ping
ping
ping
로그인 후 복사

我发送了9个ping包给服务器,tcpdump的输出如下:

// 省略了三次握手的包
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
02:57:53.697390 IP localhost.6666 > localhost.44195: Flags [.], ack 6, win 342, options [nop,nop,TS val 10249525 ecr 10249525], length 0
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
02:57:55.309576 IP localhost.6666 > localhost.44195: Flags [.], ack 11, win 342, options [nop,nop,TS val 10249686 ecr 10249686], length 0
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
02:57:58.395239 IP localhost.6666 > localhost.44195: Flags [.], ack 16, win 342, options [nop,nop,TS val 10249994 ecr 10249994], length 0
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
02:58:01.858126 IP localhost.6666 > localhost.44195: Flags [.], ack 21, win 342, options [nop,nop,TS val 10250341 ecr 10250341], length 0
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
02:58:04.132609 IP localhost.6666 > localhost.44195: Flags [.], ack 26, win 342, options [nop,nop,TS val 10250568 ecr 10250568], length 0
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
02:58:05.895728 IP localhost.6666 > localhost.44195: Flags [.], ack 31, win 342, options [nop,nop,TS val 10250744 ecr 10250744], length 0
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
02:58:07.150288 IP localhost.6666 > localhost.44195: Flags [.], ack 36, win 342, options [nop,nop,TS val 10250870 ecr 10250870], length 0
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
02:58:08.349156 IP localhost.6666 > localhost.44195: Flags [.], ack 41, win 342, options [nop,nop,TS val 10250990 ecr 10250990], length 0
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
02:58:09.906247 IP localhost.6666 > localhost.44195: Flags [.], ack 46, win 342, options [nop,nop,TS val 10251145 ecr 10251145], length 0
로그인 후 복사

有9组数据包的发送。(这里的Flags [P.]代表Push的含义)

此时服务器还没有close掉连接,实现了客户端保活连接的功能。然后我们停止发送ping,过了5秒后tcpdump就会输出一组:

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

服务端那边发送了FIN包,说明服务端close掉了连接。服务端的输出如下:

~/codeDir/phpCode/hyperf-skeleton # php server.php
string(17) "Client: Connect 1"
string(5) "ping
"
string(5) "ping
"
string(5) "ping
"
string(5) "ping
"
string(5) "ping
"
string(5) "ping
"
string(5) "ping
"
string(5) "ping
"
string(5) "ping
"
string(10) "close fd 1"
로그인 후 복사

然后我们在客户端那边ctrl + c来关闭连接:

~/codeDir/phpCode/hyperf-skeleton # nc 127.0.0.1 6666
ping
ping
ping
ping
ping
ping
ping
ping
ping
^Cpunt!

~/codeDir/phpCode/hyperf-skeleton #
로그인 후 복사

此时,tcpdump的输出如下:

 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
 03:03:02.257734 IP localhost.6666 > localhost.44195: Flags [R], seq 2678621620, win 0, length 0
로그인 후 복사

应用层心跳

1、制定ping/pong协议(mysql等自带ping协议)

2、客户端灵活的发送ping心跳包

3、服务端OnRecive检查可用性回复pong

例如:

$server->on(&#39;receive&#39;, function (\Swoole\Server $server, $fd, $reactor_id, $data)
{
if ($data == &#39;ping&#39;)
{
checkDB();
checkServiceA();
checkRedis();
$server->send(&#39;pong&#39;);
}
});
로그인 후 복사

 

结论

1、tcp的keepalive最简单,但是有兼容性问题,不够灵活

2、swoole提供的keepalive最实用,但是需要客户端配合,复杂度适中

3、应用层的keepalive最灵活但是最麻烦

위 내용은 PHP Swoole 긴 연결의 일반적인 문제 요약의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:cnblogs.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿