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


 

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

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

받습니다.

 이진우 jinoos@jinoos.com


1. 소개

저번강좌(PHP를 이용한 다중 연결 소켓 통신 (1))에 간단한 서버/클라이언트 프로그램을 만들어 보았습니다. 하지만 이것은 많은 부분이 부족하다는 생각들을 하셨을껍니다.

오늘 시간에는 socket_select() 함수를 통해서 다중의 클라이언트 요청을 처리하는 프로그램을 짜 보겠습니다.


2. SELECT

저번강좌(PHP를 이용한 다중 연결 소켓 통신 (1))의 server.php 에서

 

while($cSock = socket_accept($sSock))
{
$date = date("Y/m/d H:i:s");
socket_write($cSock, $date);
socket_close($cSock);
}

위 코드는 클라이언트 소켓이 접속하기를 대기하는 동작입니다. 하지만 socket_accept()함수는 호출된 뒤에 소켓연결 요청이 들어오면 요청 처리를 하게 됩니다. 프로그램 상에선 메시지를 전송하고 곧바로 클라이언트와 소켓을 종료하고 다시 accept 상태(blocking모드)로 전환되는 형태입니다.

이와같은 경우는 접속된 클라이언트와 소켓 접속이 반드시 끊어져야(아니면 당연히 끊어야 한다던지) 다른 클라이언트의 접속을 처리 할수 있습니다. 하지만 불규칙 적으로 클라이언트가 서버에 요청(소켓접속이 아님)을 하는 상황이라면 문제가 될 것이 자명 합니다.

이와 같은 상황을 처리 하기 위해서 다량의 socket묶음을 동시에 감시하는 SELECT가 있습니다. PHP에서는 socket_select() 함수가 바로 그것입니다.


2.1. socket_select() 함수

int socket_select ( resource &read, resource &write, resource &except, int tv_sec [, int tv_usec] )

read 인자에는 읽기 이벤트를 감시할 Socket Array가 참조됩니다. socket_select() 함수는 Array 묶음으로 들어오는 소켓들을 감시하다가 이벤트가 발생하면 해당인자를 읽기 이벤트가 발생한 Socket Array로 변환하고 Blocking 상태가 해제 됩니다.

write 인자역시 read 인자와 같은 역할로 쓰기 이벤트가 발생할 시에 사용된다.(하지만. -_- 어디에 필요하단 말인가. 본인은 이부분이 이해가 잘 안간다. 혹시 알고있다면 알려주기 바란다. except 역시 본인이 사용법을 알아내지 못했다.)

중요한 인자인 tv_sec는 select가 block 모드로 대기하고 있는 timeout을 설정합니다. 이것을 유용하게 이용하면 일정간격으로 어떠한 작업을 수행하는것도 가능합니다. NULL로 설정하면 timeout없이 대기 합니다.

 


3. 프로그램 작성

전장에서 select 함수에 대해 몇가지를 알아 보았습니다. 부족하지만 사용하기엔 크게 문제가 없을 것입니다.

오늘 작성한 프로그램은 저번강좌(PHP를 이용한 다중 연결 소켓 통신 (1)) 에서 작성한 프로그램과 기능은 같지만 약간 복잡한 형태입니다.

서버소켓을 생성한뒤에 클라이언트를 대기하고, 클라이언트가 접속하면 클라이언트 소켓을 클라이언트 소켓 묶음으로 저장한뒤에 감시합니다. 새로운 클라이언트가 접속하면 다시 클라이언트 묶음에 저장하고 감시를 반복하며, 클라이언트에서 time라는 메시지를 수신하면 현재 시간을 바로 송신하고, quit라는 메시지를 수신하면 클라이언트와 접속을 끊습니다.


3.1. 서버 만들기

시작코드는 저번 강좌와 많이 비슷합니다. 다만 무한 루프를 돌면서 서버 소켓과 클라이언트 소켓 묶음을 Array로 생성하여 select의 Read 이벤트를 감시합니다.

...
while(1)
{
$sockArr = array_merge(array($sSock), $cSock);
if(socket_select($sockArr, $tWrite = NULL, $tExcept = NULL, _TIMEOUT) > 0)
{
... (1)
}
}
...
Write 이벤트와 Except 이벤트는 무시하기 위해서 NULL 처리 되었습니다. timeout은 주어졌지만 본 프로그램에선 크게 의미 없는 상황입니다.

위 코드중 socket_select()에서 이벤트가 발생되면 $sockArr 변수에 리턴된 Array를 (1)에서 분석합니다.

...
foreach($sockArr as $sock)
{
if($sock == $sSock)
{
... // (2)
}else
{
... // (3)
}
}
...
서버 소켓에 클라이언트가 접속을 요청했을때 $sockArr에 서버 소켓이 리턴됩니다. 그리하여 (2)에서 클라이언트 소켓을 socket_accept()함수로 접속을 허용합니다.

만약 리턴된 소켓이 서버 소켓이 아니라면 클라이언트와 통신중인 소켓입니다. 당연하겠죠. array_merge(array($sSock), $cSock) 로 서버 소켓과 클라이언트 소멧만을 merge했으니까요.

(3)에서는 클라이언트에서 보내는 정보를 읽어들이고 이를 분석해서 처리 하는 부분이 들어 갑니다. 유의해서 봐야 할 부분은

