PHP & Others

PHP로 소켓 서버 작성하기

컨텐츠 정보

본문


PHP로 소켓 서버 작성하기

작성자 김영진(cogolda@hanmail.net)


대상 독자
요구사항
1 개요 - 소켓 서버란 무엇인가?
1.1 소켓의 형태
2 PHP 소켓 함수
2.1 PHP에서 소켓 만들기
2.2 실용 서버 만들기
2.3 실용적인 예
2.4 보안
가능한 기능 추가와 확장
저자에 관해

알아두기

이 자료는 http://www.zend.com/zend/tut/tutorial-staub3.php/에 있는
Writing Socket Servers in PHP를 제가 허접번역 및 내용을 추가 및 생략한 것입니다.
이미 다 아시는 내용 이시겠지만, 이해해 주시면 감사하겠습니다.
질문이나 번역을 바라는 주제가 있으시면,
이메일 또는 코멘트를 이용해 주세요. 참고로 전 영어, PHP 둘다 왕초보^^;.

대상 독자
인터넷 소켓 서버를 만들기 위해 PHP 소켓 함수 사용에 관심있는 분들..

미리 준비할 것
       
        * PHP 소켓 라이브러리. 이것은 컴파일 할 때 -enable-sockets 설정 옵션을 주시면 됩니다.
        * PHP의 CLI (Command Line Interface) 버전. 이것은 소켓서버가 커멘드 라인에서 실행하기 때문입니다.
        * 리눅스 운영체제
비록 리눅스를 사용한 따라하기이지만, 윈도우나 유닉스환경에서도 됩니다.
윈도우에서, PHP 소켓은 php.ini에서 externsion=php_sockets.dll부분의 주석을 제거해야
사용할 수 있습니다.

1 개요 - 소켓 서버란 무엇인가?

소켓 서버는 소켓 서버에 들어오는 요청과 응답을 대기하고있는 특정 포트에 할당하는 서비스입니다.
이메일 서비스(POP3, SMTP)와 웹서버는 소켓 서버의 좋은 예입니다.
HTTP(Web)서버는 들어오는 요청에 대해 포트 80에서 대기하고,
서버 안에 있는 HTML과 다른 파일(이미지, 동영상,문서)을 클라이언트 사용자에게 서비스 합니다.

소켓 서버는 주로 서비스나 데몬으로 끊임없이 실행됩니다.

쉽게 설명하자면, 우리가 네트워크 프로그래밍을 한다는 의미는 소켓이 제공하는 함수를 이용하여 프로그래밍을 한다는 의미입니다.
일상적으로, 소켓 프로그래밍과 네트워크 프로그래밍은 거의 같은 의미로 사용되고 있습니다.

1.1 소켓의 종류

정보가 인터넷으로 보내질 때, 그것은 주로 패킷으로 나누어집니다. 
왜냐하면 큰 용량을 패킷이라는 작은 단위로 쪼갠후에 보내야 하기 때문입니다.

패킷으로 정보를 쪼갤 때 두가지 다른 프로토콜이 있는데, 정보 형태에 대해 전송 필요조건에 의존하기 때문입니다.       
        * TCP(Transmmission Control Protocol) - 전송 패킷은 다른 끝에서 번호가 붙여지고 끝에가서 조립된다. 그들은전체 메시지를 형성하기 위해 조립된다 .
        TCP는 데이터의 손실이 없습니다.(만약 패킷을 잃어버리면, 재전송합니다.), 이메일처럼 완전한 받아야 하는 파일을 보낼때 적합합니다.
        * UDP(User Datagram Protocol) - 이것은 비연결 프로토콜입니다.
        TCP처럼 IP 프로토콜 위에서 실행됩니다.
그 차이는 UDP는 약간의 에러 복구 서비스를 제공하고 신뢰성이 없습니다.
UDP는 특별히 음악, 동영상 스트리밍처럼 스트리밍 데이타에 적합합니다.

2. PHP 소켓  함수

