PHP를 이용한 다중 연결 소켓 통신 (3)


 

이문서의 배포는 자유로우나 최소한 제작자의 정보는 제외하지 않고 배포해 주세요.

문서가 존재하는 모든곳에 답변을 드릴수 없으므로 질문은 홈페이지(http://www.jinoos.com)에서만 받습니다.

이진우

 


1. 소개

이번강좌에는 fork를 이용해서 새로운 프로세스를 생성하여 생성된 자식 서버프로세스가 클라이언트를 담당하는 형태를 구연해 보겠습니다.

PHP에서 fork함수로는 Process Control 함수의 pcntl_fork() 함수가 있습니다. Process Control 함수는 기본함수가 아니기 때문에 컴파일시 옵셥으로 추가시켜야 합니다.


2. pcntl_fork() 함수

 

 

int pcntl_fork ( )

함수 호출후 리턴값에 0이면 자식 프로세스이며 >0 이면 부모 프로세스로 자식 프로세스의 PID번호를 리턴 받습니다. error발생시에는 -1 값을 가집니다.

포크 함수는 포크 함수를 실행한 프로세스와 동일한 자식 프로세스를 생성합니다. 동일한 자식 프로세스라는 의미는 프로세스 계보상의 깊이만 다를뿐 동작은 똑같은 쌍둥이를 만드는 것 입니다.

자식 프로세스는 부모 프로세스의 메모리를 복사해서 클론을 만들고 리소스(파일 지시자, DB 커넥션, 소켓 커넥션 등)은 공유합니다.

간단하게 pcntl_fork() 코드를 살펴 보겠습니다.

<?php
$i = 0;
$pid = pcntl_fork();

// error
if($pid == -1)
{
echo "fork error";

// 부모 프로세스
}elseif($pid > 0)
{
for(;$i<10;$i++)
{
echo "Parent Process \$i : $i\n";
}

// 자식 프로세스
}elseif($pid == 0)
{
for(;$i<10;$i+=2)
{
echo "Child Process \$i : $i\n";
}
}
?>
부모 프로세스는 $i 값이 1씩, 자식 프로세스는 $i 값이 2씩 증가하는 프로그램 입니다. 결과는 각자 해보시기 바랍니다.

 


3. PHP 컴파일 하기

첫번째 강좌(PHP를 이용한 다중 연결 소켓 통신 (1)) 에서 소켓 함수를 사용하기 위해 --enable-sockets 옵션을 주어 컴파일 하였습니다.

오늘은 소켓 함수와 Process Control 함수를 추가시켜 컴파일 해보겠습니다.

 

#] tar -zxvf php-4.3.1.tar.gz
#] cd php-4.3.1
#] ./configure --enable-sockets --enable-pcntl
#] make

역시 php 실행파일이 생성됩니다.


4. 프로그램 작성

오늘 작성할 서버와 클라이언트의 구조는 아래와 같습니다.

 

┌───────┐ ┌───┐
┌─(Fork)─┤Child Process ├─(socket)─┤Client│
│ └───────┘ └───┘
┌───────┐ │ ┌───────┐ ┌───┐
│Master Process├─┼─(Fork)─┤Child Process ├─(socket)─┤Client│
└───────┘ │ └───────┘ └───┘
│ ┌───────┐ ┌───┐
└─(Fork)─┤Child Process ├─(socket)─┤Client│
└───────┘ └───┘
좀더 단순화 되고 직관적으로 표현되었군요.

Child Process 하나가 Client 하나를 독립적으로 마크하는 구조입니다.

연결이 끊어진 Child Process는 바로 소멸됩니다. 새로운 클라이언트가 참여하면 바로 Master Process는 pcntl_fork함수를 이용해서 Child Process를 생성하죠.


4.1. 서버 만들기

서버의 구조를 간단히 살펴보면

소켓생성
소켓바인트및 리슨
while(새로운연결수락)
{
포크
if(자식프로세스)
{
while(메시지수신)
{
메시지 처리
if(quit메시지)
{
소켓닫기
종료
}
}
}
}
구조 입니다. 메시지 처리 부분은 지난 강좌(PHP를 이용한 다중 연결 소켓 통신 (2))의 메시지 처리 부분과 동일하며 select처리 대신 fork를 이용한 처리 입니다.

 

#!/usr/local/bin/php -q
<?php
set_time_limit(0);

define("_IP", "111.222.333.12");
define("_PORT", "65000");

$sSock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_bind($sSock, _IP, _PORT);
socket_listen($sSock);

pcntl_signal(SIGCHLD, SIG_IGN);

while($sock = socket_accept($sSock))
{
socket_getpeername($sock, $sockIp, $sockPort);
msg("client connect : ".$sockIp.":".$sockPort."\n");

$pid = pcntl_fork();
msg("fork\n");
if($pid == -1)
{
msg("fork failed\n");
exit;
// 자식 프로세스 일때
}if($pid == 0)
{
while(1)
{
$buf = socket_read($sock, 4096);

// 접속 종료
if(!$buf)
{
msg("client connection broken : ".$sockIp.":".$sockPort."\n");
exit;
}
// 메시지 수신 이벤트
else
{
msg("recive data : ".$buf."\n");
$cmd = substr($buf, 0, 4);
switch($cmd)
{
// 시간전송
case "time":
msg("client(".$sockPort.") time data request\n");
socket_write($sock, date("Y/m/d H:i:s"));
break;

// 종료
case "quit":
msg("client(".$sockPort.") quit request\n");
socket_write($sock, "quit");
socket_close($sock);
exit;
break;
default:
msg("client(".$sockPort.") invalid command $cmd\n");
break;
}
}
}
}
}

function msg($msg)
{
echo "SERVER >> ".$msg;
}
?>

역시 server.php로 저장하고 실행권한을 줍니다.


4.2. 클라이언트 만들기

클라이언트는 지난 강좌(PHP를 이용한 다중 연결 소켓 통신 (2))에서 사용한 클라이언트 프로그램을 수정없이 그대로 사용합니다.


4.3. 실행하기

server.php를 실행 후 client.php를 3번 실행하고 프로세스와 프로세스 트리를 확인해보겠습니다.

server.php 실행 화면

#] ./server.php
SERVER >> client connect : 111.222.333.12:38276 -- (1)
SERVER >> fork
SERVER >> fork
SERVER >> recive data : time
SERVER >> client(38276) time data request
SERVER >> client connect : 111.222.333.12:38396 -- (2)
SERVER >> fork
SERVER >> fork
SERVER >> recive data : time
SERVER >> client(38396) time data request
SERVER >> client connect : 111.222.333.12:38559 -- (3)
SERVER >> fork
SERVER >> fork
SERVER >> recive data : time
SERVER >> client(38559) time data request -- (4)
SERVER >> recive data : quit
SERVER >> client(38276) quit request -- (5)
SERVER >> recive data : quit
SERVER >> client(38396) quit request -- (6)
SERVER >> recive data : quit
SERVER >> client(38559) quit request -- (7)

client는 (1), (2), (3)에서 3번 실행하여 동일하게 time 메시지를 송신 및 데이타를 수신하고 하고 quit 했습니다.

#] ./client.php
CLIENT >> socket connect to 111.222.333.12:65000
CLIENT >> Enter command time or quit : time
CLIENT >> Input command : time
CLIENT >> recived data : 2003/05/21 16:18:34
CLIENT >> Enter command time or quit : quit
CLIENT >> Input command : quit
#]

아래는 (3),(7)시점에서 두번 프로세스 현황을 확인(ps, pstree)한 결과 입니다.

#] ps -xa | grep server.php
30947 pts/3 S 0:00 /usr/local/bin/php -q ./server.php
31203 pts/3 S 0:00 /usr/local/bin/php -q ./server.php
31287 pts/3 S 0:00 /usr/local/bin/php -q ./server.php
31372 pts/3 S 0:00 /usr/local/bin/php -q ./server.php
31467 pts/7 S 0:00 grep server.php
#] pstree
init-+-crond
...
...
|-sshd-+-sshd---bash---server.php---3*[server.php]
| |-3*[sshd---bash---client.php]
| `-sshd---bash---pstree
...
...
`-xinetd
#]
#] ps -xa | grep server.php
30947 pts/3 S 0:00 /usr/local/bin/php -q ./server.php
31521 pts/7 S 0:00 grep server.php
#] pstree
init-+-crond
...
...
|-sshd-+-sshd---bash---su---bash---server.php
| |-3*[sshd---bash]
| `-sshd---bash---pstree
...
...
`-xinetd
#]
(3) 시점에는 fork 3번 실행한 순간이므로 부모 프로세스와 자식 프로세스 3개, 총 4개의 프로세스가 실행되고 있는것을 확인할수 있습니다.

pstree의 경우는 server.php---3*[server.php]처럼 Master Process 한개Child Process 3개로 표현되어 있습니다. 문론 메시지도 잘 전송 되었구요.. ^^


5. 결론

오늘은 PHP의 Process Control Function을 이용하여 다수의 클라이언트 요청처리를 해보았습니다.

fork방식은 select방식보다 간단한 구조로 구현하기 간편하다는 장점도 있지만, 다중 프로세스 구조라 프로세스간 통신을 위해서 부차적인 IPC를 구현해야 할 상황이 생길 수도 있다는 점이 단점이라 할 수 있습니다.

다음 강좌에는 mysql과 지금까지 배운 소켓 통신을 가지고 간단한 채팅 클라이언트/서버를 만들어 보겠습니다.


, .