$buf = socket_read($sock, 4096);

// 접속 종료
if(!$buf)
{
}
// 메시지 수신 이벤트
else
{
}
$buf 의 내용이 없을때 클라이언트가 접속을 종료함을 의미 한다는 것입니다.

자 그럼 이제 실제 코드를 보겠습니다.

 

#!/home/dimeclub/www/bin/php/php -q
<?php
set_time_limit(0);

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


$cSock = array();
$cInfo = array();
$sSock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

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

while(1)
{
$sockArr = array_merge(array($sSock), $cSock);
if(socket_select($sockArr, $tWrite = NULL, $tExcept = NULL, _TIMEOUT) > 0)
{
foreach($sockArr as $sock)
{
// Listen 하고 있는 서버 소켓일 경우
// 새로운 클라이언트의 접속을 의미
if($sock == $sSock)
{
$tSock = socket_accept($sSock);

socket_getpeername($tSock, $sockIp, $sockPort);

$cSock[$tSock] = $tSock;
$cInfo[$tSock] = array('ip'=>$sockIp, 'port'=>$sockPort);

msg("client connect : ".$sockIp.":".$sockPort."\n");
}
// 클라이언트 접속해 있는 소켓중 하나일경우
// 해당 클라이언트에서 이벤트가 발생함을 의미
else
{
$buf = socket_read($sock, 4096);

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

// 종료
case "quit":
msg("client(".$thisSockInfo['port'].") quit request\n");
socket_write($sock, "quit");
socket_close($sock);
exceptSocket(&$cSock, &$cInfo, $sock);
break;
default:
msg("client(".$thisSockInfo['port'].") invalid command $cmd\n");
break;
}
}
}
}
}
}

function exceptSocket(&$sockSet, &$infoSet, $sock)
{
unset($sockSet[$sock]);
unset($infoSet[$sock]);
// array_merge 함수에서 error 발생을 막기위한 처리
if(count($sockSet)==0)
{
$sockSet = array();
$infoSet = array();
}
}

function msg($msg)
{
echo "SERVER >> ".$msg;
}
?>
server.php로 저장합니다. 역시 실행권한은 있어야 겠지요.


3.2. 클라이언트 만들기

클라이언트 역시 몇가지를 수정하여 쉘에서 사용자의 입력을 받아 들이도록 처리하였습니다.

클라이언트는 서버와 소켓을 연결한뒤 사용자의 키입력을 기다렸다 time을 입력시 서버에 time메시지를 전송하고 quit입력시 프로그램을 종료 합니다. PHP CGI를 이용해서 사용자 키입력을 받아들이는 역할은 아래 함수처럼 구현되었습니다.

function read_data()
{
$in = fopen("php://stdin", "r");
$in_string = fgets($in, 255);
fclose($in);
return $in_string;
}
fopen("php://stdin", "r") 로 원래 의미는 리눅스 시스템에서 fopen("/dev/stdin", "r"), fopen("/proc/self/fd/0", "r"), fopen("dev/fd/0", "r") 입니다. 표준입력을 받아들이는 역할로 사용자 키입력을 받아들이죠.

만약 표준출력 또는 표출에러 출력을 받아들이려면 fopen("php://stdout","r"), fopen("php://stderr", "r") 로 구현할수 있습니다.

클라이언트는 time 라는 서버에 시간 데이터를 요청하는 메시지와 quit 라는 종료 두 메시지를 제외하고는 나머지는 무시합니다. 코드상에는 invalid command (not send) 라고 출력하지만 그이상의 어떠한 행동도 하지 않고 무시해 버립니다.

 

#!/home/dimeclub/www/bin/php/php -q
<?php
define("_IP", "111.222.333.12");
define("_PORT", "65000");

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($sock, _IP, _PORT);
msg("socket connect to "._IP.":"._PORT."\n");

while(1)
{
msg("Enter command time or quit : ");

// 사용자의 명령어를 입력받습니다.
$stdin = ereg_replace("\n|\r", "", read_data());
$stdin = substr($stdin, 0, 4);

// time 또는 quit 메시지 말고는 무시 합니다.
if($stdin == "time" || $stdin == "quit")
{
msg("Input command : ".$stdin."\n");
}else
{
msg("invalid command (not send) : ".$stdin."\n");
continue;
}

socket_write($sock, $stdin);
$sMsg = socket_read($sock, 4096);
if(substr($sMsg, 0, 4) == 'quit')
{
socket_close($sock);
exit;
}else
{
msg("recived data : ".$sMsg."\n");
}
}

// 표준입력을 받아 값을 리턴하는 함수
function read_data()
{
$in = fopen("php://stdin", "r");
$in_string = fgets($in, 255);
fclose($in);
return $in_string;
}

// 로그를 출력합니다. 디버그용
function msg($msg)
{
echo "CLIENT >> ".$msg;
}
?>

특별히 난해한 부분은 없을꺼라 생각합니다. 역시 위 소스를 client.php로 저장하고 실행권한을 줍니다.


3.3. 실행하기

서버를 실행하고 클라이언트를 실행합니다. 클라이언트#1을 실행후 time 메시지를 날리면서 클라이언트#2 실행후 종료, 클라이언트#3 실행후 종료, 그리고 다시 클라이언트#1을 종료 하였습니다.

