PHP 프로그래밍

본문 바로가기
사이트 내 전체검색


Web Programming >> PHP Programming
[목차]
제20장 PHP의 세션 동작 원리 이해하기

    1. PHP의 세션 동작 원리 이해하기

 

이전 장에서 클라이언트 측에 정보를 저장하는 쿠키는 저장공간의 제약과 응답시간의 증가 라는 한계를 갖고 있다고 말했습니다. '세션'이라고 불리는 기술은 서버 측에 정보를 저장함으로써 이런 한계를 넘어설 수 있도록 해줍니다. 그러나 쿠키가 여러 RFC문서에 정의된 명확한 규약을 갖는 것에 비해 세션은 이런식으로 구현돼야 한다는 규약을 갖지 않습니다. 각 클라이언트를 구분해 서버 측에 정보를 저장할 수 있는 것을 세션이라고 부를 뿐 구현 방법은 프로그래밍 환경에 따라 달라질 수  있습니다. 만약 여러분이 앞의 기능을 만족하는 것을 만들었다면 그것도 세션이라고 부를 수 있겠조.

어쨌든 PHP3.0.x이전 버전에서는 세션 기능을 지원하는 모듈이 없었기 때문에 'PHP Base Library'의 세션 클래스 같이 PHP코드로 작성된 일반적인 라이브러리를 사용하거나, 스스로 세션 기능을 구현해야 했습니다. 하지만, 4.0.x버전이후에는 세션을 제어할 수 있는 모듈을 기본적으로 제공하기 때문에 더 이상 이런 번거로운일을 할 필요는 없게 됐지요. 그럼  이 기본적인 세션 모듈에 대해서 하나하나 알아보겠습니다.

 

1. 세션 시작시 일어나는 일

쿠키가 setcookie()와 $HTTP_COOKIE_VARS로 대표되는 간단한 읽기/쓰기 동작만을 갖고 있다면 세션은 이것보다는 약간 복잡한 작업을 거쳐야 합니다. 우선 지금부터 세션을 시작한다는 의미로서  session_start()함수를 호출해야 합니다.

예제1 : session_start.php

<?php

 

// 세션을 시작한다.

session_start();

 

// 세션ID의 이름을 출력한다.

print "세션ID 이름 = " . session_name() . "<br>";

 

// 세션ID로 할당된 이름을 출력한다.

print "세션ID 내용 = " . session_id() . "<br>";

 

?>

 

 

위의 예제는 간단한 코드이지만, 내부적으로는 이것 저것 하는 것이 많습니다. 특별한 세션과 관련된 옵션을 지정하지 않았다면, 첫 번째 요청 때 /tmp 디렉토리 밑에 유일한 이름의 파일을 생성하고 PHPSESSID라는 이름의 쿠키를 클라이언트에게 전송합니다. 이 PHPSESSID라는 이름의 쿠키를 클라이언트에게 전송합니다. 이 PHPSESSID는 보통 '세션ID'라고하는데 임의의 문자열을 내용으로 가지며 각 클라이언트의 요청을 구분해 줍니다. 실제 생성되는 파일이름에는 'sess_'라는 접두어가 붙습니다. 그리고, 쿠키를 전송할 때 사전에 어떤 출력도 있어서는 안되므로 session_start()를 호출하기 전에 실수로 출력하는 일이 없도록 주의해야 합니다.

 

2. 세션변수의 등록과 접근

session_start()로 세션의 초기화를 마쳤다면, session_register("세션 변수")로서 세션 변수를 등록하는 과정을 거칩니다. 어떤 변수들이 세션 변수로 등록되었는지 알고 싶을 경우에는 session_is_registered("세션변수")를 사용합니다.

예제2 : session_register.php

 <?php

 

// "count"라는 이름의 세션변수를 등록한다.

// session_start()가 묵시적으로 호출된다.

session_register("count");

 

// $HTTP_COOKIE_VARS와는 달리 $HTTP_SESSION_VARS의 값을

// 변하는 것은 다음 접속에도 그대로 반영된다.

$HTTP_SESSION_VARS["count"]++;

 

print $HTTP_SESSION_VARS["count"];

 

?>

 