PHP 는 저 수준에서 소켓을 처리할 수 있습니다.
PHP3에서, PHP는 fsockopen()과 관련 함수로 처리하는 소켓을 도입하였습니다.
(네트워크 부분은 PHP공식 매뉴얼 http://php.net/network를 보세요).
PHP4에서는, PHP의 소켓은  BSD 스타일 소켓에 저수준 연결의 도입으로 멋지게 확장되었습니다.

참고: PHP에서 소켓 함수는 여전히 실험적이지만, 다음 버전에서 더욱 강화될 것입니다.
PHP 소켓함수는 잘만 작성하면 쓸만하다는 것을 테스트는 보여줍니다.

2.1. PHP로 소켓 만들기

PHP에서 저수준 소켓을 만드는 것은 C와 유닉스 소켓 프로그래밍에서 소켓 함수를 사용한것과 매우 비슷합니다.

간단한 예제로 시작해봅시다. 9000 포트에서 연결을 대기하는 소켓 서버는 입력으로 문자열을 받아들이고, 모든 공백 문자는 제거하고, 반환한다.


#!/usr/local/bin/php -q

<?
// 무한정 실행하기 위해 시간한계를 0으로 설정한다.
set_time_limit (0);

// 대기할 IP 주소와 포트번호를 설정한다
$address = '192.168.0.100';
$port = 9000;

// TCP 소켓을 만든다.
$sock = socket_create(AF_INET, SOCK_STREAM, 0);
// IP 주소와 포트번호를 소켓에 결합
socket_bind($sock, $address, $port) or die('Could not bind to address');
// 접속을 위해 대기를 시작한다
socket_listen($sock);

/* 들어오는 요청을 받아들이고 자식 프로세스로 그들을 처리한다 */
$client = socket_accept($sock);

// 클라이언트가 입력한 1024 바이트를 읽는다.
$input = socket_read($client, 1024);

// 입력받은 문자열에서 공백을 제거한다.
$output = ereg_replace("[ \\t\\n\\r]","",$input).chr(0);

// 클라이언드에 출력을 보낸다.
socket_write($client, $output);

// 자식 프로세스를 닫는다
socket_close($client);

// 주 소켓을 닫는다
socket_close($sock);
?>

이 프로그램을 실행하려면, 첫 줄 #!/usr/local/bin/php -q 가 PHP CLI(or CGI) binary의 위치에 있어야 합니다.
여러분은 소스파일의 실행모드를 바꾸는 것이 필요합니다.
(chmod 755 socket_server.php)
실행하려면,  커멘드 라인에서 ./socket_server.php치고 엔터.
외관상 이 프로그램은 아무일도 않합니다.

그럼 각각의 라인을 자세히 살펴봅시다.

        * #!/usr/local/bin/php -q
        PHP CLI 실행파일을 실행합니다. -q 옵션을 쓰면 HTTP 헤더를 출력하지 않습니다.

        * $sock =sock_create(AF_INET, SOCK_STREAM, 0)
        '주인' 소켓을 만듭니다. 이 소켓은  들어오는 요청을 위해 대기할 것이고, 클라이언트를 위해 새로운 소켓을 생성할것입니다.

PHP manual에 보면 (http://www.php.net/socket_create): AF_INET는 IPV4 프로토콜의 도메인 타입입니다.
참고: 만약 UDP 소켓을 열기위해서는, SOCK_STREAM을 지우고 SOCK_DGRAM을 쓰십시오.

        * socket_bind($sock, $address, $port) or die('Could not bind to address')
        소켓을 입력된 주소와 포트에 결합합니다.

        * socket_listen($sock)
        저장된 포트번호에서, 들어오는 연결을 대기합니다. 만약 연결되면, 자식 소켓을 생성할 것이다.

        * $cliend = socket_accept($sock)
        마스터 소켓에서 접속을 받아들인다.

        * $input = socket_read($client, 1024)
        받아들인 소켓에서 1024 바이트를 읽는다,
       
        * $output = ereg_replace("[\\t\\n\\r]","",$input).chr(0)
        정규식을 사용하여 모든 공백 문자를 제거한다.
       
        *socket_write($client, $output)
        스트림 소켓으로 테이터를 전송한다.
       

2.2 실제 서버 제작하기

지금 여러분은 소켓을 설정하고 대기하기 위해 필요한 기본 절차를 배웠습니다.
여러분은 쓸모있는 서버를 만들 준비가 되어있습니다.

위에 있는 소스코드를 보면, 이 프로그램은 단 한번 실행되고 종료됩니다.
그것은 소켓서버를 생성하기위해 요구되는 단계를 설명하기에는 좋습니다.
그러나 그것은 현실 상황에서는 적합하지 않다.
여러분의 프로그램이 실행 되고, 들어오는 요청에 응답하자마다. 프로그램이 종료되기를 원치않을 것입니다.
다시말하면, 계속 실행되야만 한다.

우리는 그러므로 프로그램은 계속 실행하기 위한 방법이 필요합니다.

우리가 명확히 exit 문을 명령할때까지, 우리는 while(true) { /* 놀지말고 일 좀 해라 */ } 이렇게 반복문을 계속적으로 사용할수 있습니다.
우리는 위의 예를 다음과 같은 기능을 추가하여 확장할 것입니다.

        *  끝없이 프로그램을 실행하게한다.
        * 종료 기능을 만든다.
        * 여러명이 접속해도 처리할수 있도록 한다.

#!/usr/local/bin/php -q
<?

// 끝없이 실행하기 위해 시간 한계를 0으로 설정한다
set_time_limit (0);

// 서버가 대기할 ip 주소와 port 번호를 설정한다.
$address = '192.168.0.100';
$port = 9000;
// 동시에 접속할 수 있는 사용자를 10명으로 한정한다.
$max_clients = 10;

// 클라이언드 정보를 얻을 배열
// 다시 말하자면, 사용자가 10명을 동시에 받아들이겠다면,
// 배열 크기를 10개로 잡아야 합니다.
$clients = Array();

// TCP 스트림 소켓 생성
$sock = socket_create(AF_INET, SOCK_STREAM, 0);

// 소켓을 아이피주소/포트에 결합
socket_bind($sock, $address, $port) or die('주소 지정에 실패했습니다.');

// 연결을 대기를 시작한다.
socket_listen($sock);

// 무한 루프 실행
while (true) {
    // 읽기위해 클라이언트 대기 소켓을 설정한다
    $read[0] = $sock;
    for ($i = 0; $i < $max_clients; $i++)
    {
        if ($client[$i]['sock']  != null)
            $read[$i + 1] = $client[$i]['sock'] ;
    }
    // socket_select()에 블럭킹 호출을 설정한다.
    $ready = socket_select($read,null,null,null);

    /* 만약 새로운 접속이 되면, 그것을 클라이언트 배열에 추가한다 */
    if (in_array($sock, $read)) {
        for ($i = 0; $i < $max_clients; $i++)
        {
            if ($client[$i]['sock'] == null) {
                $client[$i]['sock'] = socket_accept($sock);
                break;
            }
            elseif ($i == $max_clients - 1)
                print ("너무 많은 사용자")
        }
        if (--$ready <= 0)
            continue;
    } // 조건문 if in_array의 끝
   
    // 만약 클라이언트가 쓰기를 시도하면, 바로 그것을 처리한다
    for ($i = 0; $i < $max_clients; $i++) // for each client
    {
        if (in_array($client[$i]['sock'] , $read))
        { // 사용자로부터 입력을 받아서..
            $input = socket_read($client[$i]['sock'] , 1024);
        // 만약 입력이 없으면...
            if ($input == null) {
                // Zero length string meaning disconnected
                unset($client[$i]);
            }
            $n = trim($input);
        // 만약 클라이언트가 'exit'를 입력하면,
            if ($input == 'exit') {
            // 요청에 따라 연결을 종료한다
                socket_close($client[$i]['sock']);
                                // 만약 아니면...
            } elseif ($input) {
            // 공백문자를 제거하고,
                $output = ereg_replace("[ \\t\\n\\r]","",$input).chr(0);
                // 사용에게 소켓 스트림을 통하여 문자열을 보낸다.
                socket_write($client[$i]['sock'],$output);
            }
        } else {
            // 소켓 종료
            socket_close($client[$i]['sock']);
            unset($client[$i]);
        }
    }
} // while문 끝
// 주인 소켓 종료
socket_close($sock);
?>

기본 기능은 처음 예제와 같다. 다만 추가된 특징은 사용자가 문자열 'exit'를 프로그램에 보내면,
프로그램은 연결을 끝낼것이다.

이 프로그램은 반복문을 제외하고, 처음 프로그램과 매우 비슷하다.
소스는 4개의  기본 블럭이 있다.

        1 읽기위해 소켓 설정
        2 새로운 클라이언트를 대기하고 $client 배열로 그것을 설정한다.
        3 클라이언트 대기하고 입력을 기록한다.
        4 클라이언트 입력을 처리한다.

이 예에서, 새 함수는 socket_select($read, null, null, null)입니다.
이 함수는 소켓 배열에서 select() 시스템 호출을 실행하고 상태가 바뀔때까지 기다립니다.
이것은 그 상태가 바뀔때까지 막고 있다가, 누군가 접속하여 그 상태가 바뀌면 그것을 처리합니다.

마지막 요점으로, 여러분은 연결된 다른 클라이언드에 정보를 뿌리고 싶을때(예를 들면 다-대-다 채팅 환경을 말한다)를 알아낼 수있습니다.
이것은 다음과 같은 코드로 저장될수 있다.
<PRE>
$output = '이것은 제가 여러명에게 보내고 싶은 메시지입니다'.chr(0);
for ($j = 0; $j < MAX_CLIENTS; $j++) // 각각의 클라이언트
{
    if ($client[$j]['sock']) {
        socket_write($client[$j]['sock'], $output);
    }
}

원문에서 broadcast라는 말이 나오는데 우리 말로는 '방송'으로 번역하고, 
여기서는 한번에 여러명에게 정보를 보내는 것을 애기합니다. 다르게 애기하면 '대량 살포' 이런 의미입니다.

socket_select()함수 역시 중요한데. 부가 설명을 하자면,
select라는 말처럼 기다리고 있다가, 튀는 놈, 예를 들면 사용자가 접속하면, 그 놈을 선택(select)하여
넘겨주면 그걸 처리해주는 것입니다.

2.3 실용적인 사용

지금 우리는 소켓 서버 생성의 기초를 배웠다. 만약 한계가 있다면, 우리의 상상력입니다.

        *  채팅 서버(텍스트 또는 그래픽 기반). 이것은 재미있으면서 진짜 어플리케이션이 될 수 있습니다.
        *  실시간 정보 스트리밍 (뉴스, 주식..기타등등)
        *  스트리밍 멀티미디어 (이미지, 동영상과 사운드)
        *  인증 서버
        *  간단한 웹, POP3, SMTP 그리고 FTP 서버.

좀더 자세하게 말하자면, 소켓 라이브러리는 서버와 마찬가지로 클라이언트 프로그램을 만들 수 있다.

2.4 보안

보안은 접근 가능한 온라인 프로그램 생성할때 고려되어야 한다.
이것은  계속 실행되는 서버 소켓과같이 일반적인 PHP 스크립트에도 해당됩니다.

여러분이 보안 정책을 계획할때, 고려하는 많은 사실이 있다. 여기 몇가지 나열하겠다.


        * 파일 접근 - 여러분은 파일 접근을 제한해야한다. 만약 웹서버같은 것이 파일 접근을 허락하면
        , 여러분은 특정 폴더에서 파일에 접근하게 해야한다. 다른 좋은 개념은
       
        * 인증 - 보안에 약한 서버를 위해, 여러분은 인증을 사용하기를 추천한다. 여러분이
        플래시나, 비주얼 베이직으로 사용자 프론트-엔드를 만들더라도, 그것은 신뢰성이 없습니다.
        누군가 네트워크 접속과 "sniff"할수 없을 것이다.

인증을 하기 위한 한가지 좋은 방법으로, 사용자가 성공적으로 스스로 인증하기 전까지 어떤 시도도 허락하지 않는 것이다.
(위에서 예를 들면 인증 하자마자, $client[$i]['authenticated'] = true )
       
        * 암호화 - 암호화는 중요한 정보를 막기위한 대단히 좋은 방법입니다. 암호화는 특히 위에 애기한 인증과 함께 사용하면
대단히  유용합니다. 운이 좋게도 PHP는 뛰어난 암호화 라이브러리를 제공합니다.
자세한 내용은 (http://www.php.net/mcrypt)

3. 가능한 기능 추가와 확장
        * PCNTL을 사용하여 프로세스 제어와 쓰레드를 추가한다.
        * 프론트-앤드 인터페이스(다시 말해 GUI 화면)는 C++, VB, Flash (XMLSockets를 사용), 자바 또는 TCP/IP 또는 UDP 소켓을 지원하는 모든 소프트 웨어를 사용하려 작성할 수 있다.
        * 만약 서버가  시험삼아 돌아가게되면, 당신은 모니터에 에러 메시지에 출력하는 대신 텍스트 화일이나 데이터 베이스 기록되는 사용자 에러 핸들링 함수를 생성을 원할것이다.

글쓴 이에 대해
집에서 놀고 있는 백수.




---------------------------------------------------------------------------

시엔 -enable-sockets -> --enable-sockets

:-) 03/21 21:22:11 
 
 빠리 바께스 집에서 놀고 있는 백수 // ㅡ_ㅡ' 03/21 21:48:32 
 
 혀니 감사합니다. 참고 많이 할께요 03/21 22:38:30 
 
 aa 원도우에서 테스트할려면 #!/usr/local/bin/php -q 이부분을
어캐바꾸나요? 03/22 0:02:00 
 
 김영진 #!c:\\php -q
쓰시면 됩니다. 그리고 클라이언트 프로그램이 있어야
테스트가 가능한데,
곧 좀더 고급 네트워크 프로그래밍 부분을 올리겠습니다.
테스트도 가능한...^^; 03/22 0:26:46 
 
 oopp 자바나 C 등에서 쓰이는 소켓 사용과 비슷하거나 같네요.
그것들과 차이가 있다면 어떤것이 있을까요?
자바로 채팅서버를 만든다고 해도 위처럼 서버를 돌리는 형태일텐데..
거의 같다고 봐도 되는지 궁금하네요.

최소한 php 로 하더라도 스크립트 형태(웹으로의 접근) 보다는 훨씬 나은 방법이겠죠? 궁금.
 03/22 0:54:06 
 
 oopp 퍼포먼스 의 문제만 다르다고 봐야 하나? 03/22 0:54:29 
 
 L.S. C랑 거의 동일합니다. PHP의 소켓 구현이라는게 OS의 소켓 레이어에 PHP를 덮어씌운것이니까요. (PHP는 C로 작성되어있습니다)

장점이라면 PHP 문법 및 구조를 그대로 쓸 수 있다는 것이겠고, 단점이라면 역시 퍼포먼스겠지요. (PHP로 쓰레드를 만들고 부수고 하는건 불가능...)

메인 프로세스가 죽지 않기 때문에 변수 사용에도 신경써줘야합니다. (네트워크 프로그램이라면 어떤 언어를 사용해도 신경써야하지요) PHP의 메모리 관리는 실행하고 바로 죽는 웹 프로세스에 적합하게 되어있는지라... 명시적으로 unset하지 않으면 점점 메모리를 먹게 되지요. (스크립트 종료시에 할당받은 메모리를 전부 반환하게 되어있는데, 종료하지 않는 스크립트니까요) 03/22 1:54:40 
 
 아이수 걍 c 로 짜는 게 나을 수도..
db 코드 같은 건 익숙한 php 가 나을 수도 있겠군요.

각각 장단점이 있는 것 같습니다. 03/22 6:04:58 
 
 베르사체 소켓서버 Python으로도 해보세요.
아주 훌륭합니다. 03/22 10:43:17 
 
 아주작은새 퍼포먼서 비교를 해야 할것 같네요^^;;
현재 Java 로 쓰레드 만들었는데 cpu 점유율이 상당히 낮습니다.
C 로는 아직 안만들어 봤지만 거의 비슷할것 이라고 생각됩니다.
네트웍 프로그래밍에서 가장 중요한게 퍼포먼스 아니던가요?
계속 해서 점유율 올라가고 언젠가는 다운될거라는 무서움을 가지고 프로그램을 짤 수는 없는 거잖아요
^^;; 03/22 11:06:35 
 
 하하 장단점이 있는듯...ㅎ 03/22 12:46:43 
 
 aa 준비할물품중에 PHP의 CLI (Command Line Interface) 버전
이것은 원도우라면 어떻게 준비해야할가요? 03/22 13:08:15 
 
 ㅎ php 자체가 무겁고 fork 를 하기때문에 자원을 많이 먹는 것 같습니다. 하지만 많은 접속이 필요하지 않으면 괜찮습니다 03/22 13:49:52 
 
 글쿤 데몬으로 만들어서 사용할수도 있습니다 .^^
데몬과 백그라운드의 차이점을 아시면요 ,,
위에 있는 소스로 하시면 백그라운드로 밖에 못돌리죠 ^^
즉 telnet 이나 ssh터미널이 종료되면 같이 죽어버리죠
아니면 screen같은걸 써야하는데 많이 불편하고요 ~
php로 소켓프로그램을 하셔도 어떠한 용도냐에 따라
훌룡한 시스템을 만드실수 있을겁니다 03/22 14:07:52 
 
 시엔 aa// php 압축파일에 php-cli.exe가 있을겁니다. 03/22 16:20:14 
 
 aa 시엔님 원도우용은 cli라는 폴더안에 php.exe가 있습니다.
그럼 실제 실행할때는 c:\\php\\cli폴더에 있는걸 사용합니까? 아니면 c:\\php에 있는 php.exe를 사용합니까? 제가 닮대가리라서요^^ 03/22 23:52:58 
 
 L.S. c:\\php\\cli\\php.exe로 지정하면 됩니다.
c:\\php\\php.exe -q 로 지정해도 문제는 없습니다.
(CGI는 규격상 표준 입출력을 이용하므로 -q 플래그를 지정하면 사용상 차이는 없습니다) 03/23 2:38:23 

관련자료

댓글 0
등록된 댓글이 없습니다.
Today's proverb
네 마음 안에서 구하라. 마음 밖에서 구하면 천년을 구해도 허사이다. (부처)