클라이언트#1

#] ./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/09 22:32:29
CLIENT >> Enter command time or quit : time
CLIENT >> Input command : time
CLIENT >> recived data : 2003/05/09 22:32:34
CLIENT >> Enter command time or quit : time
CLIENT >> Input command : time
CLIENT >> recived data : 2003/05/09 22:32:41
CLIENT >> Enter command time or quit : time
CLIENT >> Input command : time
CLIENT >> recived data : 2003/05/09 22:32:50
CLIENT >> Enter command time or quit : time
CLIENT >> Input command : time
CLIENT >> recived data : 2003/05/09 22:32:57
CLIENT >> Enter command time or quit : quit
CLIENT >> Input command : quit
#]

클라이언트#2,#3

#] ./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/09 22:32:31
CLIENT >> Enter command time or quit : quit
CLIENT >> Input command : 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/09 22:32:47
CLIENT >> Enter command time or quit : quit
CLIENT >> Input command : quit
#]

같은 상황의 서버쪽 실행화면입니다.

 

#] ./server.php
SERVER >> client connect : 111.222.333.12:32850 -- (4)
SERVER >> client connect : 111.222.333.12:32868 -- (5)
SERVER >> recive data : time
SERVER >> client(32850) time data request
SERVER >> recive data : time
SERVER >> client(32868) time data request
SERVER >> recive data : time
SERVER >> client(32850) time data request
SERVER >> recive data : quit
SERVER >> client(32868) quit request -- (6)
SERVER >> recive data : time
SERVER >> client(32850) time data request
SERVER >> client connect : 111.222.333.12:32938 -- (7)
SERVER >> recive data : time
SERVER >> client(32938) time data request
SERVER >> recive data : time
SERVER >> client(32850) time data request
SERVER >> recive data : quit
SERVER >> client(32938) quit request -- (8)
SERVER >> recive data : time
SERVER >> client(32850) time data request
SERVER >> recive data : quit
SERVER >> client(32850) quit request -- (9)

(4)에서 클라이언트포트 32850, (5)에서 32868 이렇게 두개의 클라이언트가 접속했습니다. 서버는 두개의 클라이언트에서 time 메시지를 수신하면서 동작하다 (6)에서 하나의 클라이언트가 접속을 종료하고 (7)에서 다시 하나의 클라이언트가 32938 클라이언트 포트로 다시 접속했습니다. 그리고 다시 time 메시지를 주고 받다 (8)에서 하나의 클라이언트가 퇴장하고 그리고 (9)에서 마지막 클라이언트 까지 종료 되었습니다.

32850, 32868 그리고 32938이 각각 클라이언트 #1, #2, #3 입니다.


4. 결론

오늘은 SELECT라는 함수를 이용한 다중 소켓 연결 서버를 만들어 보았습니다. SELECT는 간단한 구조에서 다중의 클라이언트를 받아들일수 있는 방법으로 이용됩니다. 또한 SELECT 기능은 단일소켓연결의 timeout 구현에도 사용할수 있는데 이것은 여러분들이 해보시기 바랍니다.

다음 강좌에서는 PHP의 Process Control Functions을 이용해서 Fork를 이용한 다중 연결 소켓 통신을 해보겠습니다.

, .

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

이진우


차례
1. 소개
1.1. PHP 컴파일 하기
1.2. PHP의 소켓 함수
2. 소켓 서버 만들기
3. 소켓 클라이언트 만들기 와 실행
4. 결론
이문서의 배포는 자유로우나 최소한 제작자의 정보는 제외하지 않고 배포해 주세요.
문서가 존재하는 모든곳에 답변을 드릴수 없으므로 질문은
홈페이지http://www.jinoos.com)에서만 받습니다.

 


1. 소개

앞으로 몇번에 걸처서 PHP를 이용한 다중 연결 소켓 통신에 대해서 알아보겠습니다. PHP는 확장 함수로 socket 계열 함수를 지원합니다. 하지만 이것은 컴파일시 --with-socket 옵션을 주어 컴파일 해야 합니다. 클라이언트 소켓에 관한 함수 fsockopen()는 이곳에서 사용하지 않습니다.


1.1. PHP 컴파일 하기

앞으로 설명할 내용들은 일반적인 아파치 모듈로서의 PHP가 아닌 독립적으로 실행 가능한 PHP CGI형태입니다. 이를 위해서 재 컴파일이 필요하며 이때 소켓에 관한 옵션을 추가하게 됩니다.

PHP 4.3.1 버전을 이용했습니다.

 

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

make를 한 후 make install 을 하지말고 php-4.3.1 디렉터리를 보면 php 라는 실행파일이 생성된것을 알수 있다. 그럼 이제 프로그램 짤 준비는 다?다. 간단한가? 간단하다. -_-a


1.2. PHP의 소켓 함수

PHP의 소켓 함수 몇가지를 알아보고 넘어 가도록 하자. PHP의 소켓 함수는 C sys/socket.h 에 정의 되어있는 함수들과 매우 유사하다. 참고 하도록 하자.

 

 

socket_create ( int domain, int type, int protocol )

소켓의 연결 자원을 생성 합니다. 소켓 함수를 사용할 때 기본으로 쓰입니다.

