Home
home
🏡 홈
home

Ubuntu 22.04 Apache + PHP + Swoole 기반 WebSocket 환경 구축

분류
프로젝트
태그
Web
Server
Linux
C
Network
PHP
작성자
작성일
2025/04/14 00:40
실시간 채팅 기능을 PHP로 구현할 수 있을지에 대한 궁금증에서 출발하여,
WebSocket 서버 환경을 직접 구성하고 동작을 확인해본 내용을 정리하였습니다.

🎯 시작하게 된 배경입니다

최근 실시간 기능에 대한 관심이 생기면서, PHP로도 실시간 채팅을 구현할 수 있는지 알아보게 되었습니다.
조사 과정에서 PHP에서 WebSocket 서버를 실행할 수 있도록 도와주는 Swoole 확장을 알게 되었으며, 이를 통해 직접 환경을 구성하고 실행해보았습니다.
특히 Swoole은 C 언어로 개발된 PHP 확장으로, PHP로 작성된 기존의 WebSocket 라이브러리보다 훨씬 뛰어난 성능을 제공합니다.
네이티브 수준의 처리 속도와 낮은 메모리 소비 덕분에 실시간 통신 기능에 적합하다는 평가를 받고 있습니다.

🛠 Apache + PHP + 확장 모듈 설치입니다

다음 명령어를 통해 Apache 및 PHP, 그리고 각종 PHP 확장 모듈들을 설치하였습니다.
sudo apt update && sudo apt install -y \ apache2 \ php \ php-cli \ php-fpm \ php-dev \ php-pear \ libcurl4-openssl-dev \ php-curl \ php-mbstring \ php-xml \ php-mysql \ php-pgsql \ php-sqlite3 \ php-gd \ php-zip \ php-json \ php-bcmath \ php-intl \ php-readline \ php-memcached \ php-redis \ php-imagick \ php-xdebug \ composer \ unzip \ git \ curl
Bash
복사
php-dev는 Swoole을 컴파일하기 위해 필수적인 패키지입니다.
기타 확장들은 이후 개발 실험을 위한 환경을 고려하여 함께 설치하였습니다.

⚙️ Swoole 소스 설치 과정입니다

Swoole은 현재 apt로 직접 설치가 불가능하기 때문에, GitHub의 공식 저장소에서 소스를 내려받아 직접 빌드하였습니다.
git clone https://github.com/swoole/swoole-src.git cd swoole-src git fetch --tags git checkout v6.1.5 phpize ./configure make sudo make install
Bash
복사
phpize는 PHP 확장을 컴파일할 수 있는 환경을 설정하는 명령어입니다.
make install 이후에는 swoole.so 파일이 생성되며, PHP에서 사용할 수 있게 됩니다.

🔗 PHP에 swoole 확장을 등록합니다

CLI에서 swoole을 사용할 수 있도록 확장 설정 파일을 생성합니다.
echo "extension=swoole.so" | sudo tee /etc/php/8.1/cli/conf.d/20-swoole.ini
Bash
복사
WebSocket 서버는 CLI 환경에서 실행하므로, CLI 전용 설정 경로에 등록하였습니다.

✅ swoole 설치 여부 확인입니다

다음 명령어를 통해 swoole 확장이 정상적으로 등록되었는지 확인할 수 있습니다.
php -m | grep swoole
Bash
복사
출력 결과에 swoole이 포함되어 있으면 정상적으로 설치된 것입니다.

🧹 설치 후 소스 디렉토리 정리입니다

사용이 끝난 소스 디렉토리는 아래와 같이 삭제하였습니다.
cd .. rm -rf swoole-src/
Bash
복사

🌐 Apache 가상 호스트를 이용한 WebSocket 프록시 구성입니다

Swoole는 자체적으로 HTTP 서버를 구동하지만, 기존 Apache 웹 서버와 통합하기 위해 프록시 설정을 추가하였습니다.
이를 통해 도메인을 통해 Swoole WebSocket 서버에 접근할 수 있도록 구성하였습니다.

🔸 HTTP, HTTPS용 가상 호스트 설정입니다

<VirtualHost *:80> ServerAdmin example@domain.com ServerName example-domain.com RewriteEngine on ProxyRequests Off ProxyPreserveHost On ProxyPass /ws ws://localhost:9502/ ProxyPassReverse /ws ws://localhost:9502/ RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteRule /ws/(.*) ws://localhost:9501/$1 [P,L] ErrorLog "/var/log/apache2/example-domain.com-error.log" CustomLog "/var/log/apache2/example-domain.com-access.log" common </VirtualHost> <VirtualHost *:443> ServerAdmin example@domain.com ServerName example-domain.com SSLEngine on SSLProtocol all -SSLv2 -SSLv3 SSLCertificateKeyFile /home/example/ssl/current/example-domain_com_SHA256WITHRSA.key SSLCertificateFile /home/example/ssl/current/example-domain_com.crt SSLCACertificateFile /home/example/ssl/current/ChainCA/SectigoRSADomainValidationSecureServerCA.crt SSLCertificateChainFile /home/example/ssl/current/ChainCA/rsa-dv.chain-bundle.pem ErrorLog "/var/log/apache2/example-domain.com-ssl_error.log" CustomLog "/var/log/apache2/example-domain.com-ssl_access.log" common ProxyRequests Off ProxyPreserveHost On RequestHeader set X-Forwarded-Proto "https" RequestHeader set X-Forwarded-Ssl "on" RequestHeader set X-Forwarded-Port "443" <Location "/ws"> ProxyPass ws://localhost:9502/ timeout=300 connectiontimeout=300 keepalive=On ProxyPassReverse ws://localhost:9502/ </Location> RewriteEngine On RewriteCond %{HTTP:Upgrade} =websocket [NC] RewriteCond %{HTTP:Connection} =Upgrade [NC] RewriteRule /ws(.*) ws://localhost:9501$1 [P,L] </VirtualHost>
Bash
복사
이 설정을 통해, wss://example-domain.com/ws 경로로 WebSocket 서버에 접근할 수 있게 됩니다.
SSL 인증서도 함께 적용하여 보안 연결을 지원하고 있습니다.

