Computer/PHP

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

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



1 소개
2 pcntl_fork() 함수
3 PHP 컴파일 하기
4 프로그램 작성
4.1 서버 만들기
4.2 클라이언트 만들기
4.3 실행하기
5 결론




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



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



2 pcntl_fork() 함수 #
int pcntl_fork ( )

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

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

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

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



$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 에서 소켓 함수를 사용하기 위해 --with-sockets 옵션을 주어 컴파일 하였습니다. Process Control Function(이하 pcntl) 을 사용하기 위해서는 --enable-pcntl 옵션으로 컴파일 되어 있어야 합니다. 만약 phpinfo()에서 해당 옵션이 보이지 않는다면 다시 컴파일 해 주세요.


#] tar -zxvf php-4.3.1.tar.gz
#] cd php-4.3.1
#] ./configure --with-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
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를 구현해야 할 상황이 생길수도 있다는 점이 단점이라 할수 있습니다.

출처 : http://wiki.jinoos.com.