domain 파라메터에는 AF_UNIX 또는 AF_INET 으로 유닉스 도메인 소켓 또는 인터넷 소켓으로 지정합니다.

type 파라메터에는 STREAM, SOCK_DGRAM, SOCK_SEQPACKET, SOCK_RAW, SOCK_RDM, or SOCK_PACKET 등의 소켓의 종류가 설정 됩니다.

protocol 파라메터에는 TCP또는 UDP 설정(SOL_UDP or SOL_TCP)을 합니다.

socket_connect ( resource socket, string address [, int port] )

서버소켓으로 접속할 때 사용하는 함수. 소켓 클라이언트에서 사용하는 함수 입니다.

socket_setopt ( resource socket, int level, int optname, int )

소켓에 옵션을 지정합니다. 크게 중요하지 않은 함수 입니다.-_-

socket_bind ( resource socket, string address [, int port] )

서버 소켓에 주소와 포트번호를 부여 합니다. 서버 소켓에서 필수적인 작업입니다.

만약 AF_UNIX로 지정했다면 port 파라메터는 생략이 가능합니다.

socket_listen ( resource socket [, int backlog] )

서버 소켓을 클라이언트 대기 상태로 만듭니다.

socket_listen() 함수를 호출한 이후부터 서버 소켓은 클라이언트가 접속하는지 아닌지 귀를 기울이는 일을 하게 되는겁니다. 서버 소켓 사용에서 필수 적입니다.

backlog 파라메터는 받아들일 클라이언트의 최대 수를 지정합니다. 생략하면 최대 갯수로 설정됩니다.

리눅스에서 소켓의 최대 갯수는 ulimit -a 를 해보시면 open files 항목이 소켓 최대 갯수와 상응합니다.

socket_accept ( resource socket )

클라이언트 접속을 허가 할때 사용하는 함수 입니다.

함수 호출후 새로운 소켓 리소스가 리턴되는데 이것은 기존에 서버 소켓 리소스에서 생성된 클라이 언트와 통신하는 새로운 소켓 리소스 입니다.

socket_getpeername ( resource socket, string &addr [, int &port] )

클라이언트의 접속 정보를 가져 옵니다.

socket_read ( resource socket, int length [, int type] )

소켓에서 length만큼의 정보를 읽어 들입니다.

type 읽어들일 타입을 결정 합니다.

 

  • PHP_BINARY_READ : 바이너리 타입으로 읽어 들입니다. 4.1.0 포함 버전 이후부터 기본값으로 설정되었습니다.

  • PHP_NORMAL_READ : \r또는 \n이 입력될때 끊어서 읽습니다. 한마디로 라인 단위로 읽는 다는 말이죠. 4.1.0이전 버전에선 기본값.

 

socket_write ( resource socket, string buffer [, int length] )

소켓에 정보를 써넣는 함수로 마지막 인자를 생략하면 buffer의 길이가 자동으로 대입됩니다.

socket_close ( resource socket )

소켓 연결을 끊는다.

 


2. 소켓 서버 만들기

이제 실전으로 들어가 보겠다. 오늘 만들 것은 간단하게 클라이언트가 접속하면 클라이언트의 접속 정보를 출력하고 클라이언트에게 현재 날자와 시간을 전송한 후 종료 합니다.

엄밀히 말하면 오늘 만들 소켓 서버는 다중 사용자를 위한 시스템은 아니다. 하지만 오늘은 간단히 소켓의 개념을 집고 넘어가는 의미로 생각하자.

PHP를 CGI형태로 실행하기 위해서 소스코드 상단에

 

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

처럼 프로그래밍을 합니다. 이것은 Perl스크립트나 기타 쉘 스크립트에서 전처리기를 설정하는 것과 같은 방법 입니다. -q 옵션은 http protocol 헤더를 제거 하는 옵션입니다.

 

#!/usr/local/bin/php -q
<?php
define("_IP", "123.123.123.12");
define("_PORT", "65000");

$sSock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($sSock, _IP, _PORT);
socket_listen($sSock);
while($cSock = socket_accept($sSock))
{
socket_getpeername($cSock, $addr, $port);
echo "SERVER >> client connected $addr:$port \n";
$date = date("Y/m/d H:i:s");
socket_write($cSock, $date);
socket_close($cSock);
echo "SERVER >> client Close.\n";
}
?>

서버는 지속적으로 클라이언트를 기다리는 형태의 모습을 하고 있습니다.


3. 소켓 클라이언트 만들기 와 실행

클라이언트는 서버에 접속을 하고 서버쪽에서 보내온 메시지를 읽은 뒤 출력하고 종료합니다.

 

 

주의

프로그램상의 아이피는 가상으로 만든 것입니다. 테스트시 적절한 아이피로 변경해 주세요.

 

 

#!/home/dimeclub/www/bin/php/php -q
<?php
define("_IP", "123.123.123.12");
define("_PORT", "65000");

$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($sock, _IP, _PORT);
echo "CLIENT >> socket connect to "._IP.":"._PORT."\n";
$date = socket_read($sock, 4096);
echo "CLIENT >> this time is $date \n";
socket_close($sock);
echo "CLIENT >> socket closed.\n";
?>