💡 간단한 WebSocket 서버 예제입니다

Swoole을 이용하여 아래와 같이 기본적인 WebSocket 서버를 실행할 수 있습니다.
<?php $server = new Swoole\WebSocket\Server("0.0.0.0", 9502); $server->on("open", function ($server, $req) { echo "Connection opened: {$req->fd}\n"; }); $server->on("message", function ($server, $frame) { echo "Received message: {$frame->data}\n"; $server->push($frame->fd, "Server: {$frame->data}"); }); $server->on("close", function ($server, $fd) { echo "Connection closed: {$fd}\n"; }); $server->start();
PHP
복사
위 코드를 실행하면 9502 포트에서 WebSocket 서버가 동작하게 됩니다.
php websocket.php
Bash
복사

🌐 브라우저에서 WebSocket 연결 예제입니다

아래는 JavaScript를 이용한 클라이언트 연결 예제입니다.
const ws = new WebSocket("ws://localhost:9502"); ws.onopen = () => { ws.send("Hello from browser!"); }; ws.onmessage = e => { console.log("From server:", e.data); };
JavaScript
복사

⚡ Swoole의 Task 비동기 처리

Swoole은 멀티 스레드 워커 + 별도의 Task 워커 프로세스 구조를 지원하여,
네트워크 이벤트 처리(메인 워커)와 시간이 오래 걸리는 작업(Task 워커)을 분리할 수 있습니다.

📌 Task 처리 구조

1.
메인 워커 프로세스는 클라이언트의 요청을 빠르게 받고, 필요한 경우 Task 워커에 작업을 위임합니다.
2.
Task 워커 프로세스는 메인 워커로부터 받은 작업을 백그라운드에서 처리합니다.
3.
처리 완료 후 finish 이벤트를 통해 메인 워커에 결과를 전달할 수 있습니다.

🔹 예제: 메시지 저장 작업을 비동기로 처리하기

php 복사편집 <?php $server = new Swoole\WebSocket\Server("0.0.0.0", 9502); // Task 워커 수 지정 $server->set([ 'task_worker_num' => 4 // 병렬 처리 가능 ]); // 클라이언트 연결 시 $server->on("open", function ($server, $req) { echo "Connection opened: {$req->fd}\n"; }); // 메시지 수신 시 $server->on("message", function ($server, $frame) { echo "Received message: {$frame->data}\n"; // Task 워커에 비동기 작업 요청 (예: DB 저장) $server->task([ 'fd' => $frame->fd, 'message' => $frame->data ]); // 바로 응답 $server->push($frame->fd, "메시지를 받았습니다."); }); // Task 워커에서 처리 $server->on("task", function ($server, $task_id, $src_worker_id, $data) { echo "Task received: #{$task_id} from Worker #{$src_worker_id}\n"; // 여기서 무거운 작업 수행 (예: DB 저장, 외부 API 호출) file_put_contents("chat.log", "[{$data['fd']}] {$data['message']}\n", FILE_APPEND); // 결과 반환 return "저장 완료"; }); // Task 완료 후 메인 워커에 결과 전달 $server->on("finish", function ($server, $task_id, $data) { echo "Task #{$task_id} finished with result: {$data}\n"; }); $server->on("close", function ($server, $fd) { echo "Connection closed: {$fd}\n"; }); $server->start();
PHP
복사

🔹 Task를 사용해야 하는 경우

대량의 파일 I/O (로그 저장, 파일 업로드 처리 등)
데이터베이스에 대량 Insert / Update
이미지 변환, 동영상 인코딩 등 CPU 부하 작업
외부 API 호출 (슬랙, 이메일, SMS, 푸시 알림 등)
메시지 브로드캐스트 시 특정 로직 사전 처리

🔹 장점

메인 이벤트 루프 블로킹 방지
고성능 실시간 서비스 유지
Task 워커 수를 조절해 병렬 처리 확장 가능

📌 핵심 정리

task() 메서드로 작업을 비동기 Task 워커에 전달
on("task")에서 처리 로직 작성
on("finish")에서 메인 워커로 결과 회신
task_worker_num 옵션으로 동시 처리량 조절 가능

📝 마무리하며

Swoole을 활용하면 PHP에서도 충분히 WebSocket 기반의 실시간 통신 기능을 구현할 수 있다는 사실을 확인하였습니다.
기존의 요청-응답 기반 방식과는 다른 방식으로 서버를 구성할 수 있어 흥미로웠으며, 무엇보다도 C 언어로 개발된 확장인 만큼 성능과 안정성 측면에서 매우 우수하다는 인상을 받았습니다.
이제 이 환경을 기반으로 실시간 채팅 기능을 직접 구현해볼 계획이며, 앞으로는 Redis 연동이나 채팅방(Room) 기능, 메시지 브로드캐스트 등 다양한 기능을 실험해볼 예정입니다.