위의 리스트는 세션 변수를 등록하고 접근하는 예인데, 쿠키와 유사하게 세션도 "$세션변수"형식의 전역 변수나 $HTTP_SESSION_VARS["세션변수"]로 접근함녀 됩니다. 쿠키를 담당하는 $HTTP_COOKIE_VARS["쿠키이름"]이 읽기만 하고 값을 변화시킬 때는 setcookie()로 한다는 것 아직 기억하겠지요. 하지만 세션은 쿠키와 달리 이런 변수의 값을 변화시키면 다음 요청 때 반영됩니다. 또한 배열을 그대로 사용할 수 있다는 점도 쿠키에 비해서 좀더 낫습니다. 하지만 여러분의 시스템에서 위의 예제가 제대로 동작한다고 보장할 수 없습니다. 그 이유는 4.0.5이전 버전에서는 전역변수 형태의 세션 변수를 동시에 사용할 수 없고, register_globals옵션에 따라 어느 한쪽만을 사용할 수 있는 버그를 갖고 있기 때문입니다. 이버그는 4.0.6버전 부터는 양쪽 다 사용할 수 있도록 수정됐으므로, 여러분의 PHP버전을 체크하기 바랍니다.

 

3. 세션 변수 제거

session_unregister()라는 함수가 있기는 하지만, 이것은 전역 변수에 등록된 세션 변수를 제거할 뿐이지 실제로 세션변수의 내용까지 파괴하는 것은 아닙니다. $HTTP_SESSION_VARS에는 여전히 남아 있으므로, 마지막 줄에 session_unregister("name")을 추가한다고 다른 결과를 가져오지는 않습니다. 세션변수의 내용을 삭제하고 돌리는 것은 '$name= null;, $HTTP_SESSION_VARS["name"] = null;'을 동시에 사용하는 것과 같습니다. 하지만 일일이 이런 식으로 현재 세션과 관련된 모든 변수를 제거하는 것은 번거롭기 때문에 이런 경우에는 session_unset()이나 session_destroy()를 사용하면 됩니다. 어느 쪽을 사용하든 여러분이 지금까지 유지해온 정보가 한 순간에 사라지는 것을 보장하므로, 이 두함수를 호출하는 것은 신중하게 결정할 필요가 있습니다. 코드의 다른 곳에서 세션을 사용하고 있는 중일 수도 있으므로 일방적으로 세션을 삭제하는 것은 문제가 발생할 수 있게지요. 멀티세션인 경우 상관 없겠지만, PHP의 세션모듈은 기본적으로 멀티세션을 지원하지 않습니다.

 

4. 세션의 만료시간

쿠키의 경우 각각의 쿠키별로 만료시간을 지정할 수 있는 것과 는 달리 세션은 세션 변수별로는 만료시간을 지정할 수 없습니다. 대신 설정파일의 session.gc_maxlifetime 옵션에서 세션이 유지되는 시간을 초단위로 지정할 수 있습니다. 디폴트는 1440초입니다. 여기서 지정된 시간 동안은 세션이 가비지 컬렉터에 의해 제거되지 않습니다. 세션 ID로서 전달된 쿠키의 만료시간도 session.cookie_lifetime 옵션이나 session_get_cookie_params()함수로 지정할 수 있는데 디폴트는 0, 즉브라우저가 종료할 때까지입니다. 이 때 쿠키로 전달된 세션 ID가 유효하더라도 해당 세션의 정보가 가비지 컬렉터에 의해 제거될 수 있으므로 session.gc_maxlifetime을 session.cookie_lifetime보다 길 게 설정하지 않도록 주의하기 바랍니다.

 

5. 파일이 아닌곳에 세션 정보를 저장하기

이미 말한 것처럼 세션 정보를 저장하기 위해서 반드시 파일을 이용할 필요는 없습니다. 고유메모리와 같은 좀 더 빠른 저장 장치를 이용할 수도 있고, 세션과 관련된 것만을 전문으로 처리하는 데이터베이스 서버를 생각해 볼 수도 있습니다. 이런 저장장치를 이용하기 위해서는 session_set_save_handler()함수로서 새로운 세션 관리 핸들러를 등록하면 되는데, 예제3은 mysql을 이용해 핸들러를 등록하는 예입니다. 중국인 프로그래머인 장잉이 작성한 것입니다.

예제3 : session_mysql.php

<?php

 