너무 간단하다고 돌 날아오겠네요. -_-a. 위 두 파일을 server.php, client.php라 저장하고 실행권한(chmod 755 *.php)을 준 뒤 실행해 보도록 하겠습니다.

아래 화면의 #]는 쉘 커맨드 입니다.

 

#]
#] ll
total 8
-rwxr-xr-x 1 jinoos jinoos 387 May 7 04:59 client.php
-rwxr-xr-x 1 jinoos jinoos 495 May 7 04:59 server.php
#]
#] ./server.php &
[1] 23833
#]
#] ./client.php
SERVER >> client connected 123.123.123.12:45188
SERVER >> client Close.
CLIENT >> socket connect to 123.123.123.12:65000
CLIENT >> this time is 2003/05/07 04:59:49
CLIENT >> socket closed.
#]
#] ./client.php
SERVER >> client connected 123.123.123.12:45358
SERVER >> client Close.
CLIENT >> socket connect to 123.123.123.12:65000
CLIENT >> this time is 2003/05/07 05:01:03
CLIENT >> socket closed.
#]
#]

위 내용은 서버프로그램을 백그라운드로 실행하고, 짧은 시간동안 두번 클라이언트로 서버에 접속한 모습입니다. 원하던데로 출력된 것을 확인할 수 있습니다.

같은 머신 상에서 접속하게 된다면 서버와 클라이언트에서 같은 IP를 출력하게 될 것입니다. 그리고 잘보면 클라이언트의 PORT가 변경되었는데 클라이언트의 머신의 경우 일정 대역의 포트중 사용하지 않는 포트를 자동으로 사용하는 것입니다. 이것을 프로그램상으로 조정하는것은 아닙니다. OS에서 자동적으로 할당된 것을 사용하는 것입니다.


4. 결론

오늘은 간단하게 PHP를 이용해서 소켓함수를 사용하는 것을 해보았습니다. 다음시간에는 PHP의 Process Control Functions 함수들을 이용해서 Fork를 사용한 오늘 보다 좀더 진보된 다중 접속 서버 소켓을 만들어 보겠습니다.

, .

역자 - 김영진(cogolda@hanmail.net)

이 문서는 http://www.zend.com/php5/andi-book-excerpt.php/ 의 내용을 제가 번역, 수정한 내용입니다. 질문이나 의견 있으신 분은 이메일이나 코멘트를 이용해 주시면 감사하겠습니다. 부분적으로 귀찮아서 생략한 부분도 있는데, 꾸준히 보강하겠습니다. 이 문서는 php5에 관심있는 모든 프로그래머를 위해 씌여졌습니다.

목차

  1. 소개
  2. 언어 특징
    • 새로운 객체지향 모델
    • 새로운 객제지향 특징
    • 다른 새로운 언어 특징
  3. 일반적인 언어 변화
    • XML과 웹 서비스
    • 새로운 MySQLi(MySQL) 확장
    • SQLite 확장
    • Tidy 확장
    • Perl 확장
    • PHP 5에서 다른 새로운 부분
  4. 요약

미래를 준비하는 가장 좋은 방법은 미래와 직면하는 것이다.(존 스컬리)

1. 소개

만약 PHP 5가 이전 버전(php 3, php 4)처럼 성공할 것인지는 시간이 말해 줄 것이다. 모든 단점의 PHP를 제거하기 위한 목적 바뀐 새로운 특징과 변화는 전세계에서 최고의 웹 스크립트 언어로서 선도적인 위치를 지킬 것이다. 이 책은(이 문서는 PHP 5 Power Programming라는 책의 2장입니다) PHP 5를 다루고 있고, 자세하게 새로운 특징도 있다. 그렇지만, PHP 4에 익숙한 사람들을 위해 PHP 5에서 새로운 점을 알고 싶은 사람을 위해 이 장을 썼다.

2. 언어 특징

새로운 객체 지향 모델
지브 사라스키(Zeev Suraski, 일본인?)가 PHP 3시절에 객체 지향 문법을 추가했을 때, 그것은 객체에 접근하기 위한 문법적인 설탕처럼 추가했다. 객체지향 모델은 상속을 지원했고 메소드와 속성을 둘 다 합치기 위해 클래스를 만들었다. 그러나 그 이상은 아니었다. 지브와 앤디가 php 4를 위한 스크립트 엔진을 다시 작성할 때, 그것은 완전히 새로운 엔진이었고, 보다 빠르게 돌아갔고, 보다 많은 특징에 적합했다. 그렇지만 객체지향 모델은 PHP 3에서 처음 도입되었고, 미숙한 부분이 있었다.

비록 객체 모델이 심각한 한계가 있었지만 그것이 큰 규모의 PHP 어플리케이션으로 전세계에서 확산되어 사용되었다. 그것이 단점에도 불구하고 PHP 4로 OOP 패러다임의 인상적인 사용은 PHP 5 버전을 위한 주요 관심이 되었다.

PHP 3 & 4의 한계가 무엇인가? 가장 큰 단점은 객체의 복사 의미가 네이티브 타임을 위한것과 같다는 사실이다. PHP 개발자에게 어떠한 영향을 미쳤는가? 우리가 변수를 (객체에 대한 참조) 다른 변수에 할당할 때, 객체의 복사는 생성된다. 이것은 강한 성능 뿐만 아니라 php4 어플리케이션에서 버그를 유발한다. 왜냐하면 많은 개발자는 변수

