Develop!/php

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

체리필터 2011. 10. 7. 17:34
728x90
반응형

이진우

프리랜서
프로그래머

            
          


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

문서가 존재하는 모든곳에 답변을 드릴수 없으므로 질문은 홈페이지(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)) 에서 소켓 함수를 사용하기 위해 --with-sockets 옵션을 주어 컴파일 하였습니다.

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

#] 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
<?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과 지금까지 배운 소켓 통신을 가지고 간단한 채팅 클라이언트/서버를 만들어 보겠습니다.

728x90
반응형