/* ------------------------------------------------------------------------

 * session_mysql.php

 * ------------------------------------------------------------------------

 * PHP4 MySQL Session Handler

 * Version 1.00

 * by Ying Zhang (ying@zippydesign.com)

 * Last Modified: May 21 2000

 *

 * 설명 :

 * 이 프로그램을 사용하기 이 전에 다음과 같은 테이블을 여러분의

 * mysql 데이터베이스 서버에 생성해 주어야 합니다.

 *

 * CREATE TABLE sessions (

 *      sesskey char(32) not null,

 *      expiry int(11) unsigned not null,

 *      value text not null,

 *      PRIMARY KEY (sesskey)

 * );

 *

 */

 

 

// 여러분의 환경에 맞추어 아래의 설정변수들을 설정할 필요가 있습니다.

 

$SESS_DBHOST = "localhost";             /* database server hostname */

$SESS_DBNAME = "sessions";              /* database name */

$SESS_DBUSER = "phpsession";            /* database user */

$SESS_DBPASS = "phpsession";            /* database password */

 

$SESS_DBH = "";

$SESS_LIFE = get_cfg_var("session.gc_maxlifetime");

 

 

function sess_open($save_path, $session_name) {

        global $SESS_DBHOST, $SESS_DBNAME, $SESS_DBUSER, $SESS_DBPASS, $SESS_DBH;

    

        if (! $SESS_DBH = mysql_pconnect($SESS_DBHOST, $SESS_DBUSER, $SESS_DBPASS)) {

                echo "<li>Can't connect to $SESS_DBHOST as $SESS_DBUSER";

                echo "<li>MySQL Error: ", mysql_error();

                die;

        }

    

        if (! mysql_select_db($SESS_DBNAME, $SESS_DBH)) {

                echo "<li>Unable to select database $SESS_DBNAME";

                die;

        }

    

        return true;

}

 

function sess_close() {

        return true;

}

 

function sess_read($key) {

        global $SESS_DBH, $SESS_LIFE;

    

        $qry = "SELECT value FROM sessions WHERE sesskey = '$key' AND expiry > " . time();

        $qid = mysql_query($qry, $SESS_DBH);

    

        if (list($value) = mysql_fetch_row($qid)) {

                return $value;

        }

    

        return false;

}

 

function sess_write($key, $val) {

        global $SESS_DBH, $SESS_LIFE;

 

        $expiry = time() + $SESS_LIFE;

        $value = addslashes($val);

    

        $qry = "INSERT INTO sessions VALUES ('$key', $expiry, '$value')";

        $qid = mysql_query($qry, $SESS_DBH);

    

        if (! $qid) {

                $qry = "UPDATE sessions SET expiry = $expiry, value = '$value' WHERE sesskey = '$key' AND expiry > " . time();

                $qid = mysql_query($qry, $SESS_DBH);

        }

    

        return $qid;

}

 

function sess_destroy($key) {

        global $SESS_DBH;

    

        $qry = "DELETE FROM sessions WHERE sesskey = '$key'";

        $qid = mysql_query($qry, $SESS_DBH);

    

        return $qid;

}

 

function sess_gc($maxlifetime) {

        global $SESS_DBH;

    

        $qry = "DELETE FROM sessions WHERE expiry < " . time();

        $qid = mysql_query($qry, $SESS_DBH);

    

        return mysql_affected_rows($SESS_DBH);

}

 

session_set_save_handler("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");

 

?>

 

 

예제3을 이용하기 위해서는 require("session_mysql.php");를 여러분의 코드 맨 앞에 집어넣는 것과 별도의 테이블 생성과 환경설정 작업을 해야 합니다. 하지만, 그렇다고 지금까지 세션 변수를 다루는 내용이 바뀌는 것은 아닙니다. 각각의 저장장치에 해당하는 핸들러 함수를 작성한 다음, 각각의 핸들러 함수를 교체하는 것만으로 세션으 ㅣ실제 저장소를 바꾸는 것이 가능합니다. 또한 가비지 컬렉션의 알고리즘 같은 부분을 session.gc_maxlifetime에만 의존하는 것이 아닌 세션ID의 만료시간과도 연관되게 개선시킬 수 있습니다. 한편 이렇게 특정한 함수(보통 이런 함수를 핸들러(handler) 또는 콜백함수(callback)라고 부릅니다.)를 매개변수로 지정하는 기법은 전체 라이브러리의 기능을 보다 손쉽게 확장할 수 있게 하고, 사용하는 프로그래머에게 또 다른 선택의 기회를 제공합니다. 여기서는 세션을 예로 들었지만, 다른 모듈을 사용할 때에도 심심찮게 만날 수 있는 기법이므로 앞의 예제를 보면서 개념을 확실히 잡는 것이 좋겠습니다.

 