예를 들면

class Person {
var $name;
function getName() {
return $this->name;
}
function setName($name) {
$this->name = $name;
}
function Person($name) {
$this->setName($name);
}
}

function changeName($person, $name) {
$person->setName($name);
}

$person = new Person("Andi");
changeName($person, "Stig");
print $person->getName();

php4에서 이런 코드는 “Andi”를 출력한다. 이 이유는 우리가 변수로 changeName() 함수에 객체 $person을 넘기고, 그래서, $person은 복사되고 changeName()은 $person의 복사에서 작업한다. 이런 동작은 자바 같은 동작을 기대했던 많은 개발자에게는 직관적이지 않다. 자바에서 변수는 정확이 핸들(또는 포인터)을 객체에 붙들고 있다. 그러므로 그것은 단지 핸들만 복사될 뿐 전체 객체는 복제되지 않는다.

PHP 4에서 사용자는 두가지 부류가 있다, 이런 문제를 인식하는 개발자와 인식하는 개발자이다.

구형 객체 모델은 위에 말한 문제를 유발할 뿐만 아니라 존재하는 객체 모델의 상위에 추가적인 특징을 구현하는데 방해되는 기본적인 문제를 유발하기도 한다.

PHP 5에서, 객체 모델의 기반은 객체 핸들로 다시 작성되었고, 여러분이 clone 키워드를 사용하여 객체의 클론을 명시하지 않는 한, 여러분은 여러분의 객체의 내부에서 절대 만들 지 않는다. PHP5에서 참조로 객체를 넘기는 것이 필요하지 않고 참조로 그것은 할당하지도 않는다.

알아두기:참조로 넘기는 것과 참조로 할당하는 것은 여전히 지원한다. 이경우에 여러분은 변수의 내용을(객체나 다른 타입) 정확히 바꾸기를 원한다.

새로운 객체 지향 특징
새로운 객체지향 특징은 이 절에서 상세히 설명하기에 너무 방대하다. 객체지향 언어 챕터에서 자세히 나와있다.주로 새로운 특징 목록은 밑에 나열해 놓았다.

1. 메소드와 속성을 위한 public/private/protected 접근 수정자

일반적인 OO 접근 수정자의 사용은 메소드나 속성에 접근을 제어하게 해준다.

class MyClass {
private $id;

public function getId() {
return $this->id;
}
}

2. 유일(unified) 생성자 이름 __construct()

클래스의 이름이 되는 생성자 대신에, 이제는 __construct()로 선언 된다. 클래스 계층 안에서 클래서의 이동을 쉽게 만든다.

class MyClass {
function __construct() {
print "생성자 내부";
}
}

3. __destructor() 메소드를 정의함으로서, 객체 소멸자를 지원한다.

객체가 파괴될 때, 실행되던 소멸 함수를 정의가 허락한다.

<?
class MyClass {
function __destruct() {
print "객체 파괴";
}
}
?>

4. 인터페이스

is-a 관계 보다 완전하게 하기 위한 클래스를 위한 능력을 준다. 클래스는 한 클래스로부터 상속받을 수 있으나 그것이 원하는 것처럼 많은 인터페이스처럼 구현하지는 않는다.

interface Display {
function display();
}

class Circle implements Display {
function display() {
print "Displaying circle";
}
}

5. instanceof 연산자

is-a 관계 확인을 위해 언어 수준의 지원. Php4의 is_a() 함수는 지금 비난받는다.

if ($obj instance of Circle) {
print '$obj 은 Circle';
}

6. final 메소드

final 메소드 키워드는 여러분이 상속하는 클래스가 그것을 오버로드 할수 없는 것을 표시하는 메소드이다.

class MyClass {
final function getBaseClassName() {
return __CLASS__;
}
}

7. final 클래스

final로서 클래스를 선언하고, 그것은 상속할 수 없다. 다음과 같은 예제는 에러를 내보인다.

final class FinalClass {
}

class BogusClass extends FinalClass {
}

8. 명시 객체 복제(cloning)

객체를 복제하기 위해, 여러분들은 clone 키워드를 사용해야 한다. 여러분은 아마 복제 처하는 동안 호출될 __clone() 메소드를 선언했을 것이다.

class MyClass {
function __clone() {
print "Object is being cloned";
}
}
$obj = new MyClass(); // 이것은 인스턴스를 만들고
clone $obj;// 이것은 객체를 복제함

9. 클래스 상수

클래스 정의는 상수를 포함 할 수있고, 클래스를 이용하여 참조된다

class MyClass {
const SUCCESS = "Success";
const FAILURE = "Failure";
}
print MyClass::SUCCESS;

10. 정적 멤버

클래스 정의는 이제 클래스를 통해 접근 가능한 정적 멤버(속성)를 포함 할 수있다. 정적 멤버의 일반적인 사용은 독자 패턴에서 이다.

class Singleton {
static $instance = NULL;
function getInstance() {
if ($this->instance == NULL) {
$this->instance = new Singleton();
}
return $this->instance;
}
}

11. 정적 메소드

여러분은 이제 비객체 문맥으로부터 호출되는 것 허락하게하는 정적으로 메소드를 정의할 수 있다. 정적 메소드는 그들이 모든 특정 객체에 묶이지 않고 $this 변수를 정의 하지 않는다.

