PHP를 사용하여 소켓 기반 서버 애플리케이션을 구축 할 때 Socket_accept ()는 청취 소켓의 연결을 수용하는 매우 핵심 기능입니다. 그러나 동시성이 높은 환경에서 개발자는 종종 socket_accept () 차단, 응답 지연 및 서버 충돌을 겪습니다. 이 기사는 기본 메커니즘에서 시작하여 Socket_accept ()가 높은 동시성 시나리오에서 병목 현상이되는 이유를 자세히 분석하고 PHP 구현을 기반으로 최적화 제안을 제공합니다.
PHP에서 TCP 서버를 만드는 일반적인 프로세스는 다음과 같습니다.
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '0.0.0.0', 8080);
socket_listen($socket);
while (true) {
$client = socket_accept($socket);
// 후속 조치 $client 처리 및 쓰기 처리
}
Socket_accept () 의 목적은 청취 소켓에서 핸드 셰이크를 완료 한 클라이언트 연결을 추출하고 데이터 통신을 위해 새 소켓을 반환하는 것입니다.
기본적으로 Socket_accept () 가 차단됩니다. 클라이언트 연결이 없으면 연결이 올 때까지 기다립니다. 이 방법은 낮은 동시성 시나리오에서 잘 작동 할 수 있지만 다음과 같은 문제는 동시에 노출됩니다.
socket_accept () 가 차단되므로 연결을 기다리는 동안 다른 로직을 실행할 수 없습니다. 높은 동시 시나리오에서는 수천 개의 연결 요청이 동시에 도착한다고 가정하면 기본 루프에서 한 번에 하나의 연결 만 처리 할 수 있습니다. 나머지 연결은 대기 할 수 있습니다. 이 "단일 스레드 + 차단"처리 방법은 높은 동시성에서 병목 현상이 될 수 있습니다.
TCP 프로토콜은 서버 측의 백 로그를 유지하여 세 가지 핸드 셰이크를 완료했지만 응용 프로그램 계층에서 허용되지 않은 연결을 저장합니다. 대기열이 가득 차면 운영 체제에 의해 새 연결이 직접 거부됩니다. php socket_listen () 의 세 번째 매개 변수는 백 로그 큐 크기이며 기본적으로 128 일 수 있습니다.
socket_listen($socket, 1024); // 증가하다 backlog 크기
백 로그가 크게 설정 되더라도 socket_accept ()가 시간에 연결을 처리 할 수 없으면 큐가 빠르게 채워집니다.
PHP의 CLI 모드는 본질적으로 동기식, 차단, 단일 스레드이며 비동기 I/O 및 동시성 기능이 부족합니다. 즉, nginx 또는 node.js와 같은 이벤트 중심 모델을 통해 여러 연결을 동시에 처리 할 수 없습니다. PCNTL_FORK ()는 자식 프로세스를 만드는 데 사용될 수 있지만 많은 리소스를 소비하고 관리가 복잡합니다.
또한 PHP에는 고성능 이벤트 루프 메커니즘 (예 : epoll, kqueue)이 내장되어 있지 않으므로 동시 동시 시나리오에서 확장 성을 추가로 제한합니다.
개선 방법은 socket_select () 와 함께 비 블로킹 I/O 멀티플렉싱을 구현하는 것입니다.
$clients = [];
while (true) {
$read = array_merge([$socket], $clients);
$write = $except = null;
if (socket_select($read, $write, $except, null) > 0) {
if (in_array($socket, $read)) {
$newClient = socket_accept($socket);
if ($newClient) {
$clients[] = $newClient;
socket_getpeername($newClient, $ip, $port);
echo "새로운 연결 $ip:$port\n";
}
}
foreach ($clients as $key => $client) {
$data = socket_read($client, 1024, PHP_NORMAL_READ);
if ($data === false || trim($data) == '') {
unset($clients[$key]);
socket_close($client);
continue;
}
socket_write($client, "You said: $data");
}
}
}
이 방법은 Socket_accept () 의 성능 병목 현상을 어느 정도까지 완화 할 수 있지만 여전히 단일 스레드 Select () 모델을 기반으로합니다. 연결이 너무 많으면 select () 자체가 비효율적입니다. Epoll 과 같은보다 효율적인 모델은 PHP에 의해 기본적으로 지원되지 않습니다.
코드 레벨 최적화 외에도 배포 계층에서 일부 개선이 이루어질 수 있습니다.
리버스 프록시 사용 : Nginx 또는 Caddy를 리버스 프록시 서버로 사용하여 클라이언트 연결로드 밸런싱을 여러 PHP 인스턴스에 배포합니다.
PHP 확장 또는 SWOOLE 사용 : 비즈니스가 동시성이 높은 요구 사항이 매우 높으면 Swoole 과 같은 PHP 확장을 사용하는 것을 고려할 수 있습니다. Swoole 기반 비동기 서비스 모델을 구현하는 Swoole 및 다중 프로세스 처리를 지원합니다.
다중 프로세스 또는 데몬 모델 : PCNTL_FORK ()를 사용하여 간단한 프로세스 풀 모델을 구축하고 각 어린이 처리는 독립적으로 Socket_Accept ()를 호출하여 연결을 수락합니다.
Socket_accept ()가 높은 동시성에서 병목 현상이되는 근본적인 이유는 차단 호출 메커니즘과 PHP 단일 스레드 실행 모델의 한계 때문입니다. Socket_select () 를 통해 어느 정도의 동시 처리를 달성 할 수 있지만 장기적으로 비동기 I/O (예 : Swoole)를 지원하는 확장을 사용하거나 프로세스 모델을 사용하거나 외부 서비스를 사용하여 스트레스를 완화하는 것이 더 실행 가능한 솔루션입니다.
동시 동시 시나리오에서 소켓의 사용은 코드 최적화의 문제 일뿐 만 아니라 아키텍처 설계 및 실행 모델에서 포괄적 인 과제이기도합니다. 동시성 긴 연결 서비스를 구축하기 위해 PHP를 선택할 때는 자연스러운 한계와 확장 성을 평가하는 것이 중요합니다.