6. 세션을 이용한 간단한 카운터

지금까지 세션에 관한 환경을 어떻게 조작할 것인지 알아봤습니다. 이것보다 더 세세한 부분까지 설정할 수 있는 옵션과 함수에 대해 알아보는 것은 어러분의 몴으로 돌리겠습니다. 예제4는 세션을 활용해서 사용자의 접속횟수를 기로갛고 알아내는 클래스에 관한 것입니다. 즉, 사용자가 접속한 동안에는 전체 카운트를 하지 않으며 새로 접속할 때만 전체 카운트를 하도록 동작합니다. 사용자별로 카운트를 하는 기능이나 시간별로 카운트를 통계하는 거창한 기능은 없고, 대부분 카운터의 기본 기능을 구현한 것이라고 할 수 있습니다. 파일 쓰기에 관련한 세마포어로서 카운트 동작의 원자성을 보장하는 루틴이 들어가야 제대로 된 카운터로서 작동하지만, 세마포어의 동작을 설명해야 하므로 예제4에서는 생략했습니다. 또한 예제4에서는 세션을 사용했지만, 세션과 관련된 루틴을 쿠키와 대치해서 같은 일을 하는 프로그램이 되도록 작성할 수 있습니다. 이렇게 세션으로 작성된 것을 쿠키를 사용한 코드로 재작성하는 일이나 그 반대의 일은 빈번히 발행하므로 연습 삼아 직접 해보기 바랍니다.

예제4 : counter.php

<?php

 

class Counter {

    var $filename;

    

    function Counter($filename = "/tmp/count.dat") {

        // 해당하는 파일이 없으면 파일을 생성하고 내용을 0으로 초기화한다.

        if (false == file_exists($filename)) {

            $file = fopen($filename, "w");

            fwrite($file, 0);

            fclose($file);

        }

        

        $this->filename = $filename;

        

        // 세션변수를 등록한다.

        session_register("counted");

        session_register("current_count");

    }

    

    function add() {

        global $HTTP_SESSION_VARS;

 

        // 처음 사이트에 접속했을 경우에만 카운트를 한다.

        if (!isset($HTTP_SESSION_VARS["counted"])) {

            $total = $this->get_total() + 1;

            $file = fopen($this->filename, "w");

            fwrite($file, $total);

            fflush($file);

            fclose($file);

            

            $HTTP_SESSION_VARS["counted"] = true;

        }

        

        // 연속된 접속에 대한 카운트를 한다.

        $HTTP_SESSION_VARS["current_count"]++;

    }

    

    function get_total() {

        $file = fopen($this->filename, "r");

        $total = fread($file, filesize($this->filename));

        fclose($file);

 

        return $total;

    }

    

    function get_current() {

        global $HTTP_SESSION_VARS;

 

        return $HTTP_SESSION_VARS["current_count"];

    }

}

 

 

$count = new Counter();

$count->add();

 

print "전체방문객 회수 : " . $count->get_total() . "<br>";

print "현재방문객의 접속 회수 : " . $count->get_current() . "<br>";

 

?>

 

세션을 사용하면 클라이언트에서 알 수 있는 것은 오직 세션ID뿐이기 때문에 좀더 안전하고 쿠키를 남발할 필요도 없다는 장점을 갖습니다. 하지만 세션과 관련된 일을 처리하기 위해 서버가 좀더 바빠진다는 단점 또한 잊어서는 안됩니다. 또한 이글이 4.0.x에서 기본적으로 제공하는 세션 모듈을 기준으로 하고 있지만, 3.0.x의 PHP환경에서 작업하는 사람도 있을 것이고, 그런 경우에는 쿠키를 안전하게 사용하는 것과 언급한 세션 라이브러리를 사용하는 것에 대한 선텍에 좀더 신중할 필요가 있습니다. 여러분이 악조건 속에서도 꿋꿋하게 사용자가 원하는 기능을 구현할 수 있는 프로그래머가 되기를 바랍니다.

[목차]

개인정보취급방침 서비스이용약관 모바일 버전으로 보기 상단으로

TEL. 063-469-4551 FAX. 063-469-4560 전북 군산시 대학로 558
군산대학교 컴퓨터정보공학과

Copyright © www.leelab.co.kr. All rights reserved.