<?
class MyClass {
static function helloWorld() {
print "Hello, world";
}
}
MyClass::helloWorld();
?>

12. 추상 클래스

클래스는 인스턴스되는것으로부터 그것을 방해하기 위해 추상으로 선언될 수 있다. 그렇지만, 여러분들은 추상 클래스로부터 상속할 수 있다.

abstract class MyBaseClass {
function display() {
print "Default display routine being called";
}
}

13. 추상 메소드

메소드가 추상으로 선언되고, 그 때문에 상속하는 클래스에 그 정의를 양보한다. 추상메소드를 포함하는 클래스는 추상으로 선언된다.

abstract class MyBaseClass {
abstract function display();
}

14. 클래스 타입 힌트

함수 선언은 파라미터를 위해 클래스 타입 힌트를 포함하기도 한다. 만약 함수가 부적절한 클래스 타입으로 호출되면, 에러가 발생한다.

function expectsMyClass(MyClass $obj) {

}

15. 메소드에서 리턴되는 역참조(defrferencing)를 지원한다.

PHP 4, 여러분은 메소드로부터 반환되는 객체를 반참조할수 없었다. 여러분은 더미(dummy 변수에 첫 객체를 할당해야하고, 그것을 역참조 한다.

PHP 4:

$dummy = $obj->method();
$dummy->method2();

PHP 5:

$obj->method()->method2();

16. 반복자(Iterators)

php5는 php 클래스와 php 확장 클래스 둘 다 반복자 인터페이스을 구현하게 해준다. 여러분은 여러분이 foreach() 언어 생성자를 사용으로 클래스의 인스턴스를 반복할수 있을 인터페이스를 구현한다.

$obj = new MyIteratorImplementation();
foreach ($obj as $value) {
print "$value";
}

function __autoload($class_name) {
include_once($class_name . "php");
}

$obj = new MyClass1();
$obj2 = new MyClass2();

이상 새롭게 강화된 객체지원에 관해 나열했는데, 쉽게 애기하면, 자바만큼 객체를 지원한다. 대략 이런 애기입니다.

다른 새로운 언어 특징
1. 예외 핸들링

PHP 5에서 잘 알고있는 try/throw/catch/ 구조적 예외 처리 개념을 추가했다. 여러분은 Exception 클래스에서 상속한 객체를 던지게(throw) 만들 수 있다. (자바 써 보신 분은 대단히 반가워 할 내용입니다. 이제 if문을 줄이자!)

class SQLException extends Exception {
public $problem;
function __construct($problem) {
$this->problem = $problem;
}
}

try {
...
throw new SQLException("Couldn’t connect to database");
...
} catch (SQLException $e) {
print "Caught an SQLException with problem $obj->problem";
} catch (Exception $e) {
print "Caught unrecognized exception";
}

현재, 이전버전과 호환성 목적을 위해, 대부분의 내부 함수는 예외로 던질 수 없다. 이미 존재하는 set_error_handler()과 비슷하게, 여러분은 스크립트 종료전에 처리할 수 없는 예외를 잡기위해 set_exception_handler()를 사용한다.

2. 참조를 이용한 foreach

php4에서 여러분은 그 배열을 통해 반복하거나 그 변수를 수정할수 없었다. Php5는 ‘&’(reperence) 표시로 foreach() 반복문을 표시함으로서 지원한다.

foreach ($array as &$value) {
if ($value === "NULL") {
$value = NULL;
}
}

3. 참조 인자를 위한 디폴트 변수

php4에서 디폴트 변수는 변수로 넘겨지는 파라미터만 주어졌다. 참조 파라미터로 넘지겨지는 디폴트 변수를 주는 것은 지금 지원된다.

function my_func(&$arg = null) {
if ($arg === NULL) {
print '$arg is empty';
}
}
my_func();

3.일반적인 PHP 변화

XML과 웹 서비스
언어에서 다음과 같은 변화로는, php5에서 XML 업데이트는 아마도 중요하고 멋진것이다. Php5에서, 강화된 XML 기능은 다른 언어와 비교해서 동등하거나 따라잡았다.

기반
php4에서 xml지원은 다양한 바탕 XML 라이브러리를 사용하여 구현되었다. SAX 지원은 오래된 Expat 라이브러리를 사용하여 구현 되었고, XSLT는 Sablotron 라이브러리를 사용하여 구현되었고, DOM은 그놈(GNOME) 프로젝트에 의해 보다 강력한 libxml2 라이브러리를 사용하여 구현되었다. 다양한 라이브러리의 사용은 그것이 xml 지원할때 php4를 뛰어나게 만들지 못했다. 관리가 어설펐다(poor). 새로운 XML 표준은 항상 지원되지 않았고, 성능은 그것을 가졌을 때 만큼 좋진 않았고, 다양한 XML 확장 사이에 상호운영성은 존재하지 않았다.

Php 5에서 모든 XML 확장은 suberb libxml2 XML toolkit(http://www.xmlsoft.org)을 사용하여 재작성되었다. 그것은 관리하기 좋고 XML 기술을 php로 가져오는 XML 표준의 효과적인 구현하는 매우 다양한 특징이 있다.
이상 언급한 확장(SAX, DOM 그리고 XSMT)는 지금 새로운 추가 확장 SimpleXML과 SOAP을 포함하는 libxml2를 사용한다.

SimpleXML
아마도 1-2년 전으로 뒤돌아 보면, SimpleXML이 XML 파일로 작업하는 php 개발자에게 혁명임이 명확하다. SimpleXML은 정말 바보(dummies)를 위한 XML로 불려졌다. DOM과 SAX로 처리하는 대신에, SimpleXML은 PHP 객체로서 XML 파일을 보여준다. 여러분은 쉽게 접근하는 요소(elements)와 속성(attributes)를 읽고 작성하거나 반복할 수 있다.

다음 XML 파일을 보자

<clients>
<client>
<name>John Doe</name>
<account_number>87234838</account_number>
</client>
<client>
<name>Janet Smith</name>
<account_number>72384329</account_number>
</client>
</clients>

다음의 코드는 클라이언트 이름과 계좌번호를 출력한다.

$clients = simplexml_load_file('clients.xml');
foreach ($clients->client as $client) {
print "$client->name has account number $client->account_number ";
}
SimpleXML이 얼마나 간결한지 명백하다.
SOAP
Php4의 공식적인 SOAP지원은 부족했었다. 대부분 일반적이로 사용되는 SOAP 구현은 PEAR이다 그러나 그것은 php에서 전체적으로 구현되었으나, C 확장과 마찬가지로 수행할 수 없었다. 다른 이용 가능한 C 확장은 안정적이지도 널리 채택되지도 않았다. 그러므로 php 5 배포판에 포함되지 않았다.

Php5에서 SOAP 지원은 C 확장으로서 완벽하게 재작성되었고, 비록 그것이 늦은 단계에 완성되었지만, 그것은 표준의 대부분의 구현 때문에, 디폴트 배포판에 협력된다.

다음은 WSDL 파일에서 정의 된 SomeFuncion()을 호출한다

$client = new SoapClient("some.wsdl");
$client->SomeFunction($a, $b, $c);
New MySQLi(MySQL Improved) 확장
Php5를 위해, MySQL AB (http://www.mysql.com)은 MySQL 4.1과 그 이상버전에서, 새로운 기능의 포든 장점을 취하는 새로운 MySQL 확장을 작성했다. 구형 MySQL 확장에 반하여, 새로운 것은 여러분에게 함수형, 객체지향 인터페이스 둘다 사용할 수 있고, 그래서 여러분은 여러분이 좋아하는 것을 선택할 수 있다. 이 확장으로 지원되는 새로운 특징은 다음과 같다.
미리 준비된 문장(Prepared statements), 가변 결속(variable binding), SSL과 압축된 커넥션, 트랜젝션 제어, 복제(replication)…

SQLite 확장
SQLite(http://www.sqlite.org)의 지원은 php 4.3.x에서 처음 도입되었다. 그것은 SQL 서버를 요구하지 않는 내장형 SQL 라이브러리이고 SQL 서버의 규모를 요구하지 않는 어플리케이션에 매우 적합하다. 또는 만약 여러분이 ISP에서 SQL 서버에 접근할 수 없는 경우에도 적합하다. SQLite라는 이름과는 다르게 그것은 매우 다양한 특징과 트랜젝션, sub-select, view 그리고 대규모 DB file을 지원한다. 그것은 php 5버전으로서 언급되었다.
왜냐하면 그것은 php4의 뒤늦게 도입되었고, 객체지향 인터페이스와 반복자를 지원을 제공되면서 php 5의 장점을 취했기 때문이다.

Tidy 확장
Php 5는 유용한 Tidy (http://tidy.sf.net/) 라이브러리를 포함한다 그것은 php 개발자가 HTML 문서를 구문분석, 분류, 청소, 복구하도록 도와준다. Tidy 확장은 함수, 객체지향 인터페이스를 지원하고 그것의 API는 php 5 예외 메커니즘을 사용한다.
펄 확장
기본 php5 패키지에서 포함되지는 않지만, 펄 확장은 여러분이 펄 스크립트를 호출하는 것을 가능하게 한다. 펄 객체를 사용하고 다른 펄 기능을 php에서 사용할 수 있다. 이런 새로운 확장은 PECL (PHP Extension Community Library) 저장소(repository) http://pecl.php.net/package/perl 에 있다. (php가 펄객체를 사용할 수 있다고 합니다)
Php5에서 다른 새로운 것들
새로운 메모리 메니저
젠드 엔진은 새로운 메모리 관리자를 가지고 있다. 두 주요 장점은 멀티 쓰레드 환경을 위한 지원과 각 요청 후에 할당된 메모리 블록은 보다 효율적이다.

윈도우 95 지원의 취소
윈도우 95 플랫폼상에 운영되는 php는 더 이상 지원되지 않는다. 왜냐하면 MS사가 윈도우 95를 공식적으로 지원하지 않을 거라고 했기 떄문에, php 개발 공동체도 그렇게 하기로 결정했다.

4. 요약

여러분은 php5에서 많은 발전이 인상적일것이다. 위에 언급했듯이, 이 챕터는 모든 발전을 다 다루지는 않는다. 다른 추가적인 발전은 많은 버그가 수정되었고 대단히 발전했다는 